从数据库中重新加载用户详细信息对象 春季安全中的每个请求

2022-09-04 20:40:17

我一直在寻找一种方法来重新加载我们的Spring Security UserDedetails对象每个请求,并且在任何地方都找不到示例。

有谁知道如何做这样的事情?

基本上,我们希望在每个请求中重新加载用户的权限,因为该用户的权限可能会从 Web 请求更改为 Web 请求。

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

任何友好的建议都是值得赞赏的。


答案 1

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

要为每个请求重新加载用户/安全上下文,重要的是要覆盖Spring Security的HttpSessionSecurityContextRepository的默认行为,该存储库实现了SecurityContextRepository接口。

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


一些令人担忧的事情还没有完全弄清楚。

此代码线程是否安全?

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

我们的代码“风险足够低”,以至于我们没有尝试测试潜在的多线程问题。


每个请求调用数据库是否会降低性能?

最有可能的是,但我们还没有看到Web服务器响应时间的明显变化。

关于这个主题的几个快速笔记...

  • 数据库非常智能,它们具有算法来了解缓存特定查询的内容和时间。
  • 我们正在使用休眠的第二级缓存。


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

  • 过去,我们用来表示主体的 UserDetails 对象不是可序列化的,因此当我们停止并重新启动 tomcat 服务器时,所有反序列化的 SercurityContext 都将具有空主体对象,并且我们的最终用户将由于空指针异常而收到服务器错误。现在 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>


答案 2

您好,所以想分享一些与基于令牌的身份验证相关的问题,在我的情况下是Oauth2。起初,我尝试了上面的hooknc方法,在我的情况下,我使用的是基于令牌的身份验证,所以我的身份验证对象是exidsIonOf Oauth2Authentication。与标准身份验证主体不同,Oauth2 身份验证对象由授权请求和身份验证对象构造。此外,主体是使用令牌本身构造的。因此,当尝试在另一个调用中重用令牌时,它最终会得到原则上的旧用户数据。因此,此方法不适用于基于令牌的身份验证。

我最初要明确的问题是,在用户更新用户设置之后,如果用户在之后进行其他API调用,则会导致旧的用户信息。我发现在更新后颁发新令牌不是尝试更新主体是一种更好的方法。

我还应该补充一点,我的身份验证Oauth2方案是完全无状态的,所有内容都存储在数据库中。


推荐