Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/343.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在Spring Security中,每个请求都从数据库重新加载UserDetails对象_Java_Spring_Spring Security - Fatal编程技术网

Java 在Spring Security中,每个请求都从数据库重新加载UserDetails对象

Java 在Spring Security中,每个请求都从数据库重新加载UserDetails对象,java,spring,spring-security,Java,Spring,Spring Security,我一直在寻找一种方法,在每个请求中重新加载我们的SpringSecurityUserDetails对象,但在任何地方都找不到示例 有人知道怎么做这样的事吗 基本上,我们希望在每个请求中重新加载用户的权限,因为该用户的权限可能会从一个web请求更改为另一个web请求 例如,一个用户登录并随后被授予新权限(并通过电子邮件通知他们拥有新权限),我知道该用户实际获得新权限的唯一方法是注销,然后再次登录。如果可能的话,我想避免 非常感谢任何友好的建议。最后,两年后,对于上述问题以及六年后的问题,下面是关于

我一直在寻找一种方法,在每个请求中重新加载我们的SpringSecurityUserDetails对象,但在任何地方都找不到示例

有人知道怎么做这样的事吗

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

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


非常感谢任何友好的建议。

最后,两年后,对于上述问题以及六年后的问题,下面是关于如何使用Spring重新加载每个请求的用户详细信息的答案

要为每个请求重新加载用户/安全上下文,必须重写Spring security的默认行为,它实现了接口

HttpSessionSecurityContextRepository是Spring Security用来从HttpSession获取用户安全上下文的类。调用此类的代码将SecurityContext放置在threadlocal上。因此,当调用
loadContext(HttpRequestResponseHolder requestResponseHolder)
方法时,我们可以掉头向or发出请求并重新加载用户/主体


有些问题还没有完全解决

此代码是线程安全的吗?

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

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


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

很有可能,但我们还没有看到web服务器响应时间的显著变化

关于这个问题的几个简短说明

  • 数据库是超级智能的,它们有算法来知道缓存特定查询的内容和时间
  • 我们正在使用hibernate的二级缓存

我们从这一变化中获得的好处:

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



我正在尝试FilterSecurity Interceptor的重新验证技巧

用于使用JDBC userDetailsService进行表单登录

  • 身份验证的isAuthenticated集返回false
  • 将凭据设置为false。(将凭据保留在会话中
  • 当引发AuthenticationException时,注销然后重定向到登录页面
  • AuthenticationProvider

    认证为假

    package.spring;
    导入org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    导入org.springframework.security.core.Authentication;
    导入org.springframework.security.core.userdetails.userdetails;
    公共类MyDaoAuthenticationProvider扩展了DaoAuthenticationProvider{
    @凌驾
    受保护的身份验证CreateSuccesAuthentication(对象主体、身份验证、用户详细信息用户){
    身份验证结果=super.createSuccessAuthentication(主体、身份验证、用户);
    结果:setAuthenticated(false);
    返回结果;
    }
    }
    
    例外TranslationFilter的AuthenticationEntryPoint

    注销并重定向到登录页面

    package.spring;
    导入javax.servlet.http.HttpServletRequest;
    导入javax.servlet.http.HttpServletResponse;
    导入org.springframework.security.core.Authentication;
    导入org.springframework.security.core.AuthenticationException;
    导入org.springframework.security.core.context.SecurityContextHolder;
    导入org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
    导入org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    公共类MyLoginRuthenAuthenticationEntryPoint扩展了LoginRuthenAuthenticationEntryPoint{
    公共MyLoginUrlAuthenticationEntryPoint(字符串loginFormUrl){
    超级(loginFormUrl);
    }
    @凌驾
    此请求的受保护字符串determinuteUrlToUse(
    HttpServletRequest请求、HttpServletResponse响应、AuthenticationException异常){
    if(异常!=null){
    Authentication auth=SecurityContextHolder.getContext().getAuthentication();
    新的SecurityContextLogoutHandler()。注销(请求、响应、身份验证);
    SecurityContextHolder.getContext().setAuthentication(null);
    }
    返回此请求的super.determineurltouse(请求、响应、异常);
    }
    }
    
    根上下文.xml

    将凭据属性设置为false

    
    @Autowired
    private FindByIndexNameSessionRepository<Session> sessionRepo;
    
    public void tag(String username) {
        Map<String, Session> sessions = sessionRepo.findByIndexNameAndIndexValue
                (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
        for (Session s : sessions.values()) {
            s.setAttribute("reloadAuth", true);
            sessionRepo.save(s);
        }
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpSession session = ((HttpServletRequest) request).getSession();
        if (session != null) {
            Boolean reload = (Boolean) session.getAttribute("reloadAuth");
            if (Boolean.TRUE.equals(shouldReloadRoles)) {
                session.removeAttribute("reloadAuth");
                /* Do some locking based on session ID if you want just to avoid multiple reloads for a session */
                Authentication newAuth = ... // Load new authentication from DB
                SecurityContextHolder.getContext().setAuthentication(newAuth);
            }
        }
        chain.doFilter(request, response);
    }