英文:
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
bob
with "invalidate form" -
Next time, when bob tries to request any (un-/secured) resource (from any browser), (spring security default chain)
ConcurrentSessionFilter
jumps 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.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论