Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(一)

简介: Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战(一)

一. 前言

【APP 移动端】Spring Security OAuth2 手机短信验证码模式

微信图片_20230710083637.gif

【微信小程序】Spring Security OAuth2 微信授权模式

微信图片_20230710083641.gif


【管理系统】Spring Security OAuth2 密码模式

微信图片_20230710083644.gif



【管理系统】Spring Security OAuth2 验证码模式

微信图片_20230710083646.gif

Spring Security OAuth2 默认实现的四种授权模式在实际的应用场景中往往满足不了预期,如以下需求:


授权对象分多个用户体系,例如系统用户和会员用户;

在密码授权模式的基础上加个验证码校验;

基于 Spring Security OAuth2 实现手机和短信验证码登录;

基于 Spring Security OAuth2 实现微信小程序授权登录。

相信你会遇到但不仅限上面的场景,网上也有很多对 Spring Security OAuth2 授权模式扩展的相关文章,但多少有不全面和实现复杂的通病,一度会让你觉得 Spring Security OAuth2 很难, Spring 在实现核心功能基础上同时还提供了很多的扩展点,Spring Security OAuth2 亦是如此,相信这篇文章会帮助消除它很难的误解。


本篇将以实战为主,原理为辅的方式,本着全面、最少改动的原则去对 Spring Security OAuth2 授权模式的扩展,本篇涉及内容如下:


Spring Cloud Gateway 微服务网关WebFlux整合谷歌验证码 Kaptcha;

SpringBoot 整合阿里云SMS短信服务;

Spring Security OAuth2 认证授权模式底层源码分析;

Spring Security OAuth2 扩展验证码授权模式;

Spring Security OAuth2 扩展手机短信验证码授权模式;

Spring Security OAuth2 扩展微信授权模式;

Spring Security OAuth2 多用户体系刷新模式;

vue-element-admin 后台管理前端登录接入验证码授权模式;

uni-app 微信小程序登录接入微信授权模式;

uni-app H5、移动端手机验证码登录接入手机短信验证码授权模式。

🔊 先做个很重要的声明吧,本篇文章涉及所有的代码地址:


项目名称 码云(Gitee) GitHub

微服务后台 youlai-mall youlai-mall

管理前端 mall-admin-web mall-admin-web

微信小程序/H5/Android/IOS mall-app mall-app

因为涉及的内容很多,文章中做不到把所有的代码完全贴出来,但是放心源码全部在线的,同样文档也是


往期系列文章


微服务


Spring Cloud实战 | 第一篇:Windows搭建Nacos服务

Spring Cloud实战 | 第二篇:Spring Cloud整合Nacos实现注册中心

Spring Cloud实战 | 第三篇:Spring Cloud整合Nacos实现配置中心

Spring Cloud实战 | 第四篇:Spring Cloud整合Gateway实现API网关

Spring Cloud实战 | 第五篇:Spring Cloud整合OpenFeign实现微服务之间的调用

Spring Cloud实战 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT实现微服务统一认证授权

Spring Cloud实战 | 最七篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案

Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Vue前后端分离模式下无感知刷新实现JWT续期

Spring Cloud实战 | 最九篇:Spring Security OAuth2认证服务器统一认证自定义异常处理

Spring Cloud实战 | 第十篇 :Spring Cloud + Nacos整合Seata 1.4.1最新版本实现微服务架构中的分布式事务,进阶之路必须要迈过的槛

Spring Cloud实战 | 第十一篇 :Spring Cloud Gateway网关实现对RESTful接口权限和按钮权限细粒度控制

Spring Cloud实战 | 第十二篇:Sentinel+Nacos实现流控、熔断降级,赋能拥有降级功能的Feign新技能熔断,做到熔断降级双剑合璧

Spring Cloud实战 | 总结篇:Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权

中间件


SpringBoot 整合 Elastic Stack 最新版本(7.14.1)分布式日志解决方案,开源微服务全栈项目【有来商城】的日志落地实践

管理系统前端


vue-element-admin实战 | 第一篇: 移除mock接入微服务接口,搭建SpringCloud+Vue前后端分离管理平台

vue-element-admin实战 | 第二篇: 最小改动接入后台实现根据权限动态加载菜单

微信小程序


vue+uni-app商城实战 | 第一篇:从0到1快捷开发一个商城微信小程序,无缝接入Spring Cloud OAuth2实现一键授权登录

应用部署


Docker实战 | 第一篇:Linux 安装 Docker


Docker实战 | 第二篇:Docker部署nacos-server:1.4.0


Docker实战 | 第三篇:IDEA 集成 Docker 插件实现一键远程部署 SpringBoot 应用,无需三方依赖,开源微服务全栈有来商城线上部署方式


Docker实战 | 第四篇:Docker安装Nginx,实现基于vue-element-admin框架构建的项目线上部署


二. 验证码授权模式

1. 原理

验证码授权模式是在密码模式基础添加个验证码校验,如果你有 不管功夫怎样,能打赢你的就是好功夫 这样的心态完全可以使用过滤器实现,但如果想不开的话那就试下扩展吧。


因为是基于密码授权模式的扩展,就先了解密码授权模式的流程吧。因为其他几种授权模式和密码模式实现原理都是一样,弄明白密码授权模式之后其他授权模式包括如何去扩展都是轻车熟路。


密码模式流程: 根据请求参数 grant_type 的值 password 匹配到授权者 ResourceOwnerPasswordTokenGraner ,授权者委托给认证提供者管理器 ProviderManager,根据 token 类型匹配到提供者 DaoAuthenticationProvider, Provider 从数据库获取用户认证信息和客户端请求传值的用户信息进行认证密码判读,验证通过之后返回token给客户端。


下面密码授权模式时序图贴出关键类和方法,断点走几遍流程就应该知道流程。

微信图片_20230710083752.png



验证码授权模式时序图如下,仔细比对下和密码授权模式的区别。

微信图片_20230710083756.png



比较可知两者的区别基本就是授权者 Granter 的区别,后续的 Provider 获取用户认证信息和密码判断完全一致,具体新增的验证码模式授权者 CaptchaTokenGranter 和密码模式的授权者 ResourceOwnerPasswordTokenGraner 区别在于前者的 getOAuth2Authentication() 方法获取认证信息添加了校验验证码的逻辑,具体的代码实现在实战里交待。


2. 实战

验证码授权模式涉及Spring Security OAuth2扩展验证码授权模式、后台生成验证码和前端登录加入验证码三部分,涉及到前后端的东西,针对自己需要选择关注点即可。


2.1 验证码授权模式扩展

从原理得知只需重写 Granter 为其添加校验验证码的能力,所以复制密码模式的授权者 ResourceOwnerPasswordTokenGranter 然后重名为 CaptchaTokenGranter,稍加改动成为验证码模式的授权者。


CaptchaTokenGranter

/**

* 验证码授权模式 授权者

*

* @author xianrui

* @date 2021/9/25

*/

public class CaptchaTokenGranter extends AbstractTokenGranter {


   /**

    * 声明授权者 CaptchaTokenGranter 支持授权模式 captcha

    * 根据接口传值 grant_type = captcha 的值匹配到此授权者

    * 匹配逻辑详见下面的两个方法

    *

    * @see org.springframework.security.oauth2.provider.CompositeTokenGranter#grant(String, TokenRequest)

    * @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String, TokenRequest)

    */

   private static final String GRANT_TYPE = "captcha";

   private final AuthenticationManager authenticationManager;

   private StringRedisTemplate redisTemplate;


   public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,

                              OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,

                              StringRedisTemplate redisTemplate

   ) {

       super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);

       this.authenticationManager = authenticationManager;

       this.redisTemplate = redisTemplate;

   }


   @Override

   protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {


       Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());


       // 验证码校验逻辑

       String validateCode = parameters.get("validateCode");

       String uuid = parameters.get("uuid");


       Assert.isTrue(StrUtil.isNotBlank(validateCode), "验证码不能为空");

       String validateCodeKey = AuthConstants.VALIDATE_CODE_PREFIX + uuid;

     

       // 从缓存取出正确的验证码和用户输入的验证码比对

       String correctValidateCode = redisTemplate.opsForValue().get(validateCodeKey);

       if (!validateCode.equals(correctValidateCode)) {

           throw new BizException("验证码不正确");

       } else {

           redisTemplate.delete(validateCodeKey);

       }


       String username = parameters.get("username");

       String password = parameters.get("password");


       // 移除后续无用参数

       parameters.remove("password");

       parameters.remove("validateCode");

       parameters.remove("uuid");


       // 和密码模式一样的逻辑

       Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);

       ((AbstractAuthenticationToken) userAuth).setDetails(parameters);


       try {

           userAuth = this.authenticationManager.authenticate(userAuth);

       } catch (AccountStatusException var8) {

           throw new InvalidGrantException(var8.getMessage());

       } catch (BadCredentialsException var9) {

           throw new InvalidGrantException(var9.getMessage());

       }


       if (userAuth != null && userAuth.isAuthenticated()) {

           OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);

           return new OAuth2Authentication(storedOAuth2Request, userAuth);

       } else {

           throw new InvalidGrantException("Could not authenticate user: " + username);

       }

   }

}


上面相对密码模式的授权者做了两处改动,总结如下:


修改 GRANT_TYPE 的值 password 为 captcha;

getOAuth2Authentication() 方法添加验证码校验逻辑。

AuthorizationServerConfig

在 AuthorizationServerConfig 配置类重写 TokenGranter 让其支持新增的验证码模式授权者 CaptchaTokenGranter

微信图片_20230710083823.png



到此,Spring Security OAuth2 扩展验证码授权大功告成!!!


怎么样,简不简单?相信你有可能心存怀疑,那先做个测试吧。


管理前端的客户端ID是 mall-admin-web ,在测试之前,先赋予客户端支持验证码模式。

微信图片_20230710083843.png



在登录界面输入错误的验证码和正确的验证码各一次看下效果,是不是能达到预期的效果,还有验证码如何生成和前端如何传值放在后文说。

微信图片_20230710083846.gif



2.2 Spring WebFlux 整合验证码 Kaptcha

验证码生成的功能主要是生成一个随机码将其缓存redis,返回redis的key标识(一般是uuid)和随机码的图片给前端。因为没有任何业务逻辑,故这里直接放在网关,除了利用 WebFlux 性能优势之外还能减少一次转发。youlai-gateway 验证码相关代码结构图如下:


微信图片_20230710083904.png


CaptchaHandler

@Component

@RequiredArgsConstructor

public class CaptchaHandler implements HandlerFunction {


   private final Producer producer;

   private final StringRedisTemplate redisTemplate;


   @Override

   public Mono handle(ServerRequest serverRequest) {

       // 生成验证码

       String capText = producer.createText();

       String capStr = capText.substring(0, capText.lastIndexOf("@"));

       String code = capText.substring(capText.lastIndexOf("@") + 1);

       BufferedImage image = producer.createImage(capStr);

       // 缓存验证码至Redis

       String uuid = IdUtil.simpleUUID();

       redisTemplate.opsForValue().set(AuthConstants.VALIDATE_CODE_PREFIX + uuid, code, 60, TimeUnit.SECONDS);

       // 转换流信息写出

       FastByteArrayOutputStream os = new FastByteArrayOutputStream();

       try {

           ImageIO.write(image, "jpg", os);

       } catch (IOException e) {

           return Mono.error(e);

       }


       java.util.Map resultMap = new HashMap();

       resultMap.put("uuid", uuid);

       resultMap.put("img", Base64.encode(os.toByteArray()));


       return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(Result.success(resultMap)));

   }

}


微信图片_20230710084018.png

CaptchaConfig

属性 kaptcha.textproducer.impl 需要指定你自己项目文本生成器 KaptchaTextCreator 的类路径


// 验证码文本生成器

properties.setProperty("kaptcha.textproducer.impl", "com.youlai.gateway.kaptcha.KaptchaTextCreator");

1

2

CaptchaRouter

@Configuration

public class CaptchaRouter {


   @Bean

   public RouterFunction routeFunction(CaptchaHandler captchaHandler) {

       return RouterFunctions

               .route(RequestPredicates.GET("/captcha")

                       .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), captchaHandler::handle);

   }

}


验证码测试

修改 Nacos 网关配置文件 youlai-gateway.yaml 白名单添加请求路径 /captcha


访问 http://localhost:9999/captcha 如下:


微信图片_20230710084001.png


2.3 前端登录接入验证码模式

登录页面

登录表单添加验证码,完整代码地址:mall-admin-web


src/views/login/index.vue


   

     

   

 

    v-model="loginForm.validateCode"

    auto-complete="off"

    placeholder="请输入验证码"

    style="width: 65%"

    @keyup.enter.native="handleLogin"

  />

 


   

 


返回的图片是base64 加密后的字符串,所以添加前缀 data:image/gif;base64,


// 获取验证码

getValidateCode() {

 getCaptcha().then(response => {

const {img, uuid} = response.data

this.captchaUrl = "data:image/gif;base64," + img

this.loginForm.uuid = uuid;

 })

}


接口请求

src/store/modules/user.js 设置请求参数


login({commit}, userInfo) {

 const {username, password, validateCode, uuid} = userInfo

 return new Promise((resolve, reject) => {

   login({  

     username: username,

     password: password,

     grant_type: 'captcha', // 授权模式指定为 captcha 验证码模式,原先为 password 密码模式

     uuid: uuid, // 从Redis获取正确验证码的标识

     validateCode: validateCode // 验证码

   }).then(response => {

     const {access_token, refresh_token, token_type} = response.data

     const token = token_type + " " + access_token

     commit('SET_TOKEN', token)

     setToken(token)

     setRefreshToken(refresh_token)

     resolve()

   }).catch(error => {

     reject(error)

   })

 })



src/api/user.js 请求API设置请求头部


export function login(params) {

 return request({

   url: '/youlai-auth/oauth/token',

   method: 'post',

   params: params,

   headers: {

     'Authorization': 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2' // OAuth2客户端信息Base64加密,明文:mall-admin-web:123456

   }

 })

}


相关文章
|
11月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1484 3
|
10月前
|
SQL Java 数据库连接
Spring Data JPA 技术深度解析与应用指南
本文档全面介绍 Spring Data JPA 的核心概念、技术原理和实际应用。作为 Spring 生态系统中数据访问层的关键组件,Spring Data JPA 极大简化了 Java 持久层开发。本文将深入探讨其架构设计、核心接口、查询派生机制、事务管理以及与 Spring 框架的集成方式,并通过实际示例展示如何高效地使用这一技术。本文档约1500字,适合有一定 Spring 和 JPA 基础的开发者阅读。
903 0
|
9月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
789 8
|
10月前
|
监控 安全 Java
使用 @HealthEndpoint 在 Spring Boot 中实现自定义健康检查
Spring Boot 通过 Actuator 模块提供了强大的健康检查功能,帮助开发者快速了解应用程序的运行状态。默认健康检查可检测数据库连接、依赖服务、资源可用性等,但在实际应用中,业务需求和依赖关系各不相同,因此需要实现自定义健康检查来更精确地监控关键组件。本文介绍了如何使用 @HealthEndpoint 注解及实现 HealthIndicator 接口来扩展 Spring Boot 的健康检查功能,从而提升系统的可观测性与稳定性。
701 0
使用 @HealthEndpoint 在 Spring Boot 中实现自定义健康检查
|
11月前
|
Java 应用服务中间件 开发者
Spring Boot 技术详解与应用实践
本文档旨在全面介绍 Spring Boot 这一广泛应用于现代企业级应用开发的框架。内容将涵盖 Spring Boot 的核心概念、核心特性、项目自动生成与结构解析、基础功能实现(如 RESTful API、数据访问)、配置管理以及最终的构建与部署。通过本文档,读者将能够理解 Spring Boot 如何简化 Spring 应用的初始搭建和开发过程,并掌握其基本使用方法。
755 2
|
11月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
12月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1424 0
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1327 0
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
583 0
|
9月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
638 4

热门文章

最新文章