当前位置:网站首页>Oauth2.0 custom response values and exception handling
Oauth2.0 custom response values and exception handling
2022-08-02 16:02:00 【zhangyu,】
Self-use response message body
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.Accessors;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.nio.charset.Charset;
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ApiModel(value = "响应信息主体")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
private static final String SUCCESS = "SUCCESS";
private static final String FAILED = "FAILED";
@Getter
@Setter
@ApiModelProperty(value = "返回标记:成功标记=200,失败标记=500")
private int code;
@Getter
@Setter
@ApiModelProperty(value = "返回信息")
private String msg;
@Getter
@Setter
@ApiModelProperty(value = "数据")
private T data;
public static <T> R<T> ok() {
return restResult(null, HttpStatus.HTTP_OK, SUCCESS, true);
}
public static <T> R<T> ok(T data) {
return restResult(data, HttpStatus.HTTP_OK, SUCCESS, true);
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, HttpStatus.HTTP_OK, msg, true);
}
public static <T> R<T> ok(ResultCode result) {
return restResult(null, result.code, result.msg, true);
}
public static <T> R<T> fail() {
return restResult(null, HttpStatus.HTTP_INTERNAL_ERROR, FAILED, false);
}
public static <T> R<T> fail(String msg) {
return restResult(null, HttpStatus.HTTP_INTERNAL_ERROR, msg, false);
}
public static <T> R<T> fail(T data) {
return restResult(data, HttpStatus.HTTP_INTERNAL_ERROR, FAILED, false);
}
public static <T> R<T> fail(ResultCode result) {
return restResult(null, result.code, result.msg, false);
}
public static <T> R<T> fail(int code, String msg) {
return restResult(null, code, msg, false);
}
private static <T> R<T> restResult(T data, int code, String msg, boolean success) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
public static void failRender(int code, String msg, HttpServletResponse response, int status) {
try {
ObjectMapper mapper = new ObjectMapper();
response.setContentType(ContentType.build(ContentType.JSON.getValue(), Charset.defaultCharset()));
response.setStatus(status);
response.getWriter().write(mapper.writeValueAsString(R.fail(code, msg)));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void failRender(ResultCode resultCode, HttpServletResponse response, int status) {
try {
ObjectMapper mapper = new ObjectMapper();
response.setContentType(ContentType.build(ContentType.JSON.getValue(), Charset.defaultCharset()));
response.setStatus(status);
response.getWriter().write(mapper.writeValueAsString(R.fail(resultCode)));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void cast(ResultCode resultCode) {
throw new BusinessException(resultCode);
}
public static void cast(int code, String msg) {
throw new BusinessException(code, msg);
}
}
Customize the request without exception /oauth/token 获取 token 的响应格式
@Slf4j
@Aspect
@Component
public class CustomOAuthTokenAspect {
@Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public ResponseEntity response(ProceedingJoinPoint point) throws Throwable {
Object proceed = point.proceed();
ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>) proceed;
return ResponseEntity.ok(R.ok(responseEntity.getBody()));
}
}
处理 grant_type、username、password Bad exception response
- 默认情况是使用 WebResponseExceptionTranslator接口的实现类 DefaultWebResponseExceptionTranslator对抛出的异常进行处理
- The processing method in this paper is through realizationWebResponseExceptionTranslatorinterface to get started,To achieve the processing of abnormal information
- Remember to add it to the authentication server core configuration after implementation AuthorizationServerConfig Endpoint configuration for (AuthorizationServerEndpointsConfigurer.exceptionTranslator) 中,See below for writing,不急
import cn.mowen.common.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.CacheControl;
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.OAuth2AccessToken;
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;
@Slf4j
@Component
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
@Override
public ResponseEntity translate(Exception e) throws Exception {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(e);
Exception ase = (OAuth2Exception) this.throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
//异常链中有OAuth2Exception异常
if (ase != null) {
return this.handleOAuth2Exception((OAuth2Exception) ase);
}
//身份验证相关异常
ase = (AuthenticationException) this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase != null) {
return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e));
}
//异常链中包含拒绝访问异常
ase = (AccessDeniedException) this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (ase instanceof AccessDeniedException) {
return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.ForbiddenException(ase.getMessage(), ase));
}
//异常链中包含Http方法请求异常
ase = (HttpRequestMethodNotSupportedException) this.throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);
if (ase instanceof HttpRequestMethodNotSupportedException) {
return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.MethodNotAllowed(ase.getMessage(), ase));
}
return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));
}
private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {
int status = e.getHttpErrorCode();
HttpHeaders headers = new HttpHeaders();
headers.setCacheControl(CacheControl.noCache());
headers.setPragma(CacheControl.noCache().getHeaderValue());
if (status == HttpStatus.UNAUTHORIZED.value() || e instanceof InsufficientScopeException) {
headers.set(HttpHeaders.WWW_AUTHENTICATE, String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}
ResponseEntity<OAuth2Exception> response = new ResponseEntity(R.fail(e.getMessage()), 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;
}
}
}
Custom client-side exception handling filter CustomClientCredentialsTokenEndpointFilter
- Custom client-side exception handling filter: {“error”: “invalid_client”, “error_description”: “Bad client credentials”}
- By configuring to the authentication server (AuthorizationServerConfig) 的 client Authentication exception filter (client_id、client_secret Executed on error)
- To configure at the same time to set CustomAuthenticationEntryPoint Use this to format exception return values
- 注意:AuthenticationEntryPoint 没有实例,We need to implement this interface ourselves in order to inject
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {
private final AuthorizationServerSecurityConfigurer configurer;
private AuthenticationEntryPoint authenticationEntryPoint;
public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
this.configurer = configurer;
}
@Override
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
protected AuthenticationManager getAuthenticationManager() {
return configurer.and().getSharedObject(AuthenticationManager.class);
}
@Override
public void afterPropertiesSet() {
setAuthenticationFailureHandler((request, response, exception) -> authenticationEntryPoint.commence(request, response, exception));
setAuthenticationSuccessHandler((request, response, authentication) -> {
// no-op - just allow filter chain to continue to token endpoint
});
}
}
Two public exception handling classes(未认证、未授权)
实现 AuthenticationEntryPoint Interface to handle authentication exception response information
- 处理 Authentication 异常,如:token错误、过期
- 配置一:Configured into the resource server core configuration(ResourceServerConfig)对 token 进行校验
- 配置二:Configured into the authentication server core configuration(AuthorizationServerConfig),当客户端异常(client_id、client_secret错误)时会执行
import cn.hutool.http.HttpStatus;
import cn.mowen.common.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
R.failRender(HttpStatus.HTTP_UNAUTHORIZED, exception.getMessage(), response, HttpStatus.HTTP_UNAUTHORIZED);
log.error("Authentication异常: [{}], [{}], [{}]", request.getRequestURI(), exception.getMessage(), exception);
}
}
实现 AccessDeniedHandler Interface to handle the response information of permission exception
- Exception information for handling permission exceptions,For resource servers only,The authentication server does not need to be configured
- Client permission exception (resource_id),用户权限异常,Custom response value
import cn.hutool.http.HttpStatus;
import cn.mowen.common.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException {
R.failRender(HttpStatus.HTTP_UNAUTHORIZED, exception.getMessage(), response, HttpStatus.HTTP_UNAUTHORIZED);
log.error("AccessDenied异常: [{}], [{}], [{}]", exception.getMessage(), exception.getLocalizedMessage(), exception.toString());
}
}
Add the exception handling class defined above to the authentication server core configuration AuthorizationServerConfig 中
- AuthorizationServerConfig Other configuration has been omitted,Just need to find the corresponding method to append it,详细见 Oauth2.0 认证服务器搭建
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/** * 用来配置令牌端点的安全约束, Password verification methods, etc */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// Custom client-side exception handling filter: {"error": "invalid_client", "error_description": "Bad client credentials"}
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();//初始化的时候执行
endpointFilter.setAuthenticationEntryPoint(customAuthenticationEntryPoint);//Formats the response format for client exceptions
security
//.allowFormAuthenticationForClients()
.addTokenEndpointAuthenticationFilter(endpointFilter) //Add a filter before client authentication
;
/* * allowFormAuthenticationForClients 的作用: * 允许表单认证(申请令牌), 而不仅仅是Basic Auth方式提交, 且url中有client_id和client_secret的会走 ClientCredentialsTokenEndpointFilter 来保护, * 也就是在 BasicAuthenticationFilter 之前添加 ClientCredentialsTokenEndpointFilter,使用 ClientDetailsService 来进行 client side login verification. * 但是,在使用自定义的 CustomClientCredentialsTokenEndpointFilter 时, * 会导致 oauth2 仍然使用 allowFormAuthenticationForClients 中默认的 ClientCredentialsTokenEndpointFilter 进行过滤,Resulting in our customization CustomClientCredentialsTokenEndpointFilter 不生效. * 因此在使用 CustomClientCredentialsTokenEndpointFilter 时,It is no longer necessary to turn it on allowFormAuthenticationForClients() 功能. */
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.exceptionTranslator(customWebResponseExceptionTranslator)//自定义异常转换类(处理grant_type, username, password错误的异常)
;
}
}
Configure those two common exception handling classes into the core configuration of the resource server ResourceServerConfig
import cn.mowen.common.constant.OauthConstant;
import cn.mowen.common.constant.CommonWhiteConstant;
import cn.mowen.common.exception.oauth.CustomAuthenticationEntryPoint;
import cn.mowen.common.exception.oauth.CustomAccessDeniedHandler;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableResourceServer
@AllArgsConstructor
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final TokenStore jwtTokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(OauthConstant.OAUTH_RESOURCE_ID)
.tokenStore(jwtTokenStore)
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.accessDeniedHandler(new CustomAccessDeniedHandler())
.stateless(true)
;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
//放行 url 在此配置
.antMatchers(CommonWhiteConstant.white).permitAll()
.antMatchers(white).permitAll()
.anyRequest().authenticated()
;
}
// 白名单
private static final String[] white = {
"/test/**"
};
}
Oauth2.0 系列文章
The following is synchronized to Yuque、可读性好一点,CSDN Just keep reading the column.
Oauth2.0 核心篇
Oauth2.0 安全性(Take WeChat authorized login as an example)
Oauth2.0 认证服务器搭建
Oauth2.0 Add verification code login method
Oauth2.0 资源服务器搭建
Oauth2.0 Custom response values and exception handling
Oauth2.0 补充
边栏推荐
猜你喜欢
随机推荐
Unity-Post Processing
Qt | 读取文件内容并删除文件 QFile
unity 和C# 一些官方优化资料
第二十九章:树的基本概念和性质
饥荒联机版Mod开发——准备工具(一)
Unity-PlayMaker
Problems related to prime numbers - small notes
flex布局
audio console无法连接到RPC服务
Doubly linked list (normal iterators and const iterators)
光波导应用中的真实光栅效应
Unity中事件的3种实现方法
面试汇总
C语言函数调用过程-汇编分析
你的站点可能还没有准备好用于Site KitSite Kit 无法访问 WordPress REST API。请确保其已在您的站点上启用。
implement tcp copa on ns3
冷读123
如何编辑VirtualLab Fusion结果的格式
Run ns3 with multiple processes
分布式一致性协议-Paxos