如何让Spring-Security以401格式返回401响应? [英] How can I get Spring-Security to return a 401 response as a JSON format?

查看:2754
本文介绍了如何让Spring-Security以401格式返回401响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a ReST API to an application with all controllers found under /api/, these all return JSON responses with a @ControllerAdvice which handles all exceptions to map to JSON formatted results.

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

This works great as of spring 4.0 @ControllerAdvice now supports matching on annotations. What I can't work out is how to return a JSON result for a 401 - Unauthenticated and 400 - Bad Request responses.

而不是Spring只是将响应返回给容器(tomcat),将其呈现为HTML。我如何使用与我的 @ControllerAdvice 使用的技术相同的方法拦截此并呈现JSON结果。

Instead Spring is simply returning the response to the container (tomcat) which renders this as HTML. How can I intercept this and render a JSON result using the same technique that my @ControllerAdvice is using.

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>

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

我可以使用<$ c $解决401 c> BasicAuthenticationEntryPoint 直接写入输出流,但我不确定这是最好的方法。

I can solve 401 by using the BasicAuthenticationEntryPoint to write directly to the output stream, but I'm not sure it's the best approach.

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

}

我还没弄明白但是,为了处理400,我曾经尝试过捕获所有工作的控制器,但似乎有时它会与其他控制器产生奇怪的冲突行为,我不想重新访问。

I am yet to figure out how to handle 400 though, I once tried a catch all controller which did work, but it seemed that sometimes it would have odd conflicting behaviour with other controllers that I don't want to revisit.

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

My ControllerAdvice implementation has a catch all which if spring throws any exception for bad request (400) it should in theory capture it, but it does not:

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


推荐答案

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

I actually found myself asking the very same question a few weeks ago - As Dirk pointed out in the comments, @ControllerAdvice will only kick in if an exception is thrown from within a controller method, so will inevitably not catch all things (I was actually trying to solve the case for a JSON response for a 404 error).

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

The solution I settled on, although not entirely pleasant (hopefully if you get a better answer I will change my approach) is handling the error mapping in the Web.xml - I added the following, which will override the tomcat default error pages with specific URL mappings:

<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>

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

Now, if any page returns a 404, it is handled by my error controller something like this:

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

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

It all still feels pretty grim - and of course is a global handling of errors - so if you didn't always want a 404 response to be json (if you were serving a normal webapp of the same application) then it doesnt work so well. But like I said, its what I settled on to get moving, here's hoping there is a nicer way!

这篇关于如何让Spring-Security以401格式返回401响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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