有几种情况,我想更新用户/主体数据 以便反射(reflect)更改 当用户保持登录状态 (我不想强制重新认证)
从 session “内部”来看,这不是问题:
@PostMapping("/updateInfo")
fun updateMyData(
@AuthenticationPrincipal user: AppUser,
@Valid @RequestBody newInfo: UpdateDataRequest
): ResponseEntity<TestUserInfo> {
val testInfo = TestUserInfo(user, newInfo)
user.info = testInfo
val updatedUser = users.save(user)
return ResponseEntity.ok(updatedUser.info!!)
}
例如,当我允许用户更改他们自己的数据时,我可以轻松访问和更改 @AuthenticationPrincipal
- 在连续的请求中,我可以观察到数据已更新。当我需要从 session “外部”更改用户数据时,这是不同的。
用例
有两个用例:
一种)。管理员更改用户数据
乙)。用户确认他的电子邮件地址
现在一个)。显然发生在另一个 http-session 中,其中主体是具有一些管理员权限的用户。
对于 b)。您可能会问,为什么这不会在 session 中发生:我想要一个简单的一次性确认链接,即获取请求。我不能假设用户是通过设备上的 session 登录的,确认链接已打开。对我来说,做一个单独的预身份验证提供程序或其他东西来获得用户身份验证是不对的——然后会在浏览器上打开一个不再使用的不必要的 session 。
因此,在这两种情况下,当我通过 JPArepository 获取用户、更新数据并将其保存回来时,数据库中的更改都是最新的 - 但登录用户不知道该更改,因为他们的用户数据存储在http session 中,不知道需要更新。
请注意,我没有使用任何 redis/spring-session - 这只是一个普通的 http session ,所以据我了解,我不能使用
FindByIndexNameSessionRepository
.我试过的
SecurityContextRepository
- 但是,没有关于如何准确执行此操作的更多信息 - 我试图了解界面,但无法进一步了解。How to reload authorities on user update with Spring Security (使用redis忽略答案。)
SessionRegistry
,我也尝试在 bealdung 之后使用它- 但无济于事:我无法正确 session ,当登录用户有事件 session 时,getallprincipals() 始终为空。如果我有正确的 session ,我什至不确定如何从那里继续,因为 Aure 只是建议使用 expireNow()
这会强制重新身份验证 - 我想避免这种情况。 HandlerInterceptor
. Hasler Choo suggests带有哈希集的修改版本似乎更接近我的需要。如下所述 - 它有它的问题。 HandlerInterceptor
基于方法这是迄今为止我可以成功实现的唯一解决方案 - 但它似乎不是很灵活。到目前为止,我的实现仅涵盖用户角色的更改。
配置:
@Configuration
class WebMvcConfig : WebMvcConfigurer {
@Autowired
private lateinit var updateUserDataInterceptor : UpdateUserDataInterceptor
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(updateUserDataInterceptor)
}
}
处理程序拦截器:@Component
class UpdateUserDataInterceptor(
@Autowired
private val users: AppUserRepository
) : HandlerInterceptor {
private val usersToUpdate = ConcurrentHashMap.newKeySet<Long>()
fun markUpdate(user: AppUser) = usersToUpdate.add(user.id)
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
val auth = SecurityContextHolder.getContext().authentication
(auth.principal as? AppUser)?.apply {
synchronized(usersToUpdate) {
if (id in usersToUpdate) {
role = users.findById(id).get().role
usersToUpdate.remove(id)
}
}
}
return true
}
}
我宁愿不只是更新角色,而是替换整个原则 - 但主体是 final
在身份验证对象中。因此,每当 a 需要更新角色以外的其他内容时,都必须在此处特别提及。
剩下的问题:
HandlerInterceptor
还有其他解决方案吗? ? HandlerInterceptor
基于解决方案,允许我完全更新主体对象 最佳答案
I am not considering single instance applications
1. 三个因素在起作用
current session and current request
vs current session and next request
vs next session
)现在您可以从第一个因素中选择一个选项。但是对于第二个和第三个因素,您会以另一个因素的代价来优化一个因素。或者您尝试找到一个平衡点,例如尝试将受影响的用户列表保存在内存中,然后为受影响的用户访问数据库。
(不幸的是,您将受影响的用户列表保存在
UpdateUserDataInterceptor
中的优化不会工作,除非它是单实例应用程序,否则它不会存储在分布式内存中)2.现在根据我对您问题的理解,我正在对发挥作用的三个因素做出以下回答。
(稍后我将更新我对其他可能路径和这些路径可能实现的想法)
3. 所选路径的实现选项 -
next-request-with-db-calls-and-no-distributed-memory
作为请求过滤器链一部分的任何能够调用数据库的组件都可以通过更新 SecurityContext 来实现这一点。如果您在 SecurityContextRepository 中执行此操作,您将尽早执行此操作,您甚至可能有机会使用更新的原则恢复 SecurityContext,而不是更新已经创建的 SecurityContext。但是任何其他过滤器或拦截器也可以通过更新 SecurityContext 来实现这一点。
4. 详细了解每个实现
SecurityContextRepository
选项:看着
HttpSessionSecurityContextRepository
, 扩展它似乎很简单。public class HttpSessionSecurityContextRepository
implements SecurityContextRepository {
.....
public SecurityContext loadContext(HttpRequestResponseHolder reqRespHolder) {
HttpServletRequest request = reqRespHolder.getRequest();
HttpServletResponse response = reqRespHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
........
//retrieve the user details from db
//and update the principal.
.......
return context;
}
}
SecurityContextHolderStrategy
选项 看着
ThreadLocalSecurityContextHolderStrategy
, 看起来也很简单final class ThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
....
public void setContext(SecurityContext context) {
// you can intercept this call here, manipulate the SecurityContext and set it
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
.....
}
注:
principal
在身份验证对象中是最终的,您想要替换它。您可以通过创建 UserDetails
的可变包装器来实现此目的。 ,扩展您当前的UserDetailsService
并返回该包装器。然后您可以更新主体,YourWrapper principalWrapper =(YourWrapper) securityContext
.getAuthentication().getPrincipal();
principalWrapper.setPrincipal(updated);
关于spring-boot - 从 session 外部更新在线用户数据(无需重新验证),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62554398/