java - 从 Spring Security 中的另一个控制器手动调用 failureHandler?
问题描述
inb4:总的来说,我对 Spring 很陌生,所以我知道处理 SpringSecurity 是一个陡峭的过程。
tl;dr:我可以从我的自定义 SuccessHandler “抛出异常”到我的 FailureHandler 吗?
目前我正在尝试通过我们的登录页面实现一些行为。如果凭据错误,用户将被重定向回登录,并显示消息错误。如果使用的凭据是正确的,但另一个人已经在使用该帐户,则用户将被重定向到 Login 并显示一条消息错误(“最大会话错误”)。
到目前为止,在会话/并发控制中使用带有 Springs 的 ExceptionMappingAuthenticationFailureHandler 工作正常,显示正确的错误消息,但会话控制没有按我预期的那样工作。这可能是我自己的错误配置,或者只是我并不真正理解文档(英语不是我的母语,所以我完全接受这种可能性)。我希望 SessionControl 的行为是,如果与给定用户的会话处于活动状态,则试图进入的人将被踢回登录。目前它会这样做,但是如果具有会话活动的用户注销并尝试重新登录,它将踢他/她以 MaxSessionsError 登录。AFAIU 这是预期的,因为 SessionManagement.MaximumSessions() 限制了帐户的登录次数,不是用户可以同时拥有的活动会话数。我可能错了,但我仍在调查。我试图设置我的配置正如文档所说,但它没有像我预期的那样工作。
我当前的替代方法是在我确认另一个人当前正在使用帐户时尝试在 SuccessHandler 上抛出异常,以便 FailureHandler 可以将用户重定向到带有正确错误消息的登录页面。我的理解是,如果我重定向到登录页面,会话不会被破坏。我可以尝试自己摧毁它,但由于我缺乏一般经验,我真的更相信 Spring 能够做到这一点,因此我提出了问题。
欢迎任何帮助或耳光。我想学习,到目前为止我在安全方面有点疏忽。下面是 config 和 successHandler 的代码。小提示:由于某种我不知道的原因,这个项目没有使用 web.xml,所以我尝试将大多数配置转换为@Bean
符号
配置
/*Other beans and configs*/
@Bean
ExceptionMappingAuthenticationFailureHandler exceptionMappingAuthenticationFailureHandler(){
ExceptionMappingAuthenticationFailureHandler ex = new ExceptionMappingAuthenticationFailureHandler();
String RUTAERROR1 = "/login.zul?login_error=1";
String RUTAERROR2 = "/login.zul?login_error=2";
Map<String, String> errores = new HashMap<>();
errores.put(org.springframework.security.authentication.ProviderNotFoundException.class.getCanonicalName(), RUTAERROR1);
errores.put(SessionAuthenticationException.class.getCanonicalName(), RUTAERROR2);
ex.setExceptionMappings(errores);
return ex;
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/zkau/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/services/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/forgotpassword.zul").permitAll()
.antMatchers("/login.zul**").permitAll()
.antMatchers("/**").authenticated()
.and()
.formLogin()
.loginProcessingUrl("/j_spring_security_check")
.successHandler(customSuccessHandler())
.loginPage("/login.zul")
.permitAll()
// .defaultSuccessUrl("/index.zul")
// .defaultSuccessUrl("/menuAgrupadores.zul").
.failureHandler(exceptionMappingAuthenticationFailureHandler())
// .failureUrl("/login.zul?login_error=1")
.and()
.logout()
// .logoutSuccessUrl("/login.zul")
.logoutUrl("/j_spring_security_logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutSuccessHandler(logoutSuccessHandler())
.and().csrf().disable();
http.headers()
.frameOptions().sameOrigin()
.httpStrictTransportSecurity().disable();
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
}
成功处理程序
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private UsuariosRepository usuariosRepository;
private RedirectStrategy redirect = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
Usuarios user = usuariosRepository.findByUsuario(auth.getName());
if(user.getToken() != null) {
throw new SessionLimitException("Max sessions limit reached!", new ProviderNotFoundException("Máximo de sesiones excedido.")); //This is what I'm trying to do
} else {
user.setToken(request.getSession().getId());
usuariosRepository.save(user);
redirect.sendRedirect(request, response, "/menuAgrupadores.zul");
}
}
SessionLimitException 只是扩展了 AuthenticationException,没有进一步的逻辑。
编辑
正如@Eleftheria Stein-Kousathana 所说,我错过了监听器,但我必须将它添加到 web.xml。就文档而言,这有点明显,但是我的应用程序一开始就没有这个文件,并且所有其他 bean/config 都正常运行。换句话说,HttpSessionEventPublisher 的@Bean 表示法在我的实例中不起作用。如果您在这里遇到同样的问题,我强烈建议您尝试@Bean 和 XML 方式来注册此侦听器。如果你不知道,web.xml 应该放在 WEB-INF 中。
工作代码:
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/zkau/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/services/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/forgotpassword.zul").permitAll()
.antMatchers("/login.zul**").permitAll()
.antMatchers("/**").authenticated()
.and()
.formLogin()
.loginProcessingUrl("/j_spring_security_check")
.loginPage("/login.zul")
.permitAll()
.defaultSuccessUrl("/menuAgrupadores.zul")
.failureHandler(exceptionMappingAuthenticationFailureHandler())
.and()
.logout()
.logoutUrl("/j_spring_security_logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutSuccessUrl("/login.zul")
.and()
.csrf()
.disable();
http.headers()
.frameOptions()
.sameOrigin()
.httpStrictTransportSecurity()
.disable();
http.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry()); //Added this for good measure
HttpSessionEventPublisher 的 xml 表示法
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
解决方案
我建议坚持第一种方法。
你唯一缺少的是一个HttpSessionEventPublisher
.
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
添加后,它将解决注销问题,并允许新用户登录。
如果没有这个,HttpSessionDestroyedEvent
则不会在注销时发布到应用程序上下文,并且不会清除过期的会话。
这就是不允许新用户登录的原因。
请注意HttpSecurity.sessionManagement
Javadoc中的说明:
在使用 SessionManagementConfigurer.maximumSessions(int) 时,不要忘记为应用程序配置 HttpSessionEventPublisher 以确保清理过期的会话。
推荐阅读
- python - 层 Python 数字总和
- linux - 比较两个 yocto 内核的最佳方法是什么?
- python - 使用 comtypes.client.CreateObject() 后释放安装程序对象
- xml - 组合 xpath 表达式
- typescript - 使用我的 Typescript 组件时,故事书中不支持“const”枚举
- ruby-on-rails - Rails 在符合多个条件的连接中查找所有项目
- python - 重新排列 Pandas 数据框
- java - 在 Java Springboot 中为 mySQL 自定义生成的具有持久序列的列值
- algorithm - 如何在块算法中显示“if operator in for operator”?
- core-audio - PCM中的音频帧样本类型?