当前位置: 首页 > 知识库问答 >
问题:

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

柳珂
2023-03-14

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

从Spring 4.0开始,这非常有效@ControlllerAdvie现在支持注释匹配。我无法解决的是如何为401-UnAuthated和400-Bad Request响应返回JSON结果。

相反,Spring只是将响应返回给容器(tomcat),该容器将其呈现为超文本标记语言。我如何截获它并使用我的@ControlllerAdvie正在使用的相同技术呈现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>

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

我可以通过使用BasicAuthentiationEntryPoint直接写入输出流来解决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."));
    }

}

不过,我还没有弄清楚如何处理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());
    }
}

共有3个答案

寿元白
2023-03-14

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

new ResponseEntity<String>(err, HttpStatus.UNAUTHORIZED);
卜飞鸣
2023-03-14

我通过实现自己版本的HandlerExceptionResolver和子类化DefaultHandlerExceptionResolver解决了这个问题。解决这个问题需要一些时间,您必须覆盖大多数方法,尽管下面的内容正是我想要的。

首先,基础是创建一个实现。

public class CustomHandlerExceptionResolver extends DefaultHandlerExceptionResolver {
    private final ObjectMapper om;
    @Autowired
    public CustomHandlerExceptionResolver(ObjectMapper om) {
        this.om = om;
        setOrder(HIGHEST_PRECEDENCE);
    }
    @Override
    protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
        return request.getServletPath().startsWith("/api");
    }
}

现在在servlet上下文中注册它。

这现在没有什么不同,但将是第一个尝试的HandlerExceptionResolver,并将匹配以上下文路径为api的任何请求(注意:您可以将其设置为可配置参数)。

接下来,我们现在可以重写spring遇到错误的任何方法,我统计有15个。

我发现,如果我的方法最终没有处理导致尝试下一个异常解析程序的错误,我可以直接写入响应并返回空的ModelAndView对象,或者返回null。

作为处理发生请求绑定错误的情况的示例,我执行了以下操作:

@Override
protected ModelAndView handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
    ApiError ae = new ApiError(request.getRequestURI(), ex, HttpServletResponse.SC_BAD_REQUEST);
    response.setStatus(ae.getStatusCode());
    om.writeValue(response.getOutputStream(), ae);
    return new ModelAndView();
}

这里唯一的缺点是,因为我自己在写流,所以我没有使用任何消息转换器来处理写响应,所以如果我想同时支持XML和JSON API,这是不可能的,幸运的是,我只对支持JSON感兴趣,但可以增强这一技术,使用视图解析器来确定在etc中呈现什么。

如果有人有更好的方法,我仍然有兴趣知道如何处理这个问题。

戎元忠
2023-03-14

几周前,我发现自己也在问同样的问题——正如Dirk在评论中指出的那样,@ControllerAdvice只有在从控制器方法中抛出异常时才会生效,因此不可避免地无法捕获所有内容(我实际上是想解决404错误的JSON响应问题)。

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

<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,它将由我的错误控制器处理,如下所示:

@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),那么它就不那么好用了。但是就像我说的,这就是我决定要开始的,希望有更好的方法!

 类似资料:
  • 问题内容: 如何将Webmethod的值以JSON格式返回给客户端? 我要返回两个静态int值。 我是否需要使用这两个属性创建新对象并返回它? GetStatus()方法经常被调用,我不喜欢每次仅用于json格式创建一个特殊对象的想法。 在客户端,我在捕获返回值: 问题答案: 我只想带一个物体。它符合您的需求。如果您有两个返回值,则必须以结构化的方式将它们放在一起。

  • 我使用Guzzle发出一个异步请求,返回JSON。呼叫工作正常,响应正常,但是: 如果我回应- 我需要使用 JSON 来验证来自服务的响应。我该怎么做?我已经浏览了文档,但我显然错过了一些东西。 本质上类似于将json getbody输出分配给$json: 感谢任何帮助问候

  • 本文向大家介绍Springmvc如何返回xml及json格式数据,包括了Springmvc如何返回xml及json格式数据的使用技巧和注意事项,需要的朋友参考一下 问:@ResponseBody注解怎么指定返回xml 还是json 答:@RequestMapping 的produces 属性指定 produces = "application/xml" 或者 produces = "applica

  • 我希望在spring boot中返回类似以下内容的json响应: 我的RestController如下所示 但我得到的反应是这样的

  • 问题内容: 如何使用Jersey / JAX-RS框架以XML / JSON文档形式返回a并不是很明显。它已经支持s了,但是当涉及到s时,就没有了。即使将嵌入到包装器类中,XML模式中也没有类型。 关于如何在Jersey中将地图编组为XML / JSON文档的任何实用建议? 问题答案: 我知道它的回复很晚,但是我希望有一天它能对某人有所帮助:)我应用的最简单,最快的修复方法是 输出:{“ 1”:“

  • 我有一个方法返回。代码如下类所示: 和作为错误。 如何解决此错误