自用的响应信息主体
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);
}
}
自定义无异常情况下请求 /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 错误的异常响应
- 默认情况是使用 WebResponseExceptionTranslator接口的实现类 DefaultWebResponseExceptionTranslator对抛出的异常进行处理
- 本文处理方法就是通过实现WebResponseExceptionTranslator接口来入手,来达到对异常信息的处理
- 实现之后要记得将其添加到认证服务器核心配置 AuthorizationServerConfig 的端点配置 (AuthorizationServerEndpointsConfigurer.exceptionTranslator) 中,往下看会有写,不急
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;
}
}
}
自定义客户端异常处理过滤器 CustomClientCredentialsTokenEndpointFilter
- 自定义客户端异常处理过滤器: {“error”: “invalid_client”, “error_description”: “Bad client credentials”}
- 通过配置到认证服务器 (AuthorizationServerConfig) 的 client 认证异常过滤器中 (client_id、client_secret 错误时会执行)
- 配置的同时要设置 CustomAuthenticationEntryPoint 以此来格式化异常返回值
- 注意:AuthenticationEntryPoint 没有实例,需要我们自己实现这个接口才能进行注入
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
});
}
}
两个公共异常处理类(未认证、未授权)
实现 AuthenticationEntryPoint 接口来处理认证异常的响应信息
- 处理 Authentication 异常,如:token错误、过期
- 配置一:配置到资源服务器核心配置中(ResourceServerConfig)对 token 进行校验
- 配置二:配置到认证服务器核心配置中(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 接口来处理权限异常的响应信息
- 处理权限异常的异常信息,只针对于资源服务器,认证服务器无需配置
- 客户端权限异常 (resource_id),用户权限异常,自定义响应值
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());
}
}
将以上定义的异常处理类添加到认证服务器核心配置 AuthorizationServerConfig 中
- AuthorizationServerConfig 其他配置已省略,只需要找到对应方法进行追加就好,详细见 Oauth2.0 认证服务器搭建
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 用来配置令牌端点的安全约束, 密码校验方式等
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 自定义客户端异常处理过滤器: {"error": "invalid_client", "error_description": "Bad client credentials"}
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();//初始化的时候执行
endpointFilter.setAuthenticationEntryPoint(customAuthenticationEntryPoint);//格式化客户端异常的响应格式
security
//.allowFormAuthenticationForClients()
.addTokenEndpointAuthenticationFilter(endpointFilter) //添加一个客户端认证之前的过滤器
;
/*
* allowFormAuthenticationForClients 的作用:
* 允许表单认证(申请令牌), 而不仅仅是Basic Auth方式提交, 且url中有client_id和client_secret的会走 ClientCredentialsTokenEndpointFilter 来保护,
* 也就是在 BasicAuthenticationFilter 之前添加 ClientCredentialsTokenEndpointFilter,使用 ClientDetailsService 来进行 client 端登录的验证。
* 但是,在使用自定义的 CustomClientCredentialsTokenEndpointFilter 时,
* 会导致 oauth2 仍然使用 allowFormAuthenticationForClients 中默认的 ClientCredentialsTokenEndpointFilter 进行过滤,致使我们的自定义 CustomClientCredentialsTokenEndpointFilter 不生效。
* 因此在使用 CustomClientCredentialsTokenEndpointFilter 时,不再需要开启 allowFormAuthenticationForClients() 功能。
*/
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.exceptionTranslator(customWebResponseExceptionTranslator)//自定义异常转换类(处理grant_type, username, password错误的异常)
;
}
}
将那两个公共异常处理类配置到资源服务器的核心配置中 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 系列文章
以下是同步到语雀的、可读性好一点,CSDN 继续看的点专栏就好。
Oauth2.0 核心篇
Oauth2.0 安全性(以微信授权登陆为例)
Oauth2.0 认证服务器搭建
Oauth2.0 添加验证码登陆方式
Oauth2.0 资源服务器搭建
Oauth2.0 自定义响应值以及异常处理
Oauth2.0 补充文章来源地址https://www.toymoban.com/news/detail-415383.html
文章来源:https://www.toymoban.com/news/detail-415383.html
到了这里,关于Oauth2.0 自定义响应值以及异常处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!