The default data format returned by the authentication server is as follows:
{ "error": "unsupported_grant_type", "error_description": "Unsupported grant type: password1" }
The above return result is very unfriendly, and the front-end code is also difficult to determine what the error is, so we need to handle the returned error uniformly.
1. Default exception handler
The default is to use the implementation class of the WebResponseExceptionTranslator interface DefaultWebResponseExceptionTranslator to handle the thrown exception; therefore, you can use WebResponseExceptionTranslator
Interface to start, the implementation of the interface method to deal with exceptions.
2. Define the exception class that inherits OAuth2Exception
package com.yaomy.security.oauth2.exception; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; /** * @Description: Exception handling class * @ProjectName: spring-parent * @Package: com.yaomy.security.oauth2.exception.UserOAuth2Exception * @Date: 2019/7/17 15:29 * @Version: 1.0 */ @JsonSerialize(using = UserOAuth2ExceptionSerializer.class) public class UserOAuth2Exception extends OAuth2Exception { private Integer status = 400; public UserOAuth2Exception(String message, Throwable t) { super(message, t); status = ((OAuth2Exception)t).getHttpErrorCode(); } public UserOAuth2Exception(String message) { super(message); } @Override public int getHttpErrorCode() { return status; } }
3. Define serialization implementation class
package com.yaomy.security.oauth2.exception; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import org.springframework.web.util.HtmlUtils; import java.io.IOException; import java.util.Map; /** * @Description: Serialization exception class * @ProjectName: spring-parent * @Package: com.yaomy.security.oauth2.exception.BootOAuthExceptionJacksonSerializer * @Date: 2019/7/17 15:32 * @Version: 1.0 */ public class UserOAuth2ExceptionSerializer extends StdSerializer<UserOAuth2Exception> { protected UserOAuth2ExceptionSerializer() { super(UserOAuth2Exception.class); } @Override public void serialize(UserOAuth2Exception e, JsonGenerator generator, SerializerProvider serializerProvider) throws IOException { generator.writeStartObject(); generator.writeObjectField("status", e.getHttpErrorCode()); String message = e.getMessage(); if (message != null) { message = HtmlUtils.htmlEscape(message); } generator.writeStringField("message", message); if (e.getAdditionalInformation()!=null) { for (Map.Entry<String, String> entry : e.getAdditionalInformation().entrySet()) { String key = entry.getKey(); String add = entry.getValue(); generator.writeStringField(key, add); } } generator.writeEndObject(); } }
4. Custom implementation exception conversion class
package com.yaomy.security.oauth2.exception; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer; import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.security.web.util.ThrowableAnalyzer; import org.springframework.stereotype.Component; import org.springframework.web.HttpRequestMethodNotSupportedException; import java.io.IOException; /** * @Description: Resource server exception custom capture * @ProjectName: spring-parent * @Package: com.yaomy.security.oauth2.exception.OAuth2ServerWebResponseExceptionTranslator * @Date: 2019/7/17 14:49 * @Version: 1.0 */ @Component public class UserOAuth2WebResponseExceptionTranslator implements WebResponseExceptionTranslator { private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); @Override public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception { Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(e); Exception ase = (OAuth2Exception)this.throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain); //OAuth2Exception exception in exception chain if (ase != null) { return this.handleOAuth2Exception((OAuth2Exception)ase); } //Authentication related exception ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase != null) { return this.handleOAuth2Exception(new UserOAuth2WebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e)); } //Exception chain contains access denied exception ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); if (ase instanceof AccessDeniedException) { return this.handleOAuth2Exception(new UserOAuth2WebResponseExceptionTranslator.ForbiddenException(ase.getMessage(), ase)); } //Exception chain contains Http method request exception ase = (HttpRequestMethodNotSupportedException)this.throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain); if(ase instanceof HttpRequestMethodNotSupportedException){ return this.handleOAuth2Exception(new UserOAuth2WebResponseExceptionTranslator.MethodNotAllowed(ase.getMessage(), ase)); } return this.handleOAuth2Exception(new UserOAuth2WebResponseExceptionTranslator.ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e)); } private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException { int status = e.getHttpErrorCode(); HttpHeaders headers = new HttpHeaders(); headers.set("Cache-Control", "no-store"); headers.set("Pragma", "no-cache"); if (status == HttpStatus.UNAUTHORIZED.value() || e instanceof InsufficientScopeException) { headers.set("WWW-Authenticate", String.format("%s %s", "Bearer", e.getSummary())); } UserOAuth2Exception exception = new UserOAuth2Exception(e.getMessage(),e); ResponseEntity<OAuth2Exception> response = new ResponseEntity(exception, headers, HttpStatus.valueOf(status)); return response; } private static class MethodNotAllowed extends OAuth2Exception { public MethodNotAllowed(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "method_not_allowed"; } @Override public int getHttpErrorCode() { return 405; } } private static class UnauthorizedException extends OAuth2Exception { public UnauthorizedException(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "unauthorized"; } @Override public int getHttpErrorCode() { return 401; } } private static class ServerErrorException extends OAuth2Exception { public ServerErrorException(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "server_error"; } @Override public int getHttpErrorCode() { return 500; } } private static class ForbiddenException extends OAuth2Exception { public ForbiddenException(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "access_denied"; } @Override public int getHttpErrorCode() { return 403; } } }
5. Add the custom exception handling class to the authentication server configuration
package com.yaomy.security.oauth2.config; import com.yaomy.security.oauth2.enhancer.UserTokenEnhancer; import com.yaomy.security.oauth2.handler.UserAccessDeniedHandler; import com.yaomy.security.oauth2.handler.UserAuthenticationEntryPoint; import com.yaomy.security.oauth2.po.AuthUserDetailsService; import com.yaomy.security.oauth2.service.OAuth2ClientDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; /** * @Description: @EnableAuthorizationServer Annotation enable OAuth2 authorization service mechanism * @ProjectName: spring-parent * @Package: com.yaomy.security.oauth2.config.OAuth2ServerConfig * @Date: 2019/7/9 11:26 * @Version: 1.0 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private WebResponseExceptionTranslator webResponseExceptionTranslator; /** The access endpoint and token services used to configure authorization and token */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { ... endpoints.exceptionTranslator(webResponseExceptionTranslator); ... } ... }
GitHub source code: https://github.com/mingyang66/spring-parent/tree/master/spring-security-oauth2-server-redis-service