Spring-Security:返回状态401当AuthenticationManager抛出BadCredentialsException时 [英] Spring-Security: Return Status 401 When AuthenticationManager Throws BadCredentialsException

查看:4481
本文介绍了Spring-Security:返回状态401当AuthenticationManager抛出BadCredentialsException时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我想指出我不太了解Spring Security,实际上我对它的接口和类知之甚少,但我的任务并不那么简单做,并不能完全弄明白。我的代码基于Spring安全论坛的以下帖子(我没有与帖子所有者相同的问题):
http://forum.spring.io/forum/spring-projects/security / 747178-security-filter-chain-is-always-calling-authenticationmanager-每次请求两次

First of all, I'd like to point that I don't know Spring Security very much, actually I know quite little about its interfaces and classes, but I got a not so simple task to do and can't quite figure it out. My code is based in the following post in the Spring Security Forum (I'm not having the same problem as the post owner): http://forum.spring.io/forum/spring-projects/security/747178-security-filter-chain-is-always-calling-authenticationmanager-twice-per-request

我正在编写Spring MVC系统将提供HTTP内容但是,为了做到这一点,它有一个preauth检查(我目前正在使用 RequestHeaderAuthenticationFilter ,带有自定义 Aut henticationManager )。

I'm programming a Spring MVC system which will serve HTTP content but, in order to do so, it has a preauth check (which I'm currently using RequestHeaderAuthenticationFilter with a custom AuthenticationManager).

为了授权用户,我将针对两个来源检查令牌,即Redis缓存数据库和Oracle。如果在任何这些源中找不到令牌,我的自定义AuthenticationManager的authenticate方法会抛出BadCredentialsException(我认为它遵循AuthenticationManager合约)。

To authorize the user, I'll check the token against two sources, a Redis cache "database" and Oracle. If the token is not found in any of those sources, the authenticate method of my custom AuthenticationManager throws a BadCredentialsException (which I believe honours the AuthenticationManager contract).

现在我' d喜欢在HTTP响应401中返回 - 未经授权,但Spring不断返回500 - 服务器错误。是否可以自定义我的设置以仅返回401而不是500?

Now I'd like to return in the HTTP response 401 - Unauthorized, but Spring keeps returning 500 - Server Error. Is it possible to customize my setup to return only 401 not 500?

以下是相关代码:

SecurityConfig - main spring security config

SecurityConfig - main spring security config

package br.com.oiinternet.imoi.web.config;

import javax.validation.constraints.NotNull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;

@Configuration
@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(SecurityConfig.class);

    public static final String X_AUTH_TOKEN = "X-Auth-Token";

    private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

    @Bean
    public AuthenticationManager authenticationManager() {
        return new TokenBasedAuthenticationManager();
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new Http403ForbiddenEntryPoint();
    }

    @Bean
    public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(
            final AuthenticationManager authenticationManager) {
        RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        filter.setExceptionIfHeaderMissing(false);
        filter.setPrincipalRequestHeader(X_AUTH_TOKEN);
        filter.setInvalidateSessionOnPrincipalChange(true);
        filter.setCheckForPrincipalChanges(true);
        filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
        return filter;
    }

    /**
     * Configures the HTTP filter chain depending on configuration settings.
     *
     * Note that this exception is thrown in spring security headerAuthenticationFilter chain and will not be logged as
     * error. Instead the ExceptionTranslationFilter will handle it and clear the security context. Enabling DEBUG
     * logging for 'org.springframework.security' will help understanding headerAuthenticationFilter chain
     */
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = fromContext(http,
                RequestHeaderAuthenticationFilter.class);

        AuthenticationEntryPoint authenticationEntryPoint = fromContext(http, AuthenticationEntryPoint.class);

        http.authorizeRequests()
            .antMatchers(HttpMethod.GET, "/auth").permitAll()
            .antMatchers(HttpMethod.GET, "/**").authenticated()
            .antMatchers(HttpMethod.POST, "/**").authenticated()
            .antMatchers(HttpMethod.HEAD, "/**").authenticated()
        .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().securityContext()
        .and().exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
        .and()
            .addFilterBefore(requestHeaderAuthenticationFilter, LogoutFilter.class);
    }

    private <T> T fromContext(@NotNull final HttpSecurity http, @NotNull final Class<T> requiredType) {
        @SuppressWarnings("SuspiciousMethodCalls")
        ApplicationContext ctx = (ApplicationContext) http.getSharedObjects().get(ApplicationContext.class);
        return ctx.getBean(requiredType);
    }
}

TokenBasedAuthenticationManager - 我的自定义AuthenticationManager

TokenBasedAuthenticationManager - my custom AuthenticationManager

package br.com.oiinternet.imoi.web.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

import br.com.oi.oicommons.lang.message.Messages;
import br.com.oiinternet.imoi.service.AuthService;
import br.com.oiinternet.imoi.web.security.auth.AuthenticationAuthorizationToken;

public class TokenBasedAuthenticationManager implements AuthenticationManager {

    @Autowired
    private AuthService authService;

    @Autowired
    private Messages messages;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        final String token = (String) authentication.getPrincipal();

        if (authService.isAuthorized(token) || authService.authenticate(token)) {
            return new AuthenticationAuthorizationToken(token);
        } 
            throw new BadCredentialsException(messages.getMessage("access.bad.credentials"));
    }

}

使用请求/响应周期的示例curl:

Example of request/response cycle using curl:

user@user-note:curl --header "X-Auth-Token: 2592cd35124dc3d79bdd82407220a6ea7fad9b8b313a1205cf1824a5ce726aa8dd763cde8c05faadae48b47252de95b0" http://localhost:8081/test/auth -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /test/auth HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8081
> Accept: */*
> X-Auth-Token: 2592cd35124dc3d79bdd82407220a6ea7fad9b8b313a1205cf1824a5ce726aa8dd763cde8c05faadae48b47252de95b0
> 
< HTTP/1.1 500 Server Error
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Pragma: no-cache
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Connection: close
* Server Jetty(9.1.0.v20131115) is not blacklisted
< Server: Jetty(9.1.0.v20131115)
< 
* Closing connection 0
{"timestamp":1414513379405,"status":500,"error":"Internal Server Error","exception":"org.springframework.security.authentication.BadCredentialsException","message":"access.bad.credentials","path":"/test/auth"}


推荐答案

我看了一眼来源。通过继承RequestHeaderAuthenticationFilter并覆盖在检测到失败的身份验证之后刚刚抛出新的RuntimeException之前调用的unsuccessfulAuthentication(...)方法,您似乎可以相当轻松地实现此目的:

I have had a look at the source. It would seem that you could achieve this fairly easily by subclassing RequestHeaderAuthenticationFilter and overriding the unsuccessfulAuthentication(...) method which is called just after a failed authentication is detected and just before a new RuntimeException is thrown:

  public class MyRequestHeaderAuthenticationFilter extends RequestHeaderAuthenticationFilter {

        @Override
        protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException failed) {
            super.unsuccessfulAuthentication(request, response, failed);

            // see comments in Servlet API around using sendError as an alternative
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }

然后只需将您的过滤器配置指向此实例。

Then just point your Filter Config to an instance of this.

这篇关于Spring-Security:返回状态401当AuthenticationManager抛出BadCredentialsException时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆