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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 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

   }

 })

}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8天前
|
前端开发 Java 数据库连接
Spring MVC 扩展和SSM框架整合
通过以上步骤,我们可以将Spring MVC扩展并整合到SSM框架中。这个过程包括配置Spring MVC和Spring的核心配置文件,创建控制器、服务层和MyBatis的Mapper接口及映射文件。在实际开发中,可以根据具体业务需求进行进一步的扩展和优化,以构建更加灵活和高效的企业级应用程序。
20 5
|
15天前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建微服务架构
本文深入探讨了 Python 中的微服务架构,介绍了 Flask、FastAPI 和 Nameko 三个常用框架,并通过实战项目帮助读者掌握这些技术。每个框架都提供了构建微服务的示例代码,包括简单的 API 接口实现。通过学习本文,读者将能够使用 Python 构建高效、独立的微服务。
|
19天前
|
Java 应用服务中间件 Scala
Spring Boot 实现通用 Auth 认证的 4 种方式
本文介绍了在Spring Boot中实现通用Auth的四种方式:传统AOP、拦截器(Interceptor)、参数解析器(ArgumentResolver)和过滤器(Filter)。每种方式都通过实例代码详细说明了实现步骤,并总结了它们的执行顺序。首先,Filter作为Servlet规范的一部分最先被调用;接着是Interceptor,它可以在Controller方法执行前后进行处理;然后是ArgumentResolver,在参数传递给Controller之前解析并验证参数
|
3月前
|
安全 Java 数据安全/隐私保护
基于内存认证的 Spring Security
通过本文的介绍,希望您能够深入理解基于内存认证的Spring Security配置与使用方法,并能够在实际开发中灵活应用这一技术,提升应用的安全性和用户体验。
81 9
|
3月前
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
4月前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
125 8
|
4月前
|
JavaScript Java 关系型数据库
Spring事务失效的8种场景
本文总结了使用 @Transactional 注解时事务可能失效的几种情况,包括数据库引擎不支持事务、类未被 Spring 管理、方法非 public、自身调用、未配置事务管理器、设置为不支持事务、异常未抛出及异常类型不匹配等。针对这些情况,文章提供了相应的解决建议,帮助开发者排查和解决事务不生效的问题。
169 1
|
3月前
|
存储 监控 供应链
微服务拆分的 “坑”:实战复盘与避坑指南
本文回顾了从2~3人初创团队到百人技术团队的成长历程,重点讨论了从传统JSP到前后端分离+SpringCloud微服务架构的演变。通过实际案例,总结了微服务拆分过程中常见的两个问题:服务拆分边界不清晰和拆分粒度过细,并提出了优化方案,将11个微服务优化为6个,提高了系统的可维护性和扩展性。
83 0
|
4月前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
150 4
|
4天前
|
缓存 NoSQL Java
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
108 79

热门文章

最新文章