【问题标题】:ExceptionHandler for a pre-controller Filter (Spring Security)用于预控制器过滤器的 ExceptionHandler (Spring Security)
【发布时间】:2017-09-23 18:14:49
【问题描述】:

我对 Spring 安全级别的异常有疑问。

我使用 SM_USER 标头进行授权,并通过 DelegateRequestMatchingFilter 验证请求(这有助于了解是否需要 SM_USER)。

问题是,如果没有 SM_USER-header 在所有行 super.doFilter(...) 抛出一个 PreAuthenticatedCredentialsNotFoundException 不能用控制器的标准 ExceptionResolver 处理,这就是为什么看起来很奇怪,并且与抛出的所有其他异常不同应用程序。

我尝试为过滤器编写一个标有@ExceptionResolver 注释的自己的方法,但它被忽略了。

如何为此过滤器插入 ExceptionsResolver?

DelegateRequestMatchingFilter sn-p

public class DelegateRequestMatchingFilter extends RequestHeaderAuthenticationFilter {

private RequestMatcher ignoredRequests;
public DelegateRequestMatchingFilter(RequestMatcher matcher) {
    super();
    super.setPrincipalRequestHeader("SM_USER");
    this.ignoredRequests = matcher;
}   

@Override
public void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {

     HttpServletRequest httpReq = (HttpServletRequest) req;      
     if(ignoredRequests.matches(httpReq)) {
         chain.doFilter(req,resp);
     } else {
         super.doFilter(req,resp,chain);//<-- throws exception
     }       
}   
}

来自 ExceptionResolver 的片段不能解决问题,因为它仅适用于控制器

 @ControllerAdvice
public class ExceptionResolver extends AbstractHandlerExceptionResolver{


@Override
protected ModelAndView doResolveException(HttpServletRequest request,
        HttpServletResponse responce, Object handler, Exception exception) {

    ModelAndView toReturn = new ModelAndView();
    toReturn.setView(new MappingJackson2JsonView());
    toReturn.addObject("message", exception.getMessage());
    toReturn.addObject("exceptionClass", exception.getClass().getCanonicalName());
    return toReturn;
}

@ExceptionHandler(PreAuthenticatedCredentialsNotFoundException.class)       
public ResponseEntity<Result> handleBindException(PreAuthenticatedCredentialsNotFoundException e) {

    Result result = new Result("the identification header is missing");
    result.setException(PreAuthenticatedCredentialsNotFoundException.class);
    ResponseEntity<Result> response = new ResponseEntity<Result>(result, HttpStatus.FORBIDDEN);
    return response;
}
}

安全配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

private UserDetailsService userDetailsService;  
private PreAuthenticatedAuthenticationProvider preAuthenticatedProvider;



@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

    Map<String, List<GrantedAuthority>> rolesAuthorities = getRolesWithAutorities();

    userDetailsService = new CustomUserDetailsService(...);
    UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper = 
            new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService);
    preAuthenticatedProvider = new PreAuthenticatedAuthenticationProvider();
    preAuthenticatedProvider.setPreAuthenticatedUserDetailsService(wrapper);
    auth.authenticationProvider(preAuthenticatedProvider);
}   

@Bean
public SmUserFailureHandler smUserFailureHandler(){
    return new SmUserFailureHandler();
}

@Bean
public AccessDeniedHandler accessDeniedHandler(){
    AccessDeniedHandlerImpl handler = new AccessDeniedHandlerImpl();
    handler.setErrorPage("/errorPage/");        
    return handler;     
}

public RequestHeaderAuthenticationFilter siteMinderFilter() throws Exception
{

    RequestHeaderAuthenticationFilter siteMinderFilter = new DelegateRequestMatchingFilter(
            new OrRequestMatcher(
            new AntPathRequestMatcher("/uriToSkip/")));

    siteMinderFilter.setPrincipalRequestHeader("SM_USER");
    siteMinderFilter.setAuthenticationManager(authenticationManager());   
    return siteMinderFilter;
}

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

 //...   
 http.addFilter(siteMinderFilter());
 ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
  //adding methods... 
  http = registry.and();

  http.csrf().disable();
  http.headers().cacheControl().disable();
  http
  .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.NEVER);    
}
}

如果我根据答案更改我的代码,即使 SM_USEr 标头存在并且是正确的,我也会收到此异常

org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:177)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:133)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:168)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)

【问题讨论】:

  • 可能是过滤器位于弹簧层之上,这就是它不起作用的原因
  • @VinitSolanki,是的,也许吧。问题是我怎样才能让它发挥作用。

标签: java spring-mvc spring-security exception-handling


【解决方案1】:

首先,您应该在ExceptionTranslationFilter 之后添加您的过滤器,此过滤器将在捕获它们时处理AccessDeniedExceptionAuthenticationException

http.addFilterAfter(siteMinderFilter(), ExceptionTranslationFilter.class);

接下来你需要注入一个AuthenticationEntryPoint来处理AuthenticationException,有一些已经存在的AuthenticationEntryPoint你可以使用它,比如HttpStatusEntryPoint, LoginUrlAuthenticationEntryPoint, ...;

如果要返回 JSON 响应,则需要实现自己的 AuthenticationEntryPoint,例如:

public class ExampleAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(ExampleAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        logger.error(authException.getMessage(), authException);
        if (!response.isCommitted()) {
            Map<String, Object> responseMsg = new HashMap<String, Object>();
            responseMsg.put("message", authException.getMessage());
            ObjectMapper mapper = new ObjectMapper();
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter writer = response.getWriter();
                writer.write(mapper.writeValueAsString(responseMsg));
                writer.flush();
                writer.close();
            } catch (IOException e) {
                // ignore
                logger.error(e.getMessage(), e);
            }
        }
    }
}

我认为返回http状态码401而不是403比较好,403用于AccessDeniedException

然后将自己的AuthenticationEntryPoint注入ExceptionTranslationFilter

http.exceptionHandling().authenticationEntryPoint(myEntryPoint());

@Bean
AuthenticationEntryPoint myEntryPoint() {
    return new ExampleAuthenticationEntryPoint();
}

/*
Or just return 401
AuthenticationEntryPoint myEntryPoint() {
    return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
} 
*/

顺便说一句:

如果在RequestHeaderAuthenticationFilter中将continueFilterChainOnUnsuccessfulAuthentication设置为true,则超级过滤器不会抛出异常,其默认值为true,可能需要设置为false,或者需要设置在DelegateRequestMatchingFilter中注入一个AuthenticationFailureHandler来处理认证时的异常。

** 更新 **

else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                logger.debug(
                        "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
                        exception);

                sendStartAuthentication(
                        request,
                        response,
                        chain,
                        new InsufficientAuthenticationException(
                                "Full authentication is required to access this resource"));
            }
            else {
                logger.debug(
                        "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                        exception);

                accessDeniedHandler.handle(request, response,
                        (AccessDeniedException) exception);
            }
        }

从代码中可以看出,现在是AccessDeniedException,我认为您可以禁用匿名用户,或者您需要检查为什么会收到AccessDeniedException

http.anonymous().disable();

更新 2

Authentication currentUser = SecurityContextHolder.getContext()
            .getAuthentication();

    if (currentUser == null) {
        return true;
    }

    if (!checkForPrincipalChanges) {
        return false;
    }

    if(!principalChanged(request, currentUser)) {
        return false;
    }

checkForPrincipalChanges 的默认值是false,所以现在如果启用anon 过滤器,这将返回 false 并跳过在RequestHeaderAuthenticationFilter 中进行身份验证,所以请尝试禁用anon 过滤器。并且需要学习spring安全代码,并通过调试研究。

【讨论】:

  • 没有。这是行不通的。他为 exery 请求抛出异常。
  • 你能发布更多信息吗?异常堆栈?
  • 是的。如果我使用正确的 SM_USER 发出请求,它会引发异常:InsufficientAuthenticationException。我会将文本添加到 questuin 正文中
  • 所以我认为问题是:spring security 在任何情况下都会抛出这个异常,它被我的 entryPoint 捕获。
  • 我想你不明白我的意思:我在每个请求中都用我的 entraPoint 捕获了 InsufficientAuthenticationException(它是 AccessDeniedException 的一个子项):即使在正确的 SM_USER 的情况下也是如此。它不会开始调用 CustomUserDetailsS​​ervice 来检查用户是否真的存在 - 只是抛出一个异常。如果我关闭 entryPoint 和 ExceptionTranslationFilter 关闭的行,我不会收到任何异常
猜你喜欢
  • 2015-04-25
  • 1970-01-01
  • 2011-10-29
  • 1970-01-01
  • 1970-01-01
  • 2012-09-28
  • 1970-01-01
  • 2019-04-13
  • 2013-04-13
相关资源
最近更新 更多