前言

在我们使用SpringSecurity OAuth2做认证授权时,默认返回都是SpringSecurity OAuth2提供好的,返回不是很友好,本章就是针对这些异常做统一返回处理,主要解决返回格式问题,和返回提示信息重写!我们先来看看SpringSecurity OAuth2默认返回格式,如下!

本文将默认返回改造为如下格式

{code:xxx,msg:xxxx,data:xxx
}

SpringSecurity OAuth2异常处理呢,又分为两部分,一部分为授权服授权时异常,通常包含用户账号异常、授权类型异常、端点信息异常等。另一部分为资源服异常,通常为token异常、权限不够等!

认证服异常处理

知识铺垫
我们在做认证获取Token的时候,是调用SpringSecurity OAuth2提供的/oauth/token,我们进入到对应的Endpoint,这个Endpoint我这里不多介绍,想了解的看看我的往期文章即可!进入源码!



这个接口就是获取Token的,那么异常就再个方法抛出,对应的异常捕获SpringSecurity OAuth2 默认是被DefaultWebResponseExceptionTranslator处理,顶层接口为WebResponseExceptionTranslator,那么我们同样继承WebResponseExceptionTranslator重写对应方法即可!

Auth2ResponseExceptionTranslator
这个是我们自己定义的异常翻译器,代码如下


/**
* @description: SpringSecurity OAuth2 异常翻译器,统一捕获授权时异常信息处理
* @author TAO
* @date 2021/10/1 16:14
*/
@Slf4j
@Component
public class Auth2ResponseExceptionTranslator implements WebResponseExceptionTranslator {private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();@Overridepublic ResponseEntity translate(Exception e) throws Exception {Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);Exception ase = null;// 异常栈获取 OAuth2Exception 异常ase=(UnsupportedGrantTypeException) throwableAnalyzer.getFirstThrowableOfType(UnsupportedGrantTypeException.class, causeChain);if (ase != null) {return handleOAuth2Exception(new GrantTypeException(e.getMessage(), e));}ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class,causeChain);if (ase != null) {return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase));}ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);if (ase != null) {return handleOAuth2Exception(new MethodNotAllowedException(ase.getMessage(), ase));}ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);if (ase != null) {return handleOAuth2Exception((OAuth2Exception) ase);}// 不包含上述异常则服务器内部错误return handleOAuth2Exception(new ServerErrorException(HttpStatus.OK.getReasonPhrase(), e));}private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) {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", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));}return new ResponseEntity<>(new ExtendOAuth2Exception(e.getOAuth2ErrorCode(),e.getMessage()), headers,HttpStatus.valueOf(status));}}

这里逻辑其实非常简单,translate方法就是根据不同的异常栈返回不同的异常对象!这个方法需要提前准备一些异常类型、异常序列化器,

准备异常

ExtendOAuth2Exception

/**
* @description: 扩展OAuth2Exception
* @author TAO
* @date 2021/9/29 22:14
*/
@JsonSerialize(using = ExtendOAuth2ExceptionSerializer.class)
public class ExtendOAuth2Exception extends OAuth2Exception {@Getterprivate String dataMsg;public ExtendOAuth2Exception(String msg) {super(msg);}public ExtendOAuth2Exception(String msg, Throwable t) {super(msg, t);}public ExtendOAuth2Exception(String msg, String dataMsg) {super(msg);this.dataMsg = dataMsg;}}

ForbiddenException

/**
* @description: 拒绝访问Exception
* @author TAO
* @date 2021/9/29 22:10
*/
public class ForbiddenException extends ExtendOAuth2Exception{public ForbiddenException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "access_denied---拒绝访问";}@Overridepublic int getHttpErrorCode() {return HttpStatus.FORBIDDEN.value();}}

GrantTypeException

/**
* @description: 授权模式Exception
* @author TAO
* @date 2021/10/1 16:21
*/
public class GrantTypeException extends ExtendOAuth2Exception{public GrantTypeException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "不支持当前授权类型!!!";}@Overridepublic int getHttpErrorCode() {return 400;}}

MethodNotAllowedException

/**
* @description: 方法不允许访问Exception
* @author TAO
* @date 2021/9/29 22:18
*/
public class MethodNotAllowedException extends ExtendOAuth2Exception{public MethodNotAllowedException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "method_not_allowed---方法不允许访问";}@Overridepublic int getHttpErrorCode() {return HttpStatus.METHOD_NOT_ALLOWED.value();}}

ServerErrorException

/**
* @description: 服务器内部Exception
* @author TAO
* @date 2021/9/29 22:20
*/
public class ServerErrorException extends ExtendOAuth2Exception{public ServerErrorException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "server_error---服务器内部异常";}@Overridepublic int getHttpErrorCode() {return HttpStatus.INTERNAL_SERVER_ERROR.value();}}

UnauthorizedException

/**
* @description: 未授权Exception
* @author TAO
* @date 2021/9/29 22:21
*/
public class UnauthorizedException extends ExtendOAuth2Exception {public UnauthorizedException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "unauthorized---未授权";}@Overridepublic int getHttpErrorCode() {return HttpStatus.UNAUTHORIZED.value();}}

异常序列化器

/**
* @description: ExtendOAuth2Exception返回格式序列化
* @author TAO
* @date 2021/9/29 22:42
*/
public class ExtendOAuth2ExceptionSerializer extends StdSerializer<ExtendOAuth2Exception> {public ExtendOAuth2ExceptionSerializer() {super(ExtendOAuth2Exception.class);}@Overridepublic void serialize(ExtendOAuth2Exception e, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {gen.writeStartObject();gen.writeObjectField("code", String.valueOf(e.getHttpErrorCode()));gen.writeStringField("msg", e.getMessage());gen.writeStringField("data", e.getDataMsg());gen.writeEndObject();}
}

这玩意就是将我们的异常信息从新按照我们的格式返回!

配置异常翻译器
前面写了一大堆,当然只有配置给SpringSecurity OAuth2才能派上用场!找到授权服配置类

@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {//配置令牌的访问端点和令牌服务endpoints.authorizationCodeServices(authorizationCodeServices).authenticationManager(authenticationManager)//认证管理器.exceptionTranslator(new Auth2ResponseExceptionTranslator()) // 设置自定义的异常解析器.tokenServices(tokenService).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);}

.exceptionTranslator(new Auth2ResponseExceptionTranslator()) // 设置自定义的异常解析器
启动项目测试

这是我故意将授权类型写错,返回的异常信息,既有我们的自定义异常,格式有满足我们的要求!我们在将账号密码写错试试

这里返回则是英文的,改不改都无所谓了,当然咯我们也可以修改,例如我们在校验账号密码的时候,代码如下!

那么到这里基本上就差不多了,如果在细的话就要根据自己项目进行定制一些异常,大致上流程就是这些。下面进入资源服异常处理!

资源服异常

简介说明
这个就比较简单了,文章开头有基本介绍资源服异常场景,这里在重复说一下,主要场景也就三个,未携带token,权限不足,token解析失败(过期或者无效token),通常我们处理两个场景就差不多了,上面三个场景我们可以分为token异常(未携带token,oken解析失败(过期或者无效token)),下面我们开始撸代码!

token异常–未携带token,oken解析失败(过期或者无效token)

/*** @author TAO* @description: 解决匿名用户访问无权限资源时的异常处理器* 实际上也就是用户没有认证过,请求头中没有携带了Token,或者是resource_ids范围不够* @date 2021/4/8 22:43*/
@Slf4j
@Component("customAuthenticationEntryPointHandler")
public class CustomAuthenticationEntryPointHandler extends OAuth2AuthenticationEntryPoint {@Override@SneakyThrowspublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {response.setCharacterEncoding(CommonConstants.UTF8);response.setContentType(CommonConstants.CONTENT_TYPE);R<String> result = new R<>();result.setCode(HttpStatus.HTTP_UNAUTHORIZED);log.info("匿名用户访问无权限资源时的异常");if (authException != null) {log.info("errorMsg===>"+authException.getMessage());result.setMsg("匿名用户访问无权限资源时的异常");}Throwable cause = authException.getCause();if (cause instanceof OAuth2AccessDeniedException) {log.info("resource_ids范围不够");}  else if (cause instanceof InvalidTokenException) {log.info("Token解析失败");}else if (authException instanceof InsufficientAuthenticationException) {log.info("未携带token");}response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);PrintWriter printWriter = response.getWriter();printWriter.append(new ObjectMapper().writeValueAsString(result));}}

权限不足

/*** @author TAO* @description: 解决认证过的用户访问无权限资源时的异常处理器,实际上也就是用户认证过了,请求头中携带了Token,只是权限不够* @date 2021/4/8 22:42*/
@Slf4j
@Component("customAccessDeniedHandler")
public class CustomAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {log.info("认证过的用户访问无权限资源时的异常");response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.setStatus(HttpStatus.FORBIDDEN.value());R<String> result = new R<>();result.setCode(cn.hutool.http.HttpStatus.HTTP_FORBIDDEN);//权限不足403result.setMsg("认证过的用户访问无权限资源时的异常");response.getWriter().write(new ObjectMapper().writeValueAsString(result));}}

资源服配置处理器
这里也和授权服那样配置翻译器一样,在资源服核心配置中,代码如下!

@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(RESOURCE_ID)//资源 id.authenticationEntryPoint(customAuthenticationEntryPointHandler)//匿名用户访问无权限资源时的异常处理器.accessDeniedHandler(customAccessDeniedHandler)//认证过的用户访问无权限资源时的异常处理器.stateless(true);}

重启服务器测试
不携带token捕获

错误token

错误resource_ids范围

这个范围是指授权服颁发的令牌中携带的资源服id不符合!如下

此时我们的资源服为res2,所以会被异常捕获!

权限不足

那么至此怎个异常捕获就告一段落了!

写在最后

在写这篇文章的资源服异常处理时看了一下其他的资料,有的是直接将未携带token和token解析失败分开处理的,我这里是使用的如下代码

     }  else if (cause instanceof InvalidTokenException) {log.info("Token解析失败");}else if (authException instanceof InsufficientAuthenticationException) {log.info("未携带token");}

InvalidTokenException和InsufficientAuthenticationException,这可能和个人编码风格有关吧,我是属于那种不想把原有流程打乱的,也不喜欢写一些特殊处理代码,看过一篇帖子是直接通过加过滤器检验请求头中有没有携带token,来抛出这个异常的,个人觉得不是很好,这样莫名会让请求都经过一个过滤器,这样多浪费呀!代码如下!

定义过滤器

@Component
public class MyTokenFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String uri=request.getRequestURI();if(uri.equals("/oauth/token")||uri.equals("/oauth/login")){filterChain.doFilter(request,response);}else{boolean access_token=false;boolean authorization=false;if(request.getParameter("access_token")==null){access_token=true;}if(request.getHeader("Authorization")==null){authorization=true;}else{if(!request.getHeader("Authorization").startsWith("Bearer")){authorization=true;}}if(access_token&&authorization){response.setContentType("application/json;charset=UTF-8");response.getWriter().write(JSONObject.toJSONString(new ResponseResult(401,"未获得凭证!")));}else{filterChain.doFilter(request,response);}}}
}

在WebSecurity核心配置中配置当前过滤器

 @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/oauth/login").permitAll().anyRequest().authenticated().and().httpBasic().and().csrf().disable();http.addFilterBefore(mytokenfilter, BasicAuthenticationFilter.class);}

这个方案我没试,感兴趣的可以看看!最后说一点,为什么这个方案我不愿意试,是因为这里资源服认证时还加入的SpringSecurity的一套东西,在配置之过滤器这里就能体现出,因为是配置在WebSecurity的配置中,这个看过我的往期文章的就知道为什么我这里不试试,这个个人感觉就有问题,一套资源同时需要被两套东西控制容易出问题,详细请看往期文章!SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解,这就是同时配置了SpringSecurity和资源服接管产生的问题

SpringSecurity OAuth2异常处理OAuth2Exception相关推荐

  1. springsecurity oauth2.0 集成sso单点登录

    前言 在前两篇中,我们基本上了解springsecurity 的授权码模式和密码模式的工作流程,其实来说,掌握了授权码模式,再基于springsecurity 做单点登录的集成就是一件非常容易的事情 ...

  2. 六、SpringSecurity OAuth2 + SpringCloud Gateway实现统一鉴权管理

    代码 代码仓库:地址 代码分支:lesson6 博客:地址 简介 在先前文章中,我们使用SpringSecurity OAuth2搭建了一套基于OAuth2协议的授权系统,并扩展了手机验证码授权模式. ...

  3. 零基础学习SpringSecurity OAuth2 四种授权模式(理论+实战)(配套视频讲解)

    配套视频直达 背景 前段时间有同学私信我,让我讲下Oauth2授权模式,并且还强调是零基础的那种,我也不太理解这个零基础到底是什么程度,但是我觉得任何阶段的同学看完我这个视频,对OAuth2的理解将会 ...

  4. SpringSecurity+OAuth2.0+JWT实现单点登录应用

    SpringSecurity+OAuth2.0+JWT实现单点登录应用 gitee项目练习地址:https://gitee.com/xzq25_com/springsecurity.oauth2 OA ...

  5. springsecurity oauth2.0

    参考了Springboot2+SpringSecurity+Oauth2+Mysql数据库实现持久化客户端数据 1 基本环境搭建 1.1 数据库脚本 数据库脚本从官方spring-security-o ...

  6. SpringSecurity Oauth2 - 自定义 SpringBoot Starter 远程访问受限资源

    文章目录 1. 自定义 SpringBoot Starter 1. 统一的dependency管理 2. 对外暴露 properties 3. 实现自动装配 4. 指定自动配置类的路径 META-IN ...

  7. springsecurity oauth2使用jwt实现单点登录

    Jwt方式已经分享在文章结尾处的百度网盘链接中,redis方式可以看我以前发表的文章. 文章目录 前言 一.springsecurity oauth2 + redis方式的缺点 二.oauth2认证的 ...

  8. 整合SpringSecurity OAuth2 JWT (附原因)一步步来如此简单

    梳理整合 SpringCloud 和 SpringSecurity OAuth2 的搭建流程,网上看了一些,感觉都很差强人意,所以决定梳理下,不多说了开鲁吧. 首先要搭建微服务基础加包版本,基于阿里系 ...

  9. SpringSecurity OAuth2 (7) 自定义 AccessToken 和 RefreshToken (JWT with RSA 签名)

    文章目录 AuthorizationServer 引言 AuthorizationServerTokenServices ResourceServerTokenServices TokenStore ...

最新文章

  1. react创建组件_如何使用React创建时间轴组件
  2. autojs遍历当前页面所有控件_自定义控件(引入布局)
  3. python读写excel(合并单元格)
  4. 您的连接不是私密连接
  5. U盘从4G变为了75M 恢复U盘容量的方法
  6. JSON和JS数据类型转化
  7. 【js】数组的splice方法和slice方法的区别
  8. 泰拉瑞亚服务器存档位置,泰拉瑞亚国服存档怎么恢复 国服存档位置
  9. 《推荐系统实践》算法纯享(附代码链接)(六)—— 借助社交网络推荐篇
  10. 移远EC20开发环境搭建
  11. ssget 用法详解
  12. axios请求下载excel文件以及文件乱码问题
  13. 阻焊机器人系统_可处理最大PCB面板干膜和阻焊激光直接成像的X3000(LDI)系统
  14. 图解“华为云潮汕火锅”的“牛里乾坤”
  15. 蓝桥杯 Python 练习题 Fibonacci数列
  16. payjs插件php,基于payjs的discuz支付插件制作
  17. SpringBoot整合Mybatis出现的错误:At least one base package must be specified
  18. requests发送post请求的一些疑点
  19. java中定义变量名时大写_,【Java】变量命名规范
  20. arp命令(windows ),nmap查看局域网内所有主机IP和MAC

热门文章

  1. 制图操作案例:ArcGIS Pro制图及出图小技巧——以土地利用图为例
  2. 基于SmartQQ协议的QQ聊天机器人-5
  3. 前端HTML、CSS、JS绘制三角形的方法
  4. 全国数据治理认证DAMA-CDGA/CDGP(线上班)招生简章
  5. IDEA中同项目引用报红问题
  6. 帕金森病MPTP小鼠模型 MPTP小鼠模型用来测试许多不同类型的药物的疗效
  7. Vulkan学习(九): Vertex Input Description Vertex Buffer Creation
  8. git——git push 出错
  9. 知乎2018暴露出来哪些漏洞?
  10. 东京被22个核污点包围 放射量与切尔诺贝利相当