【问题标题】:ErrorHandling in Spring Security for @PreAuthorize AccessDeniedException returns 500 rather then 401@PreAuthorize AccessDeniedException 的 Spring Security 中的 ErrorHandling 返回 500 而不是 401
【发布时间】:2017-09-12 08:17:11
【问题描述】:

亲爱的,我喜欢在 Spring 4 Rest 应用程序中自定义错误处理,因此它应该返回 HTTP 代码 401 而不是服务器错误 500,以防在我的控制器中检查 @PreAuthorize 注释失败。

我有一个自己的 AuthenticationEntryPoint 和 AuthenticationFailureHandler 注册,它的开始/错误处理方法返回 401。这对我的 JWT 身份验证工作正常,但如果 @PreAuthorize 检查失败,使用“AccessDeniedException”这些错误方法永远不会被调用并弹出返回服务器错误 500。

如何自定义?看起来我错过了什么?感谢您提前提供任何提示。

这里是我的 AuthenticationEntryPoint 类:

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException, ServletException {       
    response.setContentType("application/json");
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.getOutputStream().println("{ \"error\": \"" + authException.getMessage() + "\" }");
   }
}

这里是我的 AuthenticationFailureHandler:

public class JWTAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    response.sendError(401, (new StringBuilder()).append("Authentication Failed: ").append(exception.getMessage()).toString());
    }
}

这里是我的 spring 安全配置

<security:global-method-security pre-post-annotations="enabled"/>

<security:http  pattern="/api/**" entry-point-ref="restAuthenticationEntryPoint"  >
    <security:csrf disabled="true"/>
    <security:intercept-url pattern="/api/**" access="isAuthenticated()" />
    <security:custom-filter ref="authenticationTokenProcessingFilter" before="FORM_LOGIN_FILTER"   />
</security:http>

<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider ref="jwtAuthenticationProvider" />
</security:authentication-manager>

<bean id="restAuthenticationEntryPoint" class="ch.megloff.common.webservice.jwt.RestAuthenticationEntryPoint"/>

<bean id="authenticationTokenProcessingFilter" class="ch.megloff.common.webservice.jwt.JWTAuthenticationFilter">
    <constructor-arg type="java.lang.String"><value>/api/**</value></constructor-arg>
    <property name="authenticationManager" ref="authenticationManager"></property>
    <property name="authenticationFailureHandler" ref="jwtAuthenticationFailureHandler" />
</bean>

<bean id="jwtAuthenticationProvider" class="ch.megloff.common.webservice.jwt.JWTAuthenticationProvider" />
<bean id="jwtAuthenticationFailureHandler" class="ch.megloff.common.webservice.jwt.JWTAuthenticationFailureHandler" />

这是我的休息控制器类

@RestController
public class UserController {

   @Autowired
   private UserService userService;

   @PreAuthorize("hasAuthority('admin')")
   @RequestMapping(value = "/api/user/", method = RequestMethod.GET)
   public ResponseEntity<List<User>> listAllUsers(HttpServletRequest request,    HttpServletResponse response) {
    System.out.println("Fetching all Users");
    List<User> users = userService.getUsers();
    if(users.isEmpty()){
        return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);
    }
    return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}

@PreAuthorize 在注解中检查失败时来自服务器的堆栈跟踪:

SEVERE: Servlet.service() for servlet [myusers] in context with path    [/JWTAuthenticationExample] threw exception [Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied] with root cause
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at ch.megloff.myusers.UserController$$EnhancerBySpringCGLIB$$635caa86.listAllUsers(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:220)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:720)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:466)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:154)
at ch.megloff.common.webservice.jwt.JWTAuthenticationSuccessHandler.onAuthenticationSuccess(JWTAuthenticationSuccessHandler.java:23)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:326)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:240)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)`

【问题讨论】:

    标签: java spring rest spring-mvc spring-security


    【解决方案1】:

    基于另一篇文章的讨论(参见here),我得出结论,@PreAuthorize 和身份验证的错误处理使用不同的概念。唯一的方法是在 Spring 3.2 中使用带有 @ControllerAdvice 和 @ExceptionHandler 注释的 generic error handler 的新概念。因此,您可以重用错误处理程序类。

    例子

    @Component
    @ControllerAdvice
    public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
      @Override
      public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException, ServletException {    
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getOutputStream().println("{ \"error\": \"" + authException.getMessage() + "\" }");
      }
    
      @ExceptionHandler(value = { AccessDeniedException.class })
      public void commence(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex ) throws IOException {
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getOutputStream().println("{ \"error\": \"" + ex.getMessage() + "\" }");
      }
    }
    

    【讨论】:

    • 您也可以通过在响应中设置错误来做到这一点,例如 'response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());'
    • 2018年问题仍未解决
    • 即使在 2020 年也没有修复
    猜你喜欢
    • 2019-09-18
    • 2021-07-28
    • 1970-01-01
    • 2021-07-24
    • 2018-04-26
    • 2016-10-16
    • 2015-08-19
    • 2021-12-03
    • 2021-01-28
    相关资源
    最近更新 更多