2014-10-28 78 views
5

首先,我想指出我不太瞭解Spring Security,實際上我對它的接口和類知之甚少,但是我沒有那麼簡單的任務要做,不能完全弄明白。我的代碼是基於春季安全論壇的以下帖子(我沒有與帖子所有者相同的問題): http://forum.spring.io/forum/spring-projects/security/747178-security-filter-chain-is-always-calling-authenticationmanager-twice-per-requestSpring-Security:返回狀態401當AuthenticationManager拋出BadCredentialsException

我正在編程一個Spring MVC系統,它將服務HTTP內容,但在爲了這樣做,它有一個preauth檢查(我目前使用RequestHeaderAuthenticationFilter與自定義AuthenticationManager)。

要授權用戶,我將檢查令牌對兩個來源,一個Redis緩存「數據庫」和Oracle。如果在任何這些源中找不到該標記,則我的自定義AuthenticationManager的身份驗證方法會引發BadCredentialsException(我相信這會尊重AuthenticationManager協議)。

現在我想返回HTTP響應401 - 未經授權,但Spring不斷返回500 - 服務器錯誤。是否可以自定義我的設置只返回401而不是500?

下面是相關代碼:

SecurityConfig - 主彈簧安全配置

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

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:

[email protected]: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"} 

回答

12

我看了一下源代碼。這似乎是你可以通過繼承RequestHeaderAuthenticationFilter和壓倒一切的檢測只是失敗後,認證和之前一個新的RuntimeException被拋出這就是所謂的unsuccessfulAuthentication(...)方法很容易做到這一點:

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); 
     } 
    } 

然後只需將你的Filter Config指向這個實例。