认证异常翻译

默认情况下,当我们在获取令牌时输入错误的用户名或密码,系统返回如下格式响应:

{
"error": "invalid_grant",
"error_description": "Bad credentials"
}

当grant_type错误时,系统返回:

{
"error": "unsupported_grant_type",
"error_description": "Unsupported grant type: passwordd"
}

在security中,我们可以自定义一个异常翻译器,将这些认证类型异常翻译为友好的格式

在translator包下新建类SecurityResponseExceptionTranslator

@Slf4j
@Component
public class SecurityResponseExceptionTranslator implements WebResponseExceptionTranslator { @Override
public ResponseEntity translate(Exception e) {
ResponseEntity.BodyBuilder status = ResponseEntity.status(HttpStatus.UNAUTHORIZED);
String message = "认证失败";
log.info(message, e);
if (e instanceof UnsupportedGrantTypeException) {
message = "不支持该认证类型";
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
}
if (e instanceof InvalidTokenException
&& StringUtils.containsIgnoreCase(e.getMessage(), "Invalid refresh token (expired)")) {
message = "刷新令牌已过期,请重新登录";
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
}
if (e instanceof InvalidScopeException) {
message = "不是有效的scope值";
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
}
if (e instanceof InvalidGrantException) {
if (StringUtils.containsIgnoreCase(e.getMessage(), "Invalid refresh token")) {
message = "refresh token无效";
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
}
if (StringUtils.containsIgnoreCase(e.getMessage(), "locked")) {
message = "用户已被锁定,请联系管理员";
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
}
message = "用户名或密码错误";
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
}
return status.body(ResponseVO.failed(message + ":" + e.getMessage()));
} }

要让这个异常翻译器生效,我们还需在认证服务器配置类的configure(AuthorizationServerEndpointsConfigurer endpoints)方法里指定它:

@Autowired
private SecurityResponseExceptionTranslator exceptionTranslator; ..... @Override
@SuppressWarnings("all")
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.exceptionTranslator(exceptionTranslator);
}
......

资源服务器异常

资源服务器异常主要有两种:令牌不正确返回401和用户无权限返回403

新建SecurityExceptionEntryPoint类用于处理403类型异常:

@Component
public class SecurityExceptionEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
ResponseVO.makeResponse(response
, MediaType.APPLICATION_JSON_VALUE
, HttpStatus.UNAUTHORIZED.value()
, JSONObject.toJSONString(ResponseVO.failed(401, "token无效")).getBytes());
}
}

其中ResponseVO.makeResponse和ResponseVO.failed分别是工具类ResponseVO的方法:

/**
* 设置响应
*
* @param response HttpServletResponse
* @param contentType content-type
* @param status http状态码
* @param value 响应内容
* @throws IOException IOException
*/
public static void makeResponse(HttpServletResponse response, String contentType,
int status, Object value) throws IOException {
response.setContentType(contentType);
response.setStatus(status);
response.getOutputStream().write(JSONObject.toJSONString(value).getBytes());
} public static ResponseVO failed(Integer code, String msg) {
ResponseVO result = new ResponseVO();
result.setCode(code);
result.setMsg(msg);
result.setData(Lists.newArrayList());
return result;
}

新建SecurityAccessDeniedHandler用于处理403类型异常:

@Component
public class SecurityAccessDeniedHandler implements AccessDeniedHandler { @Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
ResponseVO.makeResponse(response
, MediaType.APPLICATION_JSON_VALUE
, HttpStatus.FORBIDDEN.value()
, JSONObject.toJSONString(ResponseVO.failed(403, "没有权限访问该资源")).getBytes());
}
}

在资源服务器配置类里注入,并配置:

@Autowired
private SecurityAccessDeniedHandler accessDeniedHandler;
@Autowired
private SecurityExceptionEntryPoint exceptionEntryPoint; ...... @Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.authenticationEntryPoint(exceptionEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}

扩展

由于我们的资源服务器可能有多个,所以上面两个资源服务器的异常翻译类我们可能要复用,所以这种情况下我们应该将其抽离出来写在公共包common下,然后在其他的资源服务器中引用这个common模块。

但是这时,我们在使用自动注入这两个类时,会发现我们不能注入这两个类,这时由于Springboot的默认扫包范围是,启动类所属包路径及其子类,所以即使在这两个类上使用@Component注解标注,它们也不能被成功注册到各个资源服务器的SpringIOC容器中。我们可以使用@Enable模块驱动的方式来解决这个问题。

在common模块中新建configure包,然后在该包下新建SecurityExceptionConfigure配置类:

public class SecurityExceptionConfigure {

    @Bean
@ConditionalOnMissingBean(name = "accessDeniedHandler")
public SecurityAccessDeniedHandler accessDeniedHandler() {
return new SecurityAccessDeniedHandler();
} @Bean
@ConditionalOnMissingBean(name = "authenticationEntryPoint")
public SecurityExceptionEntryPoint authenticationEntryPoint() {
return new SecurityExceptionEntryPoint();
}
}

在该配置类中,我们注册了SecurityAccessDeniedHandler和SecurityExceptionEntryPoint。

  • @ConditionalOnMissingBean注解的意思是,当IOC容器中没有指定名称或类型的Bean的时候,就注册它。以@ConditionalOnMissingBean(name = "accessDeniedHandler")为例,当资源服务器系统中的Spring IOC容器中没有名称为accessDeniedHandler的Bean的时候,就将SecurityAccessDeniedHandler注册为一个Bean。这样做的好处在于,子系统可以自定义自个儿的资源服务器异常处理器,覆盖我们在common通用模块里定义的。

接着定义一个注解来驱动该配置类。

在common模块下新建annotation包,然后在该包下新建EnableSecurityAuthExceptionHandler注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SecurityExceptionConfigure.class)
public @interface EnableSecurityAuthExceptionHandler { }

在该注解上,我们使用@Import将SecurityExceptionConfigure配置类引入了进来。

然后,我们只需要在需要使用这两个配置类的资源服务器系统的启动类上引入@EnableSecurityAuthExceptionHandler来标记

@SpringBootApplication
@EnableSecurityAuthExceptionHandler
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

最后,我们就可以在资源服务器的配置类中愉快的使用自动注入的方式来注入SecurityAccessDeniedHandler和SecurityExceptionEntryPoint这两个类了

@Autowired
private SecurityAccessDeniedHandler accessDeniedHandler;
@Autowired
private SecurityExceptionEntryPoint exceptionEntryPoint; ...... @Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.authenticationEntryPoint(exceptionEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}

参考博客:https://mrbird.cc/deepin-springboot-autoconfig.html

OAuth + Security - 7 - 异常翻译的更多相关文章

  1. Spring Security中异常上抛机制及对于转型处理的一些感悟

    在使用Spring Security的过程中,我们会发现框架内部按照错误及问题出现的场景,划分出了许许多多的异常,但是在业务调用时一般都会向外抛一个统一的异常出来,为什么要这样做呢,以及对于抛出来的异 ...

  2. spark启动后出现“JAVA_HOME not set” 异常和"org.apache.hadoop.security.AccessControlException"异常

    /home/bigdata/hadoop/spark-2.1.1-bin-hadoop2.7/sbin/start-all.sh 启动后执行jps命令,主节点上有Master进程,其他子节点上有Wor ...

  3. OAuth + Security -1 - 认证服务器配置

    配置 基础包依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  4. OAuth + Security - 3 - JWT令牌

    为什么使用JWT令牌 在上面的资源服务器中,通过配置,我们了解到,当我们拿着token去获取资源时,程序会先去调用远程认证服务器的端点去验证解析token,或者在本地解析校验token,这样毫无疑问, ...

  5. OAuth + Security - 5 - Token存储升级(数据库、Redis)

    PS:此文章为系列文章,建议从第一篇开始阅读. 在我们之前的文章中,我们当时获取到Token令牌时,此时的令牌时存储在内存中的,这样显然不利于我们程序的扩展,所以为了解决这个问题,官方给我们还提供了其 ...

  6. C# Windows服务安装出现System.Security.SecurityException异常解决办法

    我把注册windows服务所用的安装及启用服务命令写到了bat可执行文件(名称为install.bat)中,如下所示: %SystemRoot%\Microsoft.NET\Framework\v4. ...

  7. OAuth + Security - 6 - 自定义授权模式

    我们知道OAuth2的官方提供了四种令牌的获取,简化模式,授权码模式,密码模式,客户端模式.其中密码模式中仅仅支持我们通过用户名和密码的方式获取令牌,那么我们如何去实现一个我们自己的令牌获取的模式呢? ...

  8. Spring Security报异常 Encoded password does not look like BCrypt

    控制台报错: Encoded password does not look like BCrypt 意思是前端传回去的密码格式与数据库里的密码格式不匹配,这样即使密码正确也无法校验.自然也就无法登录. ...

  9. OAuth 2.0 安全案例回顾

    原文:http://drops.wooyun.org/papers/598 0x00 背景 纵观账号互通发展史,可以发现OAuth比起其它协议(如OpenID)更流行的原因是,业务双方不仅要求账号本身 ...

  10. OAuth 2.0安全案例回顾

    转载自:http://www.360doc.com/content/14/0311/22/834950_359713295.shtml 0x00 背景 纵观账号互通发展史,可以发现OAuth比起其它协 ...

随机推荐

  1. BladeDISC 0.2.0更新发布

    ​简介:在BladeDISC正式开源三个月后,我们发布了0.2.0版本,该更新包含了大量的性能优化与功能增强. 在BladeDISC正式开源三个月后,我们发布了0.2.0版本,该更新包含了大量的性能优 ...

  2. Java应用结构规范

    ​简介:在Java程序开发中,命名和应用分层无疑是广大后端同胞的两大"痛点",本文提供一种基于领域模型的轻量级应用分层结构设计,供大家参考.下面按分层结构.分层明细.调用关系.各层 ...

  3. dotnet C# 获取当前设备可移动磁盘

    本文告诉大家如何获取当前设备的可移动磁盘 在我的 WPF 应用里面,期望获取到 U 盘的所在盘进行一些有趣的逻辑.可以通过 DriveInfo 类的 GetDrives 获取当前所有的驱动器磁盘 再通 ...

  4. WPF 触摸底层 PenImc 是如何工作的

    在 WPF 里面有其他软件完全比不上的超快速的触摸,这个触摸是通过 PenImc 获取的.现在 WPF 开源了,本文就带大家来阅读触摸底层的代码,阅读本文需要一点 C# 和 C++ 基础 现在 WPF ...

  5. 鸿蒙HarmonyOS实战-ArkUI事件(焦点事件)

    前言 焦点事件是指程序中的重要事件或关键点.焦点事件通常是程序的核心逻辑和功能,需要引起特殊的关注和处理. 在图形用户界面(GUI)编程中,焦点事件通常与用户交互和界面输入相关.例如,当用户点击按钮. ...

  6. 自定义Lua解析器管理器-------演化脚本V0.5

    [3]自定义Lua解析器管理器-------演化脚本V0.5 方便我们在项目中使用Lua解析方法,我们封装管理一个lua解析器,管理LuaState的方法执行. 解析器脚本: using LuaInt ...

  7. DNS(4) -- dns功能实现-配置正向解析和反向解析以及DNS递归查询示例

    目录 1 DNS配置示例 1.1 DNS解析类型 1.2 配置正向解析 1.3 配置反向解析 1.4 DNS递归查询 1.4.1 开启递归查询 1.4.2 关闭递归查询 1 DNS配置示例 1.1 D ...

  8. debug技巧之本地调试

    大家好啊,我是summo,今天给大家分享一下我平时是怎么调试代码的,不是权威也不是教学,就是简单分享一下,如果大家还有更好的调试方式也可以多多交流哦. 如果看过我文章的同学应该知道我是一个Java开发 ...

  9. vue 安装教程(如何在node环境下搭建vue项目)

    如果要配置node.js环境请查看(node.js环境在Window和Mac中配置,以及安装cnpm和配置Less环境)在终端输入命令 cnpm -v出现版本号 1.安装vue    window : ...

  10. uniapp底层跨端原理

    uniapp底层跨端原理 - 代码编写:开发者使用Vue.js框架编写uniapp的代码,包括页面结构.样式和逻辑等. - 编译过程:在编译过程中,uniapp会将Vue.js的代码转换为各个平台所需 ...