英文:
Spring security: delete cookies from user when I delete user
问题
我正在使用Spring Security和JSESSION cookie,因此每个用户在使用凭据登录后都会获得该cookie:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
  return http.build();
}
我创建了一个UserService来创建、检索、更新和删除用户,以及一个UserDetailsServiceImpl(它使用UserService)来实现UserDetailsService:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
  @Autowired
  private UserService userService;
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userService.findByUsername(username);
  }
}
因此,用户可以被删除。也许删除命令是由其他用户执行的,而不是由他自己执行,所以我没有会话上下文。
我想在删除用户时使与用户相关的所有cookie无效,这样他就不能再使用旧的cookie登录应用程序。
有什么方法吗?
提前感谢。
英文:
I'm using Spring security with JSESSION cookie so every user gets that cookie after its login with credentials:
  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
    return http.build();
  }
I created a UserService to create, retrieve, update and delete users and UserDetailsServiceImpl (which uses UserService) to implement UserDetailsService:
  @Service
  public class UserDetailsServiceImpl implements UserDetailsService {
  @Autowired
  private UserService userService;
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userService.findByUsername(username);
  }
}
So users can be deleted. Maybe the delete command is executed by other user rather than him so I don't have the session context.
I want to invalidate all cookies related to a user when I delete him so he cannot enter the app again using his old cookies.
Is there any way?
Thanks in advance.
答案1
得分: 2
你可以实现org.springframework.security.core.session.SessionRegistry或扩展默认实现org.springframework.security.core.session.SessionRegistryImpl以使用getAllSessions获取关联用户主体的所有会话,并根据需要使其失效。
要使会话注册表能够收到会话生命周期事件的通知,您需要在Servlet上下文中注册org.springframework.security.web.session.HttpSessionEventPublisher,示例如下:
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
    ServletListenerRegistrationBean<HttpSessionEventPublisher> listenerRegBean =  new ServletListenerRegistrationBean<>();
    listenerRegBean.setListener(new HttpSessionEventPublisher());
    return listenerRegBean;
}
@Bean
public SessionRegistry sessionRegistry(){
    return new SessionRegistryImpl();
}
英文:
You can implement org.springframework.security.core.session.SessionRegistry or extend the default implementation org.springframework.security.core.session.SessionRegistryImpl to use getAllSessions for the associated user principal and invalidate the appropriate ones as per your requirement.
For your session registry to be notified of the session life-cycle events, you need to register org.springframework.security.web.session.HttpSessionEventPublisher in Servlet context as below sample:
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
ServletListenerRegistrationBean<HttpSessionEventPublisher> listenerRegBean =  new ServletListenerRegistrationBean<>();
           
listenerRegBean.setListener(new HttpSessionEventPublisher());
           return listenerRegBean;
}
    
    @Bean
    public SessionRegistry sessionRegistry(){
     return new SessionRegistryImpl();
    }
答案2
得分: 1
I got it working like so:
- 基于gs-securing-web
 - 获取一些测试用户/修改:
@Bean InMemoryUserDetailsManager userDetailsService() { UserDetails alice = User.withUsername("alice") .password("{noop}alice") .roles("ADMIN") .build(); UserDetails bob = User.withUsername("bob") .password("{noop}bob") .roles("USER") .build(); return new InMemoryUserDetailsManager(alice, bob); } - 向
hello.html添加(基本的管理员)控制:<form sec:authorize="hasRole('ROLE_ADMIN')" th:action="@{/invalidate}" method="post"> <input type="text" name="user" /> <input type="submit" value="Delete User" /> </form> - (基本的)"invalidate/delete user" 控制器:
@Controller public class AdminController { @Autowired InMemoryUserDetailsManager userService; @Autowired SessionRegistry sessionRegistry; // ! @PostMapping("/invalidate") public String invalidate(@RequestParam String user) { // 省略(任何)验证以保持简洁... // (可选)加载要使无效的用户: User bob = (User) userService.loadUserByUsername(user); // 所有“principals”: sessionRegistry.getAllPrincipals() .stream() // 过滤正确的用户名: .filter(p -> p instanceof UserDetails prncp && prncp.getUsername().equals(bob.getUsername())) // 使所有与principal相关的会话“过期”: .forEach(p -> sessionRegistry.getAllSessions(p, false) .forEach(SessionInformation::expireNow) // ! ); // (可选)自定义用户删除/修改.. // userService.updateUser(...); // (示例)自定义响应 return "redirect:/hello"; } } - 要将所有内容连接在一起,我们(只)需要:
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(...) ... .sessionManagement(sm -> sm // 1. .maximumSessions(2) // <- 示例 .sessionRegistry(sessionRegistry()) // 2. .expiredSessionStrategy(expireStrategy())) // 3. .build(); } @Bean // org.springframework.security.core.session.*: SessionRegistry sessionRegistry() { // 2a. return new SessionRegistryImpl(); } @Bean //org.springframework.security.web.session.*: SessionInformationExpiredStrategy expireStrategy() { // 3a. // "/" 引用(自定义的会话使无效)重定向URL return new SimpleRedirectSessionInformationExpiredStrategy("/"); } 
测试时间:
- 
Bob 使用以下浏览器登录:
- Chrome(匿名)
 - 和 Opera 浏览器
 
.. 以显示
/hello - 
Alice 使用 Chrome 登录,并提交 "invalidate form" 中的
bob - 
下次 bob 尝试请求任何(未安全/已安全)资源(来自任何浏览器)时,(Spring Security 默认链)
ConcurrentSessionFilter介入:- 检测到我们设置的会话过期
 - 执行注销(处理程序)
 - 并触发 
SessionInformationExpiredEvent 
 - 
(.. 直到服务器重新启动/用户服务被重置)
 
对于负载平衡/分布式(服务器)环境:
我们可以确保“粘性会话”或
.. 作为更强大的解决方案,实现自定义的 SessionRegistry。
- 对于分发/负载平衡(会话必须是粘性的或)SessionRegistry 必须了解所有节点/实例/主体/会话
 - 为了(更)强大,我们需要从有效的、活动/持久的会话中预填充我们的 SessionRegistry。(我曾经在“devtools-reload”之后经历到它们为空,而底层(容器)会话仍然存在。)
 
英文:
I got it working like so
- Basing on gs-securing-web
 - Get us some test users/modifying:
@Bean InMemoryUserDetailsManager userDetailsService() { UserDetails alice = User.withUsername("alice") .password("{noop}alice") .roles("ADMIN") .build(); UserDetails bob = User.withUsername("bob") .password("{noop}bob") .roles("USER") .build(); return new InMemoryUserDetailsManager(alice, bob); } - Adding a (rudimentary admin) control to 
hello.html:<form sec:authorize="hasRole('ROLE_ADMIN')" th:action="@{/invalidate}" method="post"> <input type="text" name="user" /> <input type="submit" value="Delete User" /> </form> - The (rudimentary) "invalidate/delete user" controller:
@Controller public class AdminController { @Autowired InMemoryUserDetailsManager userService; @Autowired SessionRegistry sessionRegistry; // ! @PostMapping("/invalidate") public String invalidate(@RequestParam String user) { // omitted (any) validation for brevity... // (optional) load user to invalidate: User bob = (User) userService.loadUserByUsername(user); // all "principals": sessionRegistry.getAllPrincipals() .stream() // filter correct user name: .filter(p -> p instanceof UserDetails prncp && prncp.getUsername().equals(bob.getUsername())) // "expire" all sessions for principal: .forEach(p -> sessionRegistry.getAllSessions(p, false) .forEach(SessionInformation::expireNow) // ! ); // (optional) custom user deletion/modification.. // userService.updateUser(...); // (sample) custom response return "redirect:/hello"; } } - To wire it all up, we (only) need:
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(...) ... .sessionManagement(sm -> sm // 1. .maximumSessions(2) // <- example .sessionRegistry(sessionRegistry()) // 2. .expiredSessionStrategy(expireStrategy())) // 3. .build(); } @Bean // org.springframework.security.core.session.*: SessionRegistry sessionRegistry() { // 2a. return new SessionRegistryImpl(); } @Bean //org.springframework.security.web.session.*: SessionInformationExpiredStrategy expireStrategy() { // 3a. // "/" refers to (custom, session invalidation) redirect url return new SimpleRedirectSessionInformationExpiredStrategy("/"); } 
Testing Time
- 
Bob logs in with:
- Chrome (anonymous)
 - and Opera browser
 
.. to display
/hello - 
Alice logs in (with chrome), and submits
bobwith "invalidate form" - 
Next time, when bob tries to request any (un-/secured) resource (from any browser), (spring security default chain)
ConcurrentSessionFilterjumps in:- detects session expiry (set by us)
 - performs logout (handler)
 - and "fires" a 
SessionInformationExpiredEvent 
 - 
(..until server restarts/user service is reset)
 
For a load balanced/distributed (server) environment
We can ensure "sticky sessions" or
..as for a more resilient solution, implement custom SessionRegistry.
- For distribution/load balancing the (sessions must be sticky or) SessionRegistry must be aware of all nodes/instances/principals/sessions
 - for (more) resilience, we need to pre-populate our SessionRegistry from valid, active/persistent Sessions. (I experienced them empty after a "devtools-reload", while the underlying (container) sessions still persisted.)
 
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论