Java API return 格式正确 JSON 表示成功和失败,包括身份验证错误

Java APIs return well formatted JSON for success and failure including authentication errors

不是开发 API 方面的专家,因此需要来自社区的一些 help/advice。 我有一个使用 Java Spring MVC for APIs 的应用程序。我正在使用 ResponseEntity return 回复任何使用 APIs(第三方或 UI)的人。 我的例子 API

@Controller
@RequestMapping("/Test/")
public ResponseEntity<TestGroup> getTestsById(@PathVariable("id") Integer id) {
    TestGroup testGroup = testService.getTestById(id);  //calls a service that returns test from the db
    if(testGroup != null) {
        return new ResponseEntity(testGroup, HttpStatus.OK);
    } else {
        return new ResponseEntity(HttpStatus.NOT_FOUND);
    }
}

我还有其他 API 与此类似。我的问题是是否有任何框架或方法可以使我的 APIs return JSON 以格式良好的 JSON 响应错误或成功。例子

{
  "code": 400,
  "message": "Bad Request",
  "description": "There were no credentials found."
}

{
  "code": 200,
  "data": "{<JSON blob of the object to be returned>}"
}

此外, 在映射任何路由之前,已经实现了一个过滤器来检查会话信息(将它们视为 oauth 令牌)以进行身份​​验证。我也需要这个错误处理过程在那个阶段工作,所以如果有过期的令牌或无效的令牌,我会得到一个格式正确的 JSON。例子

{
  "code": 401,
  "message": "Unauthorized",
  "description": "The token used in the request is incorrect or has expired."
}

现在我收到默认消息 Spring 的身份验证过滤器以 HTML

的形式抛出
<html><head><title>Apache Tomcat/7.0.50 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 500 - Access is denied</h1><HR size="1" noshade="noshade"><p><b>type</b> Exception report</p><p><b>message</b> <u>Access is denied</u></p><p><b>description</b> <u>The server encountered an internal error that prevented it from fulfilling this request.</u></p><p><b>exception</b> <pre>org.springframework.security.access.AccessDeniedException: Access is denied
org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83)
org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
com.test.security.CookieAuthenticationFilter.doFilter(CookieAuthenticationFilter.java:95)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
</pre></p><p><b>note</b> <u>The full stack trace of the root cause is available in the Apache Tomcat/7.0.50 logs.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.50</h3></body></html>

抱歉没有传达这一点,我没有为 API 使用 spring 安全性,我的 web.xml 过滤器看起来像这样

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

<display-name>Webapp</display-name>

<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.test.spring.config</param-value>
</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.test.spring.config=</param-value>
    </init-param>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

下面是安全配置的处理方式。

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

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

    @Autowired
    private AbstractConfiguration configurationManager;

    public SecurityConfiguration() {
        super(true);
    }

    @Override
    @Bean
    protected AuthenticationManager authenticationManager() {
        return new CustomAuthenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/health/**").permitAll().and().authorizeRequests()
                .antMatchers("/complete/**").permitAll().and()
                .addFilterBefore(cookieAuthenticationFilter(), ChannelProcessingFilter.class).authorizeRequests()
                .antMatchers("/**").hasAuthority("tests");
    }

    @Bean
    public GenericFilterBean cookieAuthenticationFilter() {
        if (System.getProperty("noauth") != null) {
            return new SecurityFilterMock();
        } else {
            return new CookieAuthenticationFilter(redisTemplate());
        }
    }
}

根据建议,如果您阅读了下文,我已经制作了以下入口点 class 来处理令牌身份验证并显示 JSON

public class TokenAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private static final Logger log = LoggerFactory.getLogger(TokenAuthenticationEntryPoint.class);

@Override
public void commence(HttpServletRequest request,HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    log.info(String.format("Unauthorized access with session id '%s'",
            request.getSession().getId()));

    ErrorHandler errorResponse = new ErrorHandler();
    // populate your response object with all the info you need
    errorResponse.setCode(401);
    errorResponse.setDescription("The token used in the request is incorrect or invalid.");
    errorResponse.setMessage("Unauthorized");

    ObjectMapper jsonMapper = new ObjectMapper();

    response.setContentType("application/json;charset=UTF-8");
    response.setStatus(HttpStatus.UNAUTHORIZED.value()); 
    PrintWriter out = response.getWriter();
    out.print(jsonMapper.writeValueAsString(errorResponse));
}
}

如有任何建议或指示,我们将不胜感激

return JSON 格式错误与 Spring 身份验证你可以在这里看看我的回答:How to return JSON response for unauthorized AJAX calls instead of login page as AJAX response?

基本上,您必须调整身份验证机制并重新定义入口点以正确处理响应。