Spring具有两个安全配置-API登录失败将重定向到表单登录页面。如何更改?

时间:2019-11-22 21:12:33

标签: spring-boot spring-security rest-security

我有一个具有两种安全配置(两个WebSecurityConfigurerAdapter)的Spring Boot应用程序,一个用于具有“ /api/**”端点的REST API,另一个用于所有其他端点的Web前端。 。安全配置为here on Github,下面是其中的一些相关部分:

@Configuration
@Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
        jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
        jwtAuthenticationFilter.setPostOnly(true);

        http.antMatcher("/api/**")
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .csrf().disable()
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .addFilter(jwtAuthenticationFilter)
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));
    }
}

@Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                .loginPage("/login").permitAll()
                .defaultSuccessUrl("/")
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/?logout")
                .and()
                .authorizeRequests()
                .mvcMatchers("/").permitAll()
                .mvcMatchers("/home").authenticated()
                .anyRequest().denyAll()
                .and();
    }
}

JWTAuthenticationFilterUsernamePasswordAuthenticationFilter的自定义子类,用于处理对REST API的登录尝试(通过HTTP POST,JSON主体为/api/login),并在其中返回JWT令牌。 “授权”标头(如果成功)。

所以这就是问题所在:登录失败/api/login(具有错误的凭据或缺少JSON正文)正在重定向到HTML登录表单{{ 1}}。对其他“ /api/login”端点的未经身份验证的请求将导致一个简单的JSON响应,例如:

/api/**

未经身份验证的用户尝试访问其他受保护的URL(不以“ { "timestamp": "2019-11-22T21:03:07.892+0000", "status": 403, "error": "Forbidden", "message": "Access Denied", "path": "/api/v1/agency" } { "timestamp": "2019-11-22T21:04:46.663+0000", "status": 404, "error": "Not Found", "message": "No message available", "path": "/api/v1/badlink" } ”开头)重定向到登录表单/api/,这是所需的行为。但是我不希望对/login的API调用重定向到该表单!

如何为失败的API登录编写正确的行为?是否有为该过滤器添加新处理程序的问题?还是在我已经定义的某些行为中添加排除项?

有关异常和处理的更多详细信息:

我查看了日志,由于凭据错误或JSON格式错误而引发的异常是/api/login的子类。日志显示,例如:

org.springframework.security.authentication.AuthenticationException

当我访问另一个URL(例如不存在的URL,例如webapp_1 | 2019-11-25 15:30:16.048 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials (...stack trace...) webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@7f9648b6 webapp_1 | 2019-11-25 15:30:16.133 DEBUG 1 --- [nio-8080-exec-6] n.j.webapps.granite.home.HomeController : Accessing /login page. )时,它看起来非常不同。它非常冗长,但是看起来服务器正在尝试重定向到/api/x,但没有发现它是授权的URL。有趣的是,如果我在Web浏览器中尝试此操作,则会得到使用自定义错误页面(error.html)格式化的错误,但是如果我使用Postman进行访问,则只会收到JSON消息。日志样本:

/error

因此,看起来我可能需要做的是将REST API的“身份验证失败处理程序”配置为转到“ /错误”,而不是转到“ /登录”,但仅针对{{1 }}。

1 个答案:

答案 0 :(得分:0)

我在验证过滤器中添加了unsuccessfulAuthentication()的@Override

祖父母类(AbstractAuthenticationProcessingFilter)具有用于身份验证失败的方法(即AuthenticationException),该方法委派给身份验证失败处理程序类。我本可以创建自己的自定义身份验证失败处理程序,但决定使用一些代码简单地覆盖unsuccessfulAuthentication方法,该代码发送回带有401状态和JSON错误消息的响应:

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
    // TODO: enrich/improve error messages
    response.setStatus(response.SC_UNAUTHORIZED);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
    response.getWriter().write("{\"error\": \"authentication error?\"}");
}

...并添加了一个自定义AuthenticationEntryPoint以使其他错误匹配

它没有我在其他端点上看到的错误消息的确切形式,因此我还创建了一个自定义AuthenticationEntryPoint(用于处理对受保护端点的未授权请求的类),并且它的作用基本相同事情。我的实现:

public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        // TODO: enrich/improve error messages
        response.setStatus(response.SC_UNAUTHORIZED);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        response.getWriter().write("{\"error\": \"unauthorized?\"}");
    }
}

现在,REST端点的安全配置如下所示(请注意添加了“ .exceptionHandling().authenticationEntryPoint()”:

@Configuration
@Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
        jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");

        http.antMatcher("/api/**")
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .csrf().disable()
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .exceptionHandling()
                    .authenticationEntryPoint(new RESTAuthenticationEntryPoint())
                    .and()
                .addFilter(jwtAuthenticationFilter)
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));
    }
}

我需要处理我的错误消息,以使它们更有用和更安全。  这并不能完全回答我最初的问题,即如何获取我喜欢的默认样式的JSON响应,但是确实允许我自定义各种访问失败类型的错误,而与网络配置。 (/api/**以外的端点仍然可以使用Web表单登录。)

截至当前提交的完整代码:github