1. Spring Authorization Server自定义grant_type实现短信认证登录在OAuth2.1协议中密码模式已被废除但很多老系统仍依赖这种认证方式。Spring Authorization Server提供了扩展机制让我们可以自定义grant_type来实现短信验证码登录。这种方案既兼容了老系统需求又符合现代认证安全规范。1.1 核心实现原理自定义grant_type需要实现三个核心组件AuthenticationToken继承AbstractAuthenticationToken封装认证请求参数AuthenticationConverter将HTTP请求转换为认证TokenAuthenticationProvider执行实际的认证逻辑这种设计遵循了Spring Security的认证流程HTTP请求 → Converter生成Token → Provider执行认证 → 返回AccessToken2. 详细实现步骤2.1 定义安全常量首先需要定义短信认证相关的常量public class SecurityConstants { // 短信验证登录类型 public static final String SMS_LOGIN_TYPE smsCaptcha; // 自定义grant_type public static final String GRANT_TYPE_SMS_CODE urn:ietf:params:oauth:grant-type:sms_code; // 请求参数名 public static final String OAUTH_PARAMETER_NAME_PHONE phone; public static final String OAUTH_PARAMETER_NAME_SMS_CAPTCHA sms_captcha; }2.2 实现SmsCaptchaGrantAuthenticationToken继承AbstractAuthenticationToken封装认证信息public class SmsCaptchaGrantAuthenticationToken extends AbstractAuthenticationToken { private final SetString scopes; private final Authentication clientPrincipal; private final MapString, Object additionalParameters; private final AuthorizationGrantType authorizationGrantType; // 构造方法 public SmsCaptchaGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType, Authentication clientPrincipal, SetString scopes, MapString, Object additionalParameters) { super(Collections.emptyList()); this.scopes scopes; this.clientPrincipal clientPrincipal; this.additionalParameters additionalParameters; this.authorizationGrantType authorizationGrantType; } // 实现抽象方法 Override public Object getCredentials() { return null; } Override public Object getPrincipal() { return clientPrincipal; } // Getter方法 public SetString getScopes() { return this.scopes; } public AuthorizationGrantType getAuthorizationGrantType() { return this.authorizationGrantType; } public MapString, Object getAdditionalParameters() { return this.additionalParameters; } }2.3 实现AuthenticationConverter将HTTP请求转换为认证Tokenpublic class SmsCaptchaGrantAuthenticationConverter implements AuthenticationConverter { Override public Authentication convert(HttpServletRequest request) { // 校验grant_type String grantType request.getParameter(OAuth2ParameterNames.GRANT_TYPE); if (!SecurityConstants.GRANT_TYPE_SMS_CODE.equals(grantType)) { return null; } // 获取请求参数 MultiValueMapString, String parameters SecurityUtils.getParameters(request); // 校验scope String scope parameters.getFirst(OAuth2ParameterNames.SCOPE); SetString requestedScopes null; if (StringUtils.hasText(scope)) { requestedScopes new HashSet(Arrays.asList(scope.split( ))); } // 校验手机号和验证码参数 String phone parameters.getFirst(SecurityConstants.OAUTH_PARAMETER_NAME_PHONE); String smsCaptcha parameters.getFirst(SecurityConstants.OAUTH_PARAMETER_NAME_SMS_CAPTCHA); if (!StringUtils.hasText(phone) || !StringUtils.hasText(smsCaptcha)) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, Missing required parameters, null); } // 构建认证Token MapString, Object additionalParameters new HashMap(); additionalParameters.put(SecurityConstants.OAUTH_PARAMETER_NAME_PHONE, phone); additionalParameters.put(SecurityConstants.OAUTH_PARAMETER_NAME_SMS_CAPTCHA, smsCaptcha); return new SmsCaptchaGrantAuthenticationToken( new AuthorizationGrantType(SecurityConstants.GRANT_TYPE_SMS_CODE), SecurityContextHolder.getContext().getAuthentication(), requestedScopes, additionalParameters ); } }2.4 实现AuthenticationProvider执行实际的认证逻辑Slf4j public class SmsCaptchaGrantAuthenticationProvider implements AuthenticationProvider { private OAuth2TokenGenerator? tokenGenerator; private AuthenticationManager authenticationManager; private OAuth2AuthorizationService authorizationService; Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { SmsCaptchaGrantAuthenticationToken authToken (SmsCaptchaGrantAuthenticationToken) authentication; // 校验客户端 OAuth2ClientAuthenticationToken clientPrincipal SecurityUtils.getAuthenticatedClientElseThrowInvalidClient(authToken); RegisteredClient registeredClient clientPrincipal.getRegisteredClient(); // 校验scope SetString authorizedScopes getAuthorizedScopes(registeredClient, authToken.getScopes()); // 执行用户认证 Authentication userAuth authenticateUser(authToken); // 构建Token上下文 DefaultOAuth2TokenContext.Builder tokenContextBuilder DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(userAuth) .authorizedScopes(authorizedScopes) .authorizationGrantType(authToken.getAuthorizationGrantType()); // 生成AccessToken OAuth2TokenContext tokenContext tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); OAuth2Token generatedAccessToken this.tokenGenerator.generate(tokenContext); // 生成RefreshToken(如果支持) OAuth2RefreshToken refreshToken generateRefreshTokenIfSupported(registeredClient, clientPrincipal, tokenContextBuilder); // 生成ID Token(如果请求了openid scope) OidcIdToken idToken generateIdTokenIfRequested(authorizedScopes, tokenContextBuilder); // 保存授权信息 OAuth2Authorization authorization buildAuthorization(registeredClient, userAuth, authorizedScopes, authToken, generatedAccessToken, refreshToken, idToken); this.authorizationService.save(authorization); // 返回认证结果 return new OAuth2AccessTokenAuthenticationToken( registeredClient, clientPrincipal, (OAuth2AccessToken)generatedAccessToken, refreshToken, idToken ! null ? Map.of(OidcParameterNames.ID_TOKEN, idToken.getTokenValue()) : Map.of() ); } // 其他辅助方法... }3. 配置授权服务器最后需要在授权服务器配置中添加我们的自定义grant_typeBean public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); // 创建自定义Converter和Provider SmsCaptchaGrantAuthenticationConverter converter new SmsCaptchaGrantAuthenticationConverter(); SmsCaptchaGrantAuthenticationProvider provider new SmsCaptchaGrantAuthenticationProvider(); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .tokenEndpoint(tokenEndpoint - tokenEndpoint .accessTokenRequestConverter(converter) .authenticationProvider(provider)); // 获取并设置Provider所需的依赖 DefaultSecurityFilterChain build http.build(); provider.setTokenGenerator(http.getSharedObject(OAuth2TokenGenerator.class)); provider.setAuthorizationService(http.getSharedObject(OAuth2AuthorizationService.class)); provider.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); return build; }4. 短信验证码认证实现在短信验证码认证Provider中我们需要验证手机号和验证码Component public class SmsCaptchaLoginAuthenticationProvider extends DaoAuthenticationProvider { Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { // 获取当前请求 HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 获取登录类型 String loginType request.getParameter(loginType); String grantType request.getParameter(grant_type); // 如果是短信登录或自定义grant_type if (Objects.equals(loginType, SecurityConstants.SMS_LOGIN_TYPE) || Objects.equals(grantType, SecurityConstants.GRANT_TYPE_SMS_CODE)) { // 验证短信验证码 String sessionCaptcha (String) request.getSession(false) .getAttribute((String) authentication.getPrincipal()); if (!Objects.equals(sessionCaptcha, authentication.getCredentials())) { throw new BadCredentialsException(短信验证码错误); } } else { // 其他情况走默认密码验证 super.additionalAuthenticationChecks(userDetails, authentication); } } }5. 测试验证可以使用Postman测试自定义grant_type首先获取短信验证码GET /getSmsCaptcha?phone13800138000然后使用自定义grant_type获取tokenPOST /oauth2/token Content-Type: application/x-www-form-urlencoded Authorization: Basic base64(clientId:clientSecret) grant_typeurn:ietf:params:oauth:grant-type:sms_code phone13800138000 sms_captcha123456 scopemessage.read6. 注意事项与最佳实践验证码存储实际项目中验证码应该存储在Redis等缓存中并设置有效期而不是Session防刷机制需要对短信接口做限流防止恶意刷短信安全加固验证码建议使用6位以上随机数同一验证码最多验证3次失败后失效每日单个手机号发送上限建议设置为10次性能考虑短信服务建议异步化处理验证码校验接口需要做缓存优化日志记录记录短信发送和验证日志对异常登录尝试进行监控这种自定义grant_type方案既保持了OAuth2的标准流程又满足了业务特定的认证需求是传统密码模式的良好替代方案。
企业数字化 ERP 产品动态
相关推荐
基于AI的Minecraft模组自动生成器前端实现方案 在实际游戏开发或模组制作过程中,很多开发者都希望有一个工具能快速生成 Minecraft 模组的基础代码、配置和资源文件。传统模组开发需要手动编写 Java 代码、配置 JSON 文件、处理资源路径和合成表,过程繁琐且容易出错。借助 AI 技术,我们可以… · 2026/9/13 18:47:56
VLA模型:视觉-语言-行动统一的具身智能技术解析 1. 什么是VLA模型:从“看图说话”到“看见即行动”的范式跃迁如果你过去几年关注过AI领域,大概率听过CLIP、BLIP这类视觉-语言对齐模型——它们能告诉你一张图里有没有猫,甚至能生成“一只橘猫趴在窗台上晒太阳”的描述。但这类模型有个根本局… · 2026/9/17 11:39:02
WeChatExporter:Mac用户必备的微信聊天记录完整备份解决方案 WeChatExporter:Mac用户必备的微信聊天记录完整备份解决方案 【免费下载链接】WeChatExporter 一个可以快速导出、查看你的微信聊天记录的工具 项目地址: https://gitcode.com/gh_mirrors/wec/WeChatExporter
在数字时代,微信聊天记录承载着我们的… · 2026/8/13 19:07:12
告别apt-get随缘版本:手动安装并管理gcc-arm-none-eabi交叉编译工具链 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 6:45:49
ESP32 上跑 WebAssembly:WAMR 运行时原理与实操指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 6:44:59
GEE实战01期:制作北京及周边地区夏季 NDVI 分布图 本期关注:借助ee.ImangCollection完成影像集合与时空筛选。
核心流程:在不下载数据的背景下,在浏览器上完成影像调用,NDVI合成,地图显示,简单分析以及数据导出的过程。
1.案例背景
归一化植被指数&#… · 2026/9/24 6:44:28
创维E900-S变砖救砖指南:海思3798MV100短接强刷实测 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 6:44:22
力扣集训day05 思路主要是结合归并排序的思路进行解答,大致就是1.先二分拆分(merge()),拆到拆无可拆,也就是左右边界重合为止,至于l>r这种情况,是用来判断空链表这种特殊情况的。2.然… · 2026/9/24 6:43:51
微信小程序 checkbox 和 radio 组件案例学习 ## 一、实验介绍本次案例学习微信小程序中 checkbox 复选框组件与 radio 单选框组件,实现对文本样式和字体大小的动态控制。复选框支持多选,可以同时设置文字加粗、倾斜、下划线;单选框只能选择一项,用来切换诗词的字体尺寸。本次… · 2026/9/24 6:43:45
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44