Spring Security Oauth - OAuth2Exceptions 的自定义格式

标签 spring spring-security spring-security-oauth2

spring security oauth 的错误格式符合 OAuth 规范,看起来像这样。

{
  "error":"insufficient_scope",
  "error_description":"Insufficient scope for this resource",
  "scope":"do.something"
}

特别是在资源服务器上,我发现为身份验证问题获取不同的错误格式有点奇怪。所以我想改变这个异常的呈现方式。

documentation

Error handling in an Authorization Server uses standard Spring MVC features, namely @ExceptionHandler methods



所以我尝试了这样的事情来自定义错误的格式:
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyErrorHandler {

    @ExceptionHandler(value = {InsufficientScopeException.class})
    ResponseEntity<MyErrorRepresentation> handle(RuntimeException ex, HttpServletRequest request) {
        return errorResponse(HttpStatus.FORBIDDEN,
                MyErrorRepresentation.builder()
                        .errorId("insufficient.scope")
                        .build(),
                request);
    }
}

但这不起作用。

看代码,所有的错误渲染似乎都是在DefaultWebResponseExceptionTranslator#handleOAuth2Exception中完成的.但是实现自定义 WebResponseExceptionTranslator不允许更改格式。

任何提示?

最佳答案

首先,了解一些 Spring Security OAuth2 的知识。

  • OAuth2 有两个主要部分

  • AuthorizationServer : /oauth/token, get token

    ResourceServer : url resource priviledge management


  • Spring Security 将过滤器添加到服务器容器的过滤器链中,因此 Spring Security 的异常不会到达@ControllerAdvice

  • 然后,自定义 OAuth2Exceptions 应该考虑 AuthorizationServer 和 ResourceServer。
    这是配置
    @Configuration
    @EnableAuthorizationServer
    public class OAuthSecurityConfig extends AuthorizationServerConfigurerAdapter {
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //for custom
            endpoints.exceptionTranslator(new MyWebResponseExceptionTranslator());
        }
    }
    
    @Configuration
    @EnableResourceServer
    public  class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) {
                // format message
                resources.authenticationEntryPoint(new MyAuthenticationEntryPoint());
                resources.accessDeniedHandler(new MyAccessDeniedHandler());
            }
    }
    
    MyWebResponseExceptionTranslator 将异常转换为 ourOAuthException,我们通过 jackson 自定义 ourOAuthException 序列化程序,默认情况下 OAuth2 使用的方式相同。
    @JsonSerialize(using = OAuth2ExceptionJackson1Serializer.class)
    public class OAuth2Exception extends RuntimeException {
    
    其他自定义句柄类的东西
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
    import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
    
    /**
     * @author qianggetaba
     * @date 2019/6/21
     */
    public class MyWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
        @Override
        public ResponseEntity<OAuth2Exception> translate(Exception exception) throws Exception {
            if (exception instanceof OAuth2Exception) {
                OAuth2Exception oAuth2Exception = (OAuth2Exception) exception;
                return ResponseEntity
                        .status(oAuth2Exception.getHttpErrorCode())
                        .body(new CustomOauthException(oAuth2Exception.getMessage()));
            }else if(exception instanceof AuthenticationException){
                AuthenticationException authenticationException = (AuthenticationException) exception;
                return ResponseEntity
                        .status(HttpStatus.UNAUTHORIZED)
                        .body(new CustomOauthException(authenticationException.getMessage()));
            }
            return ResponseEntity
                    .status(HttpStatus.OK)
                    .body(new CustomOauthException(exception.getMessage()));
        }
    }
    
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
    
    /**
     * @author qianggetaba
     * @date 2019/6/21
     */
    @JsonSerialize(using = CustomOauthExceptionSerializer.class)
    public class CustomOauthException extends OAuth2Exception {
        public CustomOauthException(String msg) {
            super(msg);
        }
    }
    
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.ser.std.StdSerializer;
    
    import java.io.IOException;
    import java.util.Arrays;
    import java.util.Map;
    
    /**
     * @author qianggetaba
     * @date 2019/6/21
     */
    public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
    
        public CustomOauthExceptionSerializer() {
            super(CustomOauthException.class);
        }
    
        @Override
        public void serialize(CustomOauthException value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeStartObject();
            jsonGenerator.writeNumberField("code4444", value.getHttpErrorCode());
            jsonGenerator.writeBooleanField("status", false);
            jsonGenerator.writeObjectField("data", null);
            jsonGenerator.writeObjectField("errors", Arrays.asList(value.getOAuth2ErrorCode(),value.getMessage()));
            if (value.getAdditionalInformation()!=null) {
                for (Map.Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
                    String key = entry.getKey();
                    String add = entry.getValue();
                    jsonGenerator.writeStringField(key, add);
                }
            }
            jsonGenerator.writeEndObject();
        }
    }
    
    用于自定义 ResourceServer 异常
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author qianggetaba
     * @date 2019/6/21
     */
    public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                             AuthenticationException authException)
                throws ServletException {
    
            Map map = new HashMap();
            map.put("errorentry", "401");
            map.put("message", authException.getMessage());
            map.put("path", request.getServletPath());
            map.put("timestamp", String.valueOf(new Date().getTime()));
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            try {
                ObjectMapper mapper = new ObjectMapper();
                mapper.writeValue(response.getOutputStream(), map);
            } catch (Exception e) {
                throw new ServletException();
            }
        }
    
    }
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author qianggetaba
     * @date 2019/6/21
     */
    public class MyAccessDeniedHandler implements AccessDeniedHandler{
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.setContentType("application/json;charset=UTF-8");
            Map map = new HashMap();
            map.put("errorauth", "400");
            map.put("message", accessDeniedException.getMessage());
            map.put("path", request.getServletPath());
            map.put("timestamp", String.valueOf(new Date().getTime()));
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            try {
                ObjectMapper mapper = new ObjectMapper();
                mapper.writeValue(response.getOutputStream(), map);
            } catch (Exception e) {
                throw new ServletException();
            }
        }
    }
    

    关于Spring Security Oauth - OAuth2Exceptions 的自定义格式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45006285/

    相关文章:

    spring - Spring @Async取消并开始吗?

    java - Spring boot,Vaadin - 无法在 View 中 Autowiring 服务

    java - Spring 批处理 : migrating 1 to n relationship where n is potentially huge

    session - GRAILS:如何通过Spring Security核心插件获取当前登录用户的数量?

    java - Spring Security + MVC : same @RequestMapping, 不同@Secured

    java - Spring Security 3.1.3 @Autowired 在使用 WebApplicationInitializer 时不起作用

    java - XML 架构引用

    spring-boot - 将 OAuth2 登录附加到 Spring Boot 应用程序中的现有用户

    spring security oauth2 + 切换用户过滤器

    java - Spring Security OAuth2 与版本 2.0.+ 中的自定义 TokenGranter