当前位置: 首页 > 知识库问答 >
问题:

在Spring Security中每次请求都从数据库中重新加载

唐彦
2023-03-14

基本上,我们希望每一个请求都重新加载用户的权限,因为用户的权限可能会在不同的web请求之间发生变化。

例如,一个用户登录并随后被授予新的权限(并通过电子邮件通知他们拥有新的权限),我知道该用户实际获得新权限的唯一方法是注销,然后再次登录。如果可能的话,我会尽量避免。

欢迎任何友好的建议。

共有1个答案

茅华灿
2023-03-14

最后,在两年之后,对于上面的问题和这个问题之后的六年,这里有一个关于如何用Spring重新加载用户的每个请求的UserDetails的答案...

要在每个请求中重新加载用户/安全上下文,重写Spring Security的HttpSessionSecurityContextRepository的默认行为是很重要的,它实现了SecurityContextRepository接口。

HttpSessionSecurityContextRepository是Spring Security用于从HttpSession获取用户的安全上下文的类。调用该类的代码将SecurityContext放置在ThreadLocal上。因此,当调用LoadContext(HttpRequestResponseHolder,requestResponseHolder)方法时,我们可以转向并向DAO或存储库发出请求,并重新加载用户/主体。


有些事情还没有完全弄清楚。

这个代码线程安全吗?

我不知道,这取决于是否有一个新的SecurityContext创建的每个线程/请求到web服务器。如果有一个新的SecurityContext创建的生命是好的,但如果没有,可能会出现一些有趣的意外行为,如过时的对象异常、保存到数据存储的用户/主体的错误状态等等。

  • 数据库是超级聪明的,它们有算法知道什么和什么时候缓存特定查询。
  • 我们正在使用Hibernate的第二级缓存。


我们从此更改中获得的好处:

  • 我们用来表示主体的UserDetails对象过去是不可序列化的,因此当我们停止并重新启动tomcat服务器时,所有被反序列化的SercurityContexts都将具有一个空主体对象,并且我们的最终用户将由于空指针异常而收到服务器错误。既然UserDetails/Principal对象是可序列化的,并且每个请求都重新加载用户,我们就可以启动/重新启动服务器,而不必清理工作目录。
  • 我们没有收到任何客户关于其新权限未立即生效的投诉。

守则

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import xxx.repository.security.UserRepository;
import xxx.model.security.User;
import xxx.service.security.impl.acegi.AcegiUserDetails;

public class ReloadUserPerRequestHttpSessionSecurityContextRepository extends HttpSessionSecurityContextRepository {

    // Your particular data store object would be used here...
    private UserRepository userRepository;

    public ReloadUserPerRequestHttpSessionSecurityContextRepository(UserRepository userRepository) {

        this.userRepository = userRepository;
    }

    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {

        // Let the parent class actually get the SecurityContext from the HTTPSession first.
        SecurityContext context = super.loadContext(requestResponseHolder);

        Authentication authentication = context.getAuthentication();

        // We have two types of logins for our system, username/password
        // and Openid, you will have to specialize this code for your particular application.
        if (authentication instanceof UsernamePasswordAuthenticationToken) {

            UserDetails userDetails = this.createNewUserDetailsFromPrincipal(authentication.getPrincipal());

            // Create a new Authentication object, Authentications are immutable.
            UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());

            context.setAuthentication(newAuthentication);

        } else if (authentication instanceof OpenIDAuthenticationToken) {

            UserDetails userDetails = this.createNewUserDetailsFromPrincipal(authentication.getPrincipal());

            OpenIDAuthenticationToken openidAuthenticationToken = (OpenIDAuthenticationToken) authentication;

            // Create a new Authentication object, Authentications are immutable.
            OpenIDAuthenticationToken newAuthentication = new OpenIDAuthenticationToken(userDetails, userDetails.getAuthorities(), openidAuthenticationToken.getIdentityUrl(), openidAuthenticationToken.getAttributes());

            context.setAuthentication(newAuthentication);
        }

        return context;
    }

    private UserDetails createNewUserDetailsFromPrincipal(Object principal) {

        // This is the class we use to implement the Spring Security UserDetails interface.
        AcegiUserDetails userDetails = (AcegiUserDetails) principal;

        User user = this.userRepository.getUserFromSecondaryCache(userDetails.getUserIdentifier());

        // NOTE:  We create a new UserDetails by passing in our non-serializable object 'User', but that object in the AcegiUserDetails is transient.
        // We use a UUID (which is serializable) to reload the user.  See the userDetails.getUserIdentifier() method above.
        userDetails = new AcegiUserDetails(user);

        return userDetails;
    }
}


要用xml配置插入新的SecurityContextRepository,只需在security:http上下文上设置security-context-repository-ref属性。

XML示例:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                           http://www.springframework.org/schema/security
                           http://www.springframework.org/schema/security/spring-security-4.0.xsd">
    <security:http context-repository-ref="securityContextRepository" >
         <!-- intercept-url and other security configuration here... -->
    </security:http>

    <bean id="securityContextRepository" class="xxx.security.impl.spring.ReloadUserPerRequestHttpSessionSecurityContextRepository" >
        <constructor-arg index="0" ref="userRepository"/>
    </bean>
</beans>
 类似资料:
  • 问题内容: 我一直在寻找一种方法,可以在每个请求时重新加载我们的Spring Security UserDetails对象,并且无法在任何地方找到示例。 有人知道该怎么做吗? 基本上,我们希望为每个请求重新加载用户权限,因为该用户权限可能会从Web请求更改为Web请求。 例如,一个已登录并随后被授予新权限的用户(并通过电子邮件被通知他们具有新权限),我知道该用户实际获得该新权限的唯一方法是先注销然

  • 这是我的代码: 我想滚动我的卷轴,但不能,因为#refresh重新加载每3000。有人知道如何解决这个问题吗?

  • 我已经创建了一个应用程序的布局有三个标签在Android2.3。每个选项卡都有一个片段,在每个片段中,都有一个列表视图。在列表视图中显示的所有数据都来自互联网,数据总是在变化,所以每次我看那个片段的时候都想重新加载数据。例如,我现在正在观看“tab1”,当我单击“tab2”时,“tab2”中的数据将重新加载,而当我单击返回“tab1”时,“tab1”中的数据也将再次重新加载。我试过了 TabSAd

  • 问题内容: 我在Windows 7上将Elasticsearch与angularjs和oracle一起使用,它的工作原理越来越好(感谢stackoverflower帮助)。我对Elasticsearch有一个问题:我文档中的元素数量正在增加,我不知道为什么/如何做。我的由Elasticsearch索引的oracle表包含12010个元素,现在我在弹性文档中得到了84070个元素(通常由curl _

  • 问题内容: 我有ng-table的页面,可以很好地处理初始数据。我有一个选择框,该框根据选择的选项发送服务器请求,然后Iam将新数据获取为angular,但未使用ng- table更新。 这是我的看法: 我的控制器: 问题答案: $scope.$watch(‘result’, function () { $scope.tableParams.settings().$scope = $scope;

  • 问题内容: 我有两个变量“ userId”和“ name”。例如,当我单击“ SHOW USERID”按钮时,它可以正常工作并设置为“ renderUserId = true”,并使用“ render”显示它,但是如果我单击另一个“ SHOW”按钮,则表示Bean处于重建状态并且松动了“ renderUserId = true”,它变为“ false”和“ renderName = true”,因