首页/新闻资讯/正文详情

Spring Authorization Server实现短信验证码登录的OAuth2扩展方案

发布时间:2026/11/16 6:20:54 来源:云帆数科 栏目:资讯中心
Spring Authorization Server实现短信验证码登录的OAuth2扩展方案
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的标准流程又满足了业务特定的认证需求是传统密码模式的良好替代方案。

相关推荐

基于AI的Minecraft模组自动生成器前端实现方案
基于AI的Minecraft模组自动生成器前端实现方案

在实际游戏开发或模组制作过程中,很多开发者都希望有一个工具能快速生成 Minecraft 模组的基础代码、配置和资源文件。传统模组开发需要手动编写 Java 代码、配置 JSON 文件、处理资源路径和合成表,过程繁琐且容易出错。借助 AI 技术,我们可以… · 2026/10/28 11:41:19

VLA模型:视觉-语言-行动统一的具身智能技术解析
VLA模型:视觉-语言-行动统一的具身智能技术解析

1. 什么是VLA模型:从“看图说话”到“看见即行动”的范式跃迁如果你过去几年关注过AI领域,大概率听过CLIP、BLIP这类视觉-语言对齐模型——它们能告诉你一张图里有没有猫,甚至能生成“一只橘猫趴在窗台上晒太阳”的描述。但这类模型有个根本局… · 2026/11/2 16:23:51

WeChatExporter:Mac用户必备的微信聊天记录完整备份解决方案
WeChatExporter:Mac用户必备的微信聊天记录完整备份解决方案

WeChatExporter:Mac用户必备的微信聊天记录完整备份解决方案 【免费下载链接】WeChatExporter 一个可以快速导出、查看你的微信聊天记录的工具 项目地址: https://gitcode.com/gh_mirrors/wec/WeChatExporter 在数字时代,微信聊天记录承载着我们的… · 2026/11/7 1:05:30

白云报考电工证多少钱多久拿证 跨省办证全攻略
白云报考电工证多少钱多久拿证 跨省办证全攻略

白云报考电工证多少钱多久拿证 跨省办证全攻略 现在找工作动不动就卡在特种作业证上,特别是电工证,没有证根本进不了门。如果你在白云区或者打算从鹤壁跨省办理电工证, 白云报考电工证多少钱 这个问题肯定绕不开,而且 多久拿证 更是你最关心的点。… · 2026/11/16 6:20:47

鹿邑电工证复审保姆级教程:工地太忙根本没时间复习考试?
鹿邑电工证复审保姆级教程:工地太忙根本没时间复习考试?

鹿邑电工证复审保姆级教程:工地太忙根本没时间复习考试? 工地太忙根本没时间复习考试,这几乎是所有电工朋友的心声。尤其是在鹿邑这样的建筑工地密集区域,电工证的复审不仅是法规要求,更是保障自身和他人安全的重要一环。很多人误以为复审就是走个流程,… · 2026/11/16 6:20:47

家庭电工作业总结证怎么转异地 你在外省考的证全国通用吗
家庭电工作业总结证怎么转异地 你在外省考的证全国通用吗

家庭电工作业总结证怎么转异地 你在外省考的证全国通用吗 之前考的证在外省能不能转过来?这几乎是每个在外务工的特种作业人员都问过的问题。特别是像你这样在信阳浉河区从事房建工程的朋友,如果之前是在老家或者其他省份考的 家庭电工作业总结证… · 2026/11/16 6:20:47

在吴川哪考电工证最快真实经历告诉你怎么选培训机构
在吴川哪考电工证最快真实经历告诉你怎么选培训机构

在吴川哪考电工证最快真实经历告诉你怎么选培训机构 工地太忙根本没时间复习考试,这是多少一线工人的真实写照。作为在吴川从事电工工作的我,去年为了考取低压电工证,几乎每天都在工地和家之间来回奔波,晚上只能靠手机刷题。直到遇见一家靠谱的培训机构,… · 2026/11/16 6:20:47

电工作业证新版官方回应:工地太忙根本没时间复习考试?一文说清
电工作业证新版官方回应:工地太忙根本没时间复习考试?一文说清

电工作业证新版官方回应:工地太忙根本没时间复习考试?一文说清 工地太忙根本没时间复习考试,这几乎是所有一线电工的共同烦恼,尤其是焦作地区的应届毕业生,刚刚踏入工地,就被各种操作流程和证书要求压得喘不过气。别急,官方回应已经明确,… · 2026/11/16 6:20:47

考电工证包安排工作吗官方回应来了,外省证能不能转过来看这里
考电工证包安排工作吗官方回应来了,外省证能不能转过来看这里

考电工证包安排工作吗官方回应来了,外省证能不能转过来看这里 之前考的证在外省能不能转过来?这个问题几乎每个考电工证的人都问过,特别是那些在外地打工、换过几次城市的朋友们。官方回应已经明确, 特种作业操作证全国通用… · 2026/11/16 6:19:47

长春初级电工证怎么考?通过率揭秘全在这里
长春初级电工证怎么考?通过率揭秘全在这里

长春初级电工证怎么考?通过率揭秘全在这里 之前考的证在外省能不能转过来?这几乎是每个想从事电工工作的人都会问的问题。特别是那些在外地打拼多年、手里握着一张“外地电工证”的人,到了长春想继续干这一行,第一反应就是“我的证能用吗?”。今天我们就… · 2026/11/16 3:21:55

国内电工证报考条件与考试时间
国内电工证报考条件与考试时间

低压电工证怎么考?学历不高也能报名,官方报名入口全在这 你是不是经常听说“电工证是入行门槛”,但又担心自己学历不够、经验不足,根本报不了名?别急,今天就带你从零了解【国内电工证】的报考流程、考试内容,还有怎么在 官方报名入口… · 2026/11/16 3:01:18

电工作业时如何断电?电子证书到底能不能全国通用?
电工作业时如何断电?电子证书到底能不能全国通用?

电工作业时如何断电?电子证书到底能不能全国通用? 实操考试心里没底怕挂科,特别是电工作业时如何断电这一操作,不仅关系到你能不能拿到证书,更关系到以后能不能顺利上岗。很多人觉得这不过就是“断个电”,但其实这里面的门道可不少,稍有不慎就可能出错… · 2026/11/15 0:00:50

电工证培训的通知在哪考?别再被中介骗了
电工证培训的通知在哪考?别再被中介骗了

电工证培训的通知在哪考?别再被中介骗了 你是不是还在为 电工证培训的通知在哪考 发愁?网上搜了一堆信息,结果要么是挂羊头卖狗肉的培训机构,要么就是打着“包过”旗号的骗子?别急,今天我来给你讲讲, 电工证培训的通知… · 2026/11/16 0:00:34

高级电工证企业认可吗保姆级教程:实操考试心里没底怕挂科?看完这些建议再考不迟
高级电工证企业认可吗保姆级教程:实操考试心里没底怕挂科?看完这些建议再考不迟

高级电工证企业认可吗保姆级教程:实操考试心里没底怕挂科?看完这些建议再考不迟 你是不是也这样,站在高压电工实操考试的设备前,手心冒汗,生怕一个操作失误就被判不合格?实操考试心里没底怕挂科,这是很多准备考高级电工证的人的真实写照。别担心,今天… · 2026/11/16 0:00:34

电工证去哪个部门考核值不值得考 信阳转岗人真实经历分享
电工证去哪个部门考核值不值得考 信阳转岗人真实经历分享

电工证去哪个部门考核值不值得考 信阳转岗人真实经历分享 怕考不过白交培训费?你不是一个人,信阳的李师傅去年刚转岗做电工,就因为没搞清楚电工证去哪个部门考核,差点交了8000块的培训费。别急,下面这份实操考试全攻略,告诉你电工证到底值不值得考… · 2026/11/16 0:00:34

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 1:51:20

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 4:02:19

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 3:02:05

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码