spring - Spring Security UsernamePasswordAuthenticationFilter:登录失败后如何访问请求
问题描述
我正在使用 Angular 7 和 Spring Boot 实现登录页面,并且正在处理登录失败的问题。基本上我想在 X 登录尝试失败后锁定特定时间的登录。
HttpSecurity 配置
@Override
protected void configure(HttpSecurity http) throws Exception {
logger.info("#### Configuring Security ###");
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/rest/users/authenticate");//this override the default relative url for login: /login
http
.httpBasic().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/rest/", "/rest/helloworld/**").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint()).and()
.addFilter(jwtAuthenticationFilter);
为了处理登录,我创建了一个过滤器
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static Logger logger = Logger.getLogger(JWTAuthenticationFilter.class);
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserDto credentials = new ObjectMapper().readValue((request.getInputStream()), UserDto.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUserName(),
credentials.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//sucessfull authentication stuff
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
logger.info("Authentication failed");
ErrorMessage errorMessage = new ErrorMessage("access_denied", "Wrong email or password.");
String jsonObject = JSONUtil.toJson(errorMessage);
//processing authentication failed attempt
UserDto credentials = new ObjectMapper().readValue((request.getInputStream()), UserDto.class);
AuthenticationService authenticationService = Application.getApplicationContext().getBean(AuthenticationService.class);
int numFailedAttemptLogin = authenticationService.authenticationFailedAttempt(credentials.getUserName());
response.setStatus(403);
PrintWriter out = response.getWriter();
out.print(jsonObject);
out.flush();
out.close();
//super.unsuccessfulAuthentication(request, response, failed);
}
}
登录工作正常,没有问题。我的问题是unsuccessfulAuthentication方法。当用户输入错误的凭据时,会引发 BadCredentials 异常并调用unsuccessfulAuthentication方法。在这里,我需要再次访问请求表单以提取用户名并处理身份验证失败尝试,我收到以下异常
java.io.IOException: Stream closed
这是因为在attemptAuthentication方法中,请求输入流被读取并且显然是关闭的。
如何访问unsuccessfulAuthentication中的请求正文信息?
我尝试了 SecurityContextHolder.getContext().getAuthentication() 但由于身份验证失败,它为空。
有人有什么主意吗?
此致
解决方案
在遵循M.Deinum 的建议后,我能够创建一个监听特定异常的组件:
@Component
public class AuthenticationEventListener implements ApplicationListener<ApplicationEvent> {
private static Logger logger = Logger.getLogger(AuthenticationEventListener.class);
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
logger.info(String.format("Event types: %s", applicationEvent.getClass()));
if (applicationEvent instanceof AbstractAuthenticationFailureEvent) {
String username = ((AbstractAuthenticationFailureEvent) applicationEvent).getAuthentication().getName();
if (applicationEvent instanceof AuthenticationFailureBadCredentialsEvent) {
logger.info(String.format("User %s failed to login", username));
//this.handleFailureEvent(username, event.getTimestamp());
}
}
}
}
这种方法使用异常来驱动在特定场景中执行的操作。我能够像这样继续使用我的JWTAuthenticationFilter来实现类似的东西
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserDto credentials = new ObjectMapper().readValue((request.getInputStream()), UserDto.class);
try {
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUserName(),
credentials.getPassword(),
new ArrayList<>())
);
} catch (BadCredentialsException bce) {
try {
handleBadCredentials(credentials, response);
throw bce;
} catch (LockedException le) {
handleUserLocked(credentials, response);
throw le;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
logger.info("Authentication failed");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.getWriter().print(authException.getLocalizedMessage());
response.getWriter().flush();
}
谢谢大家的时间和帮助,非常感谢。
推荐阅读
- python - 将文本附加到 xml 标记值
- angular - 可选依赖项
- python - 返回的输出未进入 xlsxwriter
- node.js - 如何通过 AD FS 在 React 中进行身份验证?
- java - 尽管服务器没有运行,socket() 没有抛出异常
- java - 如何在java上显示所有对象
- python - Python:合并两个模型以创建具有两个分类数据的新模型
- java - Jasper 报告空白页
- java - 如何使用 NavigableIndex 从 cqengine IndexedCollection 获取第一项或最后一项
- angular - Angular 6 材料 - 如何从 matDatepicker 获取日期和时间?