当前位置: 首页 > 知识库问答 >
问题:

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

宦正诚
2023-03-14

我有一个Spring Boot应用程序,它有两个安全配置(两个WebSecurityConfigureAdapter),一个用于带有“/API/**”endpoint的REST API,另一个用于所有其他endpoint的web前端。安全配置在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();
    }
}

JWTAuthenticationFilter是UsernamePasswordAuthenticationFilter的一个自定义子类,它处理对REST API的登录尝试(通过HTTP POST,将JSON正文发送到API/login),如果成功,则在“Authorization”标头中返回JWT令牌。

问题是:失败的登录尝试(凭据错误或缺少JSON正文)会重定向到HTML登录表单。对其他“/api/***”endpoint的未经身份验证的请求会导致一个简单的JSON响应,例如:

{
    "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"
}

未经身份验证的用户尝试访问其他受保护的URL(不是以“/api/”开头)时,会重定向到登录表单,这是所需的行为。但我不想让对API/login的API调用重定向到该表单!

如何为失败的API登录编写正确的行为代码?是否存在为该筛选器添加新处理程序的问题?或者在我已经定义的行为中添加排除?

我查看了日志,对于错误凭据或格式错误的JSON引发的异常是org的一个子类。springframework。安全身份验证。身份验证异常(AuthenticationException)。日志显示,例如:

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.

当我访问另一个URL时,例如一个不存在的URL,例如/api/x,它看起来非常不同。它非常冗长,但看起来服务器正试图重定向到/error,并且没有发现它是授权的URL。有趣的是,如果我在Web浏览器中尝试此操作,我会将错误格式化为我的自定义错误页面(error.html),但如果我使用Postman访问它,我只会收到一条JSON消息。日志示例:

webapp_1  | 2019-11-25 16:07:22.157 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point
webapp_1  |
webapp_1  | org.springframework.security.access.AccessDeniedException: Access is denied
...
webapp_1  | 2019-11-25 16:07:22.174 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter     : Calling Authentication entry point.
webapp_1  | 2019-11-25 16:07:22.175 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.Http403ForbiddenEntryPoint     : Pre-authenticated entry point called. Rejecting access
...
webapp_1  | 2019-11-25 16:07:22.211 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/api/**'
...
webapp_1  | 2019-11-25 16:07:22.214 DEBUG 1 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy        : /error reached end of additional filter chain; proceeding with original chain
webapp_1  | 2019-11-25 16:07:22.226 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
webapp_1  | 2019-11-25 16:07:22.230 DEBUG 1 --- [nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
webapp_1  | 2019-11-25 16:07:22.564 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
webapp_1  | 2019-11-25 16:07:22.577 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{timestamp=Mon Nov 25 16:07:22 GMT 2019, status=403, error=Forbidden, message=Access Denied, path=/a (truncated)...]
webapp_1  | 2019-11-25 16:07:22.903 DEBUG 1 --- [nio-8080-exec-6] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
webapp_1  | 2019-11-25 16:07:22.905 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 403

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

共有1个答案

慕容兴贤
2023-03-14

祖辈类(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?\"}");
}

这与我在其他endpoint上看到的错误消息的形式不同,因此我还创建了一个自定义的AuthenticationEntryPoint(处理对受保护endpoint的未授权请求的类),它的作用基本相同。我的实施:

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?\"}");
    }
}

现在RESTendpoint的安全配置如下所示(注意添加了“.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响应,但它确实允许我独立于web配置自定义所有类型的访问失败导致的错误。(api/**之外的endpoint仍然可以使用web表单登录。)

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

 类似资料:
  • 我正在使用带有java配置和两个HttpSecurity配置的spring-security 3.2.0.rc2。一个用于REST API,一个用于UI。当我发布到/logout时,它重定向到/login?logout,但随后(错误地)重定向到/login。当我成功地输入用户名和密码时,我会被重定向到登录-注销,并且必须再次输入凭据才能进入主页面。因此,似乎login的permitAll不被用于l

  • 我正在尝试为任何未经身份验证且当前具有以下配置的请求配置基于Java的Spring Security重定向到登录页面: 在实现 的类中,我有以下内容: 在 方法中设置断点会显示该方法在启动时被调用,但没有请求重定向到 。

  • 记录器文件中的日志- org.springframework.Security.Access.event.loggerlistener-安全授权失败,原因是:org.springframework.Security.Access.accessdeniedexception:访问被拒绝;通过身份验证的主体:org.springframework.security.authentication.ano

  • 我使用的是Spring Security 4.1.1,但我遇到了一个问题:我试图访问URL,应用程序会重定向到登录页面。到现在为止,一直都还不错。 但是,成功登录后,应用程序会再次将我重定向到登录页面,并且不会创建任何会话,因此即使尝试直接访问URL(在URL栏中键入),应用程序也会重定向到登录页面。 有一些URL我必须要求登录才能访问它们。其他的,我可以访问无需身份验证。这些我不需要验证的URL

  • 我正在尝试将Spring Security性与spring boot restful API集成。我的项目代码如下: web安全配置包括 你能帮我清理这个案子吗?

  • 我现在一直在努力使用我的登录页面来让组件呈现Loggedin组件。我的前端是Reactjs,后端是NodeJS。我对nodejs、expression和react都是新手。 在loginform组件上,我使用fetch进行了一次post,它将用户名和密码传递给后端的相应endpoint。没问题。在后端,它读取我存储用户(不使用任何数据库)的jsonfile来查找匹配项,如果用户名和密码都匹配,则它