从Spring Database中的数据库每个请求重新加载UserDetails对象 [英] Reload UserDetails Object from Database Every Request in Spring Security

查看:141
本文介绍了从Spring Database中的数据库每个请求重新加载UserDetails对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have been looking for a way to reload our Spring Security UserDetails object every request and cannot find an example anywhere.

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

Does anyone know how to do such a thing?

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

Basically, we want to reload a user's authorities every request because that user's authorities might change from web request to web request.

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

For instance, a user that is logged in and is subsequently granted a new authority (and is notified that they have a new authority via email), the only way that I know of that user actually gaining that new authority is by logging out and then logging back in again. Which I would like to avoid if possible.

任何友好的建议都表示赞赏。

Any friendly advice is appreciated.

推荐答案

最后,两年后,对于上述问题以及这个问题后的六年,这里是一个回答如何使用Spring为每个请求重新加载用户的UserDetails ...

Finally, after two years, for the question above and for six years after this question, here is an answer on how to reload a user's UserDetails per request with Spring...

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

To reload a user/security context per request, it is important to override the default behavior of Spring Security's HttpSessionSecurityContextRepository, which implements the SecurityContextRepository interface.

HttpSessionSecurityContextRepository是Spring Security用于从HttpSession获取用户安全上下文的类。调用此类的代码是将SecurityContext放在threadlocal上的代码。因此,当调用 loadContext(HttpRequestResponseHolder requestResponseHolder)方法时,我们可以转向并向 DAO 存储库并重新加载用户/委托人。

The HttpSessionSecurityContextRepository is the class that is used by Spring Security to get the user's security context from the HttpSession. The code that calls this class is what places the SecurityContext on threadlocal. So when the loadContext(HttpRequestResponseHolder requestResponseHolder) method is called we can turn around and make a request to a DAO or Repository and reload the user/principal.



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


Some things of concern that have not quite been figured out quite yet.

此代码线程是否安全?

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

I have no idea, it depends on if there is a new SecurityContext created per thread/request into the web server. If there is a new SecurityContext created life is good, but if not, there could be some interesting unexpected behavior like stale object exceptions, the wrong state for a user/principal being saved to the data store, etc...

我们的代码低风险,我们还没有尝试过测试潜在的多线程问题。

Our code is 'low risk enough' that we haven't tried to test potential multi-thread issues.



每次请求调用数据库是否会影响性能?

最有可能,但我们还没有看到我们的网络服务器响应时间发生了显着变化。

Most likely, but we haven't seen noticeable change in our web server response times.

关于此主题的几个快速说明......

A couple of quick notes on this subject...


  • 数据库非常智能,他们有算法知道缓存特定查询的内容和时间。

  • 我们正在使用hibernate的二级缓存。



我们从此次更改收到的好处:


Benefits we've received from this change:


  • 它用来表示我们用来表示P的UserDetails对象rincipal不是Serializable,因此当我们停止并重新启动我们的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;
    }
}



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


To plug a new SecurityContextRepository with xml configuration, just set the security-context-repository-ref attribute on the security:http context.

示例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 Database中的数据库每个请求重新加载UserDetails对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆