gpt4 book ai didi

java - Spring Security 中的每个请求都从数据库中重新加载 UserDetails 对象

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:20:28 24 4
gpt4 key购买 nike

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

有人知道怎么做吗?

基本上,我们希望在每次请求时重新加载用户的权限,因为该用户的权限可能会随着 Web 请求的不同而发生变化。

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

感谢任何友好的建议。

最佳答案

最后,两年后,对于上面的问题和this question之后的六年,这是关于如何使用 Spring 根据请求重新加载用户的 UserDetails 的答案...

要为每个请求重新加载用户/安全上下文,重要的是要覆盖 Spring Security HttpSessionSecurityContextRepository 的默认行为, 它实现了 SecurityContextRepository界面。

HttpSessionSecurityContextRepository 是 Spring Security 用于从 HttpSession 获取用户安全上下文的类。调用此类的代码将 SecurityContext 置于线程本地。因此,当调用 loadContext(HttpRequestResponseHolder requestResponseHolder) 方法时,我们可以转身向 DAO 发出请求或 Repository并重新加载用户/主体。


一些值得关注的事情还没有完全弄清楚。

这段代码线程安全吗?

我不知道,这取决于网络服务器中是否为每个线程/请求创建了一个新的 SecurityContext。如果有一个新的 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>

关于java - Spring Security 中的每个请求都从数据库中重新加载 UserDetails 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23072235/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com