自用的响应信息主体

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();@Overridepublic 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);}@Overridepublic String getOAuth2ErrorCode() {return "method_not_allowed";}@Overridepublic int getHttpErrorCode() {return 405;}}private static class UnauthorizedException extends OAuth2Exception {public UnauthorizedException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "unauthorized";}@Overridepublic int getHttpErrorCode() {return 401;}}private static class ServerErrorException extends OAuth2Exception {public ServerErrorException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "server_error";}@Overridepublic int getHttpErrorCode() {return 500;}}private static class ForbiddenException extends OAuth2Exception {public ForbiddenException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "access_denied";}@Overridepublic 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;}@Overridepublic void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {this.authenticationEntryPoint = authenticationEntryPoint;}@Overrideprotected AuthenticationManager getAuthenticationManager() {return configurer.and().getSharedObject(AuthenticationManager.class);}@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {/*** 用来配置令牌端点的安全约束, 密码校验方式等*/@Overridepublic 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() 功能。*/}@Overridepublic 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;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.resourceId(OauthConstant.OAUTH_RESOURCE_ID).tokenStore(jwtTokenStore).authenticationEntryPoint(new CustomAuthenticationEntryPoint()).accessDeniedHandler(new CustomAccessDeniedHandler()).stateless(true);}@Overridepublic 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 补充

Oauth2.0 自定义响应值以及异常处理相关推荐

  1. OAuth2.0 - 自定义模式授权 - 短信验证码登录

    一.OAuth2.0 - 自定义模式授权 上篇文章我们分析了目前的情况,演示了微服务的大环境下在保证安全的情况下通过SpringGateWay实现统一的鉴权处理,但是前面的演示中,我们都是基于用户名密 ...

  2. Springboot+oauth2.0实现微信登录(oauth2.0自定义授权模式)

    1.前置准备参考 https://blog.csdn.net/qq_34190023/article/details/81133619 2.微信登录实现流程图 3.oauth自定义授权模式 上图大概描 ...

  3. Oauth2.0 认证服务器搭建

    核心 POM <dependency><groupId>org.springframework.cloud</groupId><artifactId>s ...

  4. Oauth2.0 安全性(以微信授权登陆为例)

    前言 用户 A 要使用微信账号登陆 Z 平台(www.z.com),一个黑客 H 想要把使用微信账号登陆 Z 平台的用户 A 转到恶意网站(www.h.com)来侵犯 A 的隐私 为什么要校验 red ...

  5. OAuth2.0实现自定义颁发token

    一. 底层原理 通过浏览器/外部服务传入账号密码,以及clientId以及secret申请token,TokenEndpoint 即为校验并颁发Token的方法. org.springframewor ...

  6. android 新浪微博Oauth2.0认证以及自定义webview认证

    首先不得不说,自己犯了一个比较窝囊的错误,不过也不能完全怪我,因为大家都知道,新浪微博Oauth2.0提供的jar包,好家伙2M多,谁看谁都不想用,才使得我去研究1.0的使用,研究好久,终于实现可用了 ...

  7. C# 网络编程之豆瓣OAuth2.0认证详解和遇到的各种问题及解决

            最近在帮人弄一个豆瓣API应用,在豆瓣的OAuth2.0认证过程中遇到了各种问题,同时自己需要一个个的尝试与解决,最终完成了豆瓣API的访问.作者这里就不再吐槽豆瓣的认证文档了,毕竟人 ...

  8. 实战干货!Spring Cloud Gateway 整合 OAuth2.0 实现分布式统一认证授权!

    今天这篇文章介绍一下Spring Cloud Gateway整合OAuth2.0实现认证授权,涉及到的知识点有点多,有不清楚的可以看下陈某的往期文章. 文章目录如下: 微服务认证方案 微服务认证方案目 ...

  9. 前后端分离Oauth2.0 - springsecurity + spring-authorization-server —授权码模式

    序言 对于目前有很多的公司在对旧的web工程做重构,拆分服务,使用前端vue,后端springboot微服务,重构的要点之一是认证授权框架的选型. 对于原有的 spring-security-oaut ...

最新文章

  1. leetcode算法题--乘积最大子数组
  2. 抛硬币 直到连续出现两次字为止
  3. ElementUI中使用el-time-picker向SpringBoot传输24小时制时间参数以及数据库中怎样存储
  4. B07_NumPy 高级索引(整数数组索引,布尔索引,花式索引)
  5. h5 兑换商品 页面模版_H5页面制作工具编辑功能对比:木疙瘩、微吾、云
  6. apscheduler mysql_APScheduler (重点)
  7. P3295-[SCOI2016]萌萌哒【ST表,并查集】
  8. java在创建对象时必须_Java中5种创建对象的方式
  9. 学linux需要关闭防火墙,一起学习linux 关闭防火墙命令
  10. RecycleView的Item Animator动画
  11. Java之一致性hash算法原理及实现
  12. web前端开发初学者十问集锦(5)
  13. Python 多线程7-线程通信
  14. EntityFramework在root目录web.config中的配置设置
  15. Python 的List排序
  16. 峰度的意义_李德荃关于偏度与峰度的讲解
  17. C++实现windows平台下音频播放音量调节功能
  18. [深度学习]基于TensorFlow的基本深度学习模型
  19. VC6.0的兼容性问题解决方案
  20. Web 3.0 :它是互联网的未来吗?

热门文章

  1. HTML(0)-内联元素和块级元素
  2. Swift 周报 第十一期
  3. Matlab:三角剖分表示法
  4. 数字源表如何测试MOS管?
  5. 轮廓波-非下采样轮廓波NSCT
  6. RH436之资源与资源组
  7. 模拟电话拨号器数字界面 以及跳转到拨打界面
  8. 讨论游戏服务器压力的那点事儿
  9. php冒泡算法排序,PHP算法大全(2)冒泡排序算法解决差生排序问题
  10. w7提示无法关闭计算机,电脑关不了机怎么办w7系统_win7电脑正常关机关不了解决方法-win7之家...