如何让Spring-Security以401格式返回401响应?

Bre*_*yan 25 java spring-mvc spring-security

我有一个ReST API到一个应用程序,其中找到了所有控制器/api/,这些都返回JSON响应,@ControllerAdvice其中处理所有异常以映射到JSON格式的结果.

这在春季4.0 @ControllerAdvice现在支持匹配注释,效果很好.我无法解决的是如何返回401的JSON结果 - 未经验证和400 - 错误请求响应.

相反,Spring只是将响应返回给容器(tomcat),将其呈现为HTML.我如何拦截它并使用我@ControllerAdvice正在使用的相同技术呈现JSON结果.

security.xml文件

<bean id="xBasicAuthenticationEntryPoint"
      class="com.example.security.XBasicAuthenticationEntryPoint">
  <property name="realmName" value="com.example.unite"/>
</bean>
<security:http pattern="/api/**"
               create-session="never"
               use-expressions="true">
  <security:http-basic entry-point-ref="xBasicAuthenticationEntryPoint"/>
  <security:session-management />
  <security:intercept-url pattern="/api/**" access="isAuthenticated()"/>
</security:http>
Run Code Online (Sandbox Code Playgroud)

XBasicAuthenticationEntryPoint

public class XBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException)
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                               authException.getMessage());
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以通过使用BasicAuthenticationEntryPoint直接写入输出流来解决401 ,但我不确定这是最好的方法.

public class XBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    private final ObjectMapper om;

    @Autowired
    public XBasicAuthenticationEntryPoint(ObjectMapper om) {
        this.om = om;
    }

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException)
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        om.writeValue(httpResponse.getOutputStream(),
                      new ApiError(request.getRequestURI(),
                      HttpStatus.SC_UNAUTHORIZED,
                      "You must sign in or provide basic authentication."));
    }

}
Run Code Online (Sandbox Code Playgroud)

我还没弄明白如何处理400,我曾经试过捕捉所有控制器做了工作,但似乎有时它会与其他控制器有奇怪的冲突行为,我不想重新审视.

我的ControllerAdvice实现有一个捕获所有如果spring抛出任何异常的错误请求(400)它理论上应该捕获它,但它不会:

@ControllerAdvice(annotations = {RestController.class})
public class ApiControllerAdvisor {
    @ExceptionHandler(Throwable.class)
    public ResponseEntity<ApiError> exception(Throwable exception,
                                              WebRequest request,
                                              HttpServletRequest req) {
        ApiError err = new ApiError(req.getRequestURI(), exception);
        return new ResponseEntity<>(err, err.getStatus());
    }
}
Run Code Online (Sandbox Code Playgroud)

rhi*_*nds 17

几周前我发现自己问了同样的问题 - 正如Dirk在评论中指出的那样,@ ControllerAdvice只会在控制器方法中抛出异常时启动,所以不可避免地会抓住所有东西(我实际上是试图解决404错误的JSON响应的情况).

我确定的解决方案,虽然不是很愉快(希望如果你得到一个更好的答案,我将改变我的方法)是在Web.xml中处理错误映射 - 我添加了以下内容,它将覆盖特定于tomcat的默认错误页面URL映射:

<error-page>
    <error-code>404</error-code>
    <location>/errors/resourcenotfound</location>
</error-page>
<error-page>
    <error-code>403</error-code>
    <location>/errors/unauthorised</location>
</error-page>
<error-page>
    <error-code>401</error-code>
    <location>/errors/unauthorised</location>
</error-page>
Run Code Online (Sandbox Code Playgroud)

现在,如果任何页面返回404,它由我的错误控制器处理,如下所示:

@Controller
@RequestMapping("/errors")
public class ApplicationExceptionHandler {


    @ResponseStatus(HttpStatus.NOT_FOUND)
    @RequestMapping("resourcenotfound")
    @ResponseBody
    public JsonResponse resourceNotFound(HttpServletRequest request, Device device) throws Exception {
        return new JsonResponse("ERROR", 404, "Resource Not Found");
    }

    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @RequestMapping("unauthorised")
    @ResponseBody
    public JsonResponse unAuthorised(HttpServletRequest request, Device device) throws Exception {
        return new JsonResponse("ERROR", 401, "Unauthorised Request");
    }
}
Run Code Online (Sandbox Code Playgroud)

这一切仍然感觉非常严峻 - 当然是全局处理错误 - 所以如果你不总是希望404响应是json(如果你正在服务同一个应用程序的正常webapp)那么它不能很好地工作.但就像我说的那样,我决定采取行动,这里希望有一个更好的方式!


Shi*_*mar 2

您应该注意在 ResponseEntity 中设置错误代码,如下所示:

new ResponseEntity<String>(err, HttpStatus.UNAUTHORIZED);
Run Code Online (Sandbox Code Playgroud)