springboot 使用shiro集成阿里云短信验证码

article/2025/8/19 18:09:39

目录

1.阿里云短信验证码服务

2.发送短信验证码

3.shiro配置多个realm

4.验证短信验证码

5.一些修改思路


引言:短信验证码是通过发送验证码到手机的一种有效的验证码系统,主要用于验证用户手机的合法性及敏感操作的身份验证。在注册和修改密码时需要用到短信验证码校验手机号的功能。本文主要集成阿里云的短信验证码功能,进行功能实现。

1.阿里云短信验证码服务

首先,我们需要登录阿里云官网,进入控制台搜索短信验证码(sms)服务,进行开通。

然后我们需要开通ACCESS KEY,进行api调用时需要进行填写,来验证用户的身份。

然后我们需要进入访问控制,建立用户组及用户,并分配权限SMS给他:

然后,我们可以进入短信服务,遵循的推荐顺序,进行具体功能的开通:

首先,申请签名,然后是申请短信发送的模板,然后需要记住以上内容,方便在api中进行填写。

2.发送短信验证码

完成上述步骤后,我们就可以开始代码的填写。

首先,在springboot项目中引入依赖:

        <!-- 阿里云服务依赖 --><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.3.3</version></dependency>

然后,编写业务类代码:

@Service
public class AuthServiceImpl extends ServiceImpl<AuthDao, UserAccount> implements AuthService {@Overridepublic boolean sendMessage(String phoneNum, Map<String, Object> code) {if(StringUtils.isEmpty(phoneNum)) {return false;}/**创建阿里云连接* @Param regionld 默认为default* @Param accessKeyId 你的accessKey id* @Param secret 你的accessKey秘钥*/DefaultProfile profile = DefaultProfile.getProfile("default","accessKeyId", "secret");IAcsClient client = new DefaultAcsClient(profile);//发送请求CommonRequest request = new CommonRequest();//request.setProtocol(ProtocolType.HTTPS);request.setMethod(MethodType.POST);request.setDomain("dysmsapi.aliyuncs.com");request.setVersion("2017-05-25");request.setAction("SendSms");request.putQueryParameter("PhoneNumbers", phoneNum);           //发送的手机号对象request.putQueryParameter("SignName", "阿里云短信测试");    //申请的签名名称request.putQueryParameter("TemplateCode", "SMS_154950909"); //申请的短信模板request.putQueryParameter("TemplateParam", JSONObject.toJSONString(code));    //验证码try {CommonResponse response = client.getCommonResponse(request);System.out.println(response.getData());return response.getHttpResponse().isSuccess();} catch (Exception e) {e.printStackTrace();}return false;}
}

上述代码根据阿里云官网的示例代码进行修改,进行手机验证码的发送。

编写controller代码:

//利用阿里云发送短信验证码@GetMapping("/send/{phone}")public CommonResult<Object> codeSend(@PathVariable("phone") String phone){String code = redisTemplate.opsForValue().get(phone);if(!StringUtils.isEmpty(code)){log.info("验证码存在,未过期");}//不存在,生成验证码code = UUID.randomUUID().toString().substring(0,4);HashMap<String,Object> map = new HashMap<>();map.put("code",code);//发送验证码boolean isSend = authService.sendMessage(phone,map);if(isSend){//存储验证码redisTemplate.opsForValue().set(phone,code, 300,TimeUnit.SECONDS);return CommonResult.success(null);}else {//失败return CommonResult.fail(null);}}

上述代码中,若发送成功,使用redis存储验证码(存储时间上述代码为5分钟,具体可以自己设定),方便后续的检验。

3.shiro配置多个realm

在手机验证码发送成功后,我们还需接收验证码进行登录验证,下面我们使用shiro集成多个realm实现。分开两个接口,一个是正常的用户名密码登录,可参考springboot集成shiro实现登录验证;另一个使用手机和验证码登录,如果手机已注册,将账号信息存入jwt token中,如果没有注册,生成临时账号,将信息存入jwt token。

下面是多个realm的实现:

(1)编写LoginToken,继承原始的UsernamePasswordToken,添加需要获取的参数及认证参数:

public class LoginToken extends UsernamePasswordToken {//手机号private String phone;//定义登陆的类型是为了在后面的校验中 去选择使用哪一个realmprivate String loginType;public LoginToken(String phone, String loginType){this.phone=phone;this.loginType=loginType;}//认证规则,与realm中的认证对应@Overridepublic Object getCredentials() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getPhone() {return phone;}public void setLoginType(String loginType) {this.loginType = loginType;}public String getLoginType() {return loginType;}
}

(2)编写多个realm的管理类,根据loginType指定使用的realm:

public class LoginTypeModularRealmAuthenticator extends ModularRealmAuthenticator {//就是通过传入数据的类型  来选择使用哪一个Realm@Overrideprotected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {//做Realm的一个校验assertRealmsConfigured();//获取前端传递过来的tokenLoginToken loginToken =(LoginToken)authenticationToken;//现在就可以获取这个登陆的类型了String loginType = loginToken.getLoginType();  //  登陆类型   1:User   2:Phone//获取所有的realms()Collection<Realm> realms = getRealms();//登陆类型对应的所有realm全部获取到Collection<Realm> typeRealms=new ArrayList<>();for (Realm realm:realms){//realm类型和现在登陆的类型做一个对比//注意loginType中的值需要和realm中的名称对应,如PhoneRealm对应的loginType为phoneif(realm.getName().contains(loginType)){   //就能分开这两个realmtypeRealms.add(realm);}}if(typeRealms.size()==1){return doSingleRealmAuthentication(typeRealms.iterator().next(), loginToken);}else{return doMultiRealmAuthentication(typeRealms, loginToken);}}
}

(3)编写shiro配置类,将bean注入,部分代码如下:

    //@Qualifier("userRealm") UserRealm userRealm@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(Collection<Realm> realms) {DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager();SecurityManager.setRealms(realms);return SecurityManager;}//该bean为username和password验证realm@Beanpublic UserRealm userRealm() {UserRealm userRealm = new UserRealm();//注册MD5加密userRealm.setCredentialsMatcher(hashedCredentialsMatcher());return userRealm;}//该bean为phone验证realm@Beanpublic PhoneRealm phoneRealm() {return new PhoneRealm();}/*** 系统自带的Realm管理,主要针对多realm*/@Beanpublic ModularRealmAuthenticator modularRealmAuthenticator() {//自己重写的ModularRealmAuthenticatorLoginTypeModularRealmAuthenticator modularRealmAuthenticator = new LoginTypeModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());return modularRealmAuthenticator;}

(4)编写PhoneRealm:

@Slf4j
public class PhoneRealm extends AuthorizingRealm {//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {LoginToken token = (LoginToken) authenticationToken;//用户名/密码认证String phone = token.getPhone();//参数2为token中定义的getCredentials()return  new SimpleAuthenticationInfo(phone,phone,getName());}
}

上述代码只是一个手机登录的简单实现,如果需要,可在 doGetAuthenticationInfo 方法中进行扩展。

4.验证短信验证码

完成上述代码后,我们即可对登录的手机验证码进行验证登录。

首先,封装一个手机登录的dto,分别对于username登录和phone登录:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginPhone implements Serializable {private static final long serialVersionUID = -5331320733431220933L;@NotBlank(message = "手机号不能为空")   // 非空,message为错误的提示信息private String phone;@NotBlank(message = "验证码不能为空")   // 非空@CodePatten // 密码自定义校验,密码必须含数字、大写字母、小写字母、特殊字符private String code;@NotBlank(message = "登录类型不能为空")   // 非空,message为错误的提示信息private String loginType;
}

 上述代码使用了自定义注解@CodePatten进行参数的简单校验,如果不熟悉,可以删去,不影响使用;下面是自定义校验的代码:

@Documented
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(CodePatten.List.class)
@Constraint(validatedBy = {CodePattenValidator.class})
public @interface CodePatten {//校验失败返回信息String message() default "验证码长度错误";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Documentedpublic @interface List {CodePatten[] value();}
}
import com.seven.springcloud.annotation.CodePatten;
import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class CodePattenValidator implements ConstraintValidator<CodePatten,String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {if (StringUtils.isBlank(value)) {return false;}return validateCode(value);}private boolean validateCode(String code) {return code.length() == 4;}
}

 然后在控制类中编写验证短信的代码:

@PostMapping("/login/phone")public CommonResult<Object> loginByPhoneNum(@RequestBody @Validated LoginPhone user){String phone=user.getPhone();String code=user.getCode();String loginType=user.getLoginType();if(!"phone".equals(loginType)){return new CommonResult<>(NormalResultEnum.SYSTEM_FAIL.getCode(), NormalResultEnum.SYSTEM_FAIL.getMessage());}if(!code.equals(redisTemplate.opsForValue().get(phone))){return new CommonResult<>(NormalResultEnum.FAIL.getCode(), "验证码错误");}//shiro验证Subject subject= SecurityUtils.getSubject();LoginToken token = new LoginToken(phone,loginType);try {subject.login(token);} catch (AuthenticationException e) {log.warn("用户登录异常:" + e.getMessage());return CommonResult.system_fail(null);}Map<String, Object> tokenMap = new HashMap<>();UserAccount account = authService.getOne(new QueryWrapper<UserAccount>().eq("mobile",phone));if(account!=null){// 手机号已注册tokenMap = jwtTokenUtil.generateTokenAndRefreshToken(String.valueOf(account.getId()), account.getUsername(), //用户角色映射表中中查询用户角色rolesService.getOne(new QueryWrapper<AccountRoles>().eq("username",account.getUsername())).getRoles());}else{// 手机号未注册,赋予user权限tokenMap = jwtTokenUtil.generateTokenAndRefreshToken(UUID.randomUUID().toString().substring(0,8), phone,"user");}return CommonResult.success(tokenMap);}

上述代码逻辑如下:首先判断登录类型是否为手机登录,否则不可调用该接口;然后根据手机号到redis中取对应的验证码,如果取不到或是不相同,则验证失败;然后使用shiro进行手机号的登录验证;登录成功后,将登录账号的信息存入token中,返回前端。

(此处的用户权限管理使用RBAC模型实现,创建了单独的用户权限表,若不熟悉,可以将用户权限和用户账号存入同一张表,直接取出即可)

至此,手机验证码代码实现结束,进行验证:

发送验证码:

使用手机号登录:

登录成功,返回jwt token;

查看jwt token中值,可以查得,该手机号未注册,为临时账号:

 验证码错误,则登录失败。

5.一些修改思路

代码中,用户名登录和手机登录可封装到同一个接口中,但LoginToken需要进行一些修改:

    public LoginToken(String username, String password, String loginType){super(username, password);this.loginType=loginType;}//认证规则,与realm中的认证对应@Overridepublic Object getCredentials() {if(phone==null){return getPassword();}return phone;}

controller中,根据业务需求,添加如下代码即可:

        //shiro验证Subject subject= SecurityUtils.getSubject();LoginToken token = new LoginToken(username,password,loginType);

同时,最好是封装好一个同时包含phoen和username等信息的dto,方便进行参数的读取。


http://chatgpt.dhexx.cn/article/AZE5TBVN.shtml

相关文章

thinkphp6 +阿里云短信验证码

一、登录阿里云开通短信验证码 1、正常添加&#xff0c;等待审核成功&#xff0c;获取自己的账号的access_key 以及access_secret 进到下边界面创建就行&#xff0c;保存好 二、下载阿里云SDK包 composer require alibabacloud/sdk 三、thinkphp6 基本操作 1、三个文件 第一…

阿里云短信验证码发送

1&#xff0c;下载sdk composer require alibabacloud/dysmsapi-20170525 2.0.9 2&#xff0c;封装短信发送类 <?phpnamespace App\Http\business; use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi; use Darabonba\OpenApi\Models\Config; use AlibabaCloud\SDK\Dysm…

JAVA实现阿里云短信验证码发送

详情可参考阿里云官方文档&#xff1a; 开始使用 - 阿里云SDK - 阿里云 首先进入阿里云开通短信服务 并且申请阿里云签名和模板 redis安装详见 centos redis安装及相关命令_lanan_dream的博客-CSDN博客_centos redis 安装 1、配置类中引入redis配置&#xff1a;固定写法&#…

新版阿里云发送短信验证码详解

一、申请短信服务步骤 首先进入阿里云控制台&#xff0c;点击左侧产品与服务&#xff0c;找到短信服务。 第一次进入会有新手引导&#xff0c;这里主要介绍一下大致步骤和易错点 申请短信签名和模板 短信签名一般是企业或者组织名的简写&#xff0c;主要是告知收信人自己是谁…

使用阿里云发送短信验证码

目录 一、用户注册登录流程 二、使用阿里云发送短信验证码 ⭐生成密钥 ⭐将密钥配置到项目中 ⚪新建properties文件并填入相应信息 ⚪pom配置 ​⚪ 测试代码 ⚪添加签名 ​⚪添加短信发送的模板 &#x1f4a7;验证码防盗刷监控 一、用户注册登录流程 二、使用阿里…

通用阿里云的短信验证码(详细)

阿里云手机短信验证码 第一步 登录阿里云开放平台 1、进入阿里云开放平台---->点击控制台 2、点击AccessKey管理 3、点击之后会弹出提示&#xff0c;选择开始使用子用户 4、新建一个用户组&#xff0c;然后按要求填写即可 5、创建一个用户&#xff0c;按要求填写内容&…

《设计模式系列》- 代理模式

有情怀&#xff0c;有干货&#xff0c;微信搜索【三太子敖丙】关注这个有一点点东西的程序员。 本文 GitHub https://github.com/JavaFamily 已收录&#xff0c;有一线大厂面试完整考点、资料以及我的系列文章。 设计模式已经跟大家分享很多了常见的模式了&#xff0c;感兴趣的…

Nginx代理tomcat

为什么需要为tomcat配置nginx反向代理&#xff1f; 1.当服务器上同时拥有nginx与tomcat时&#xff0c;tomcat修改8080端口为80会冲突 2.tomcat不更改监听端口8080即可使用nginx的80端口 3.Nginx对于静态的请求速度上要优于Tomcat&#xff0c;Tomcat不擅长做高并发的静态文件请…

GPU虚拟化

GPU 虚拟化技术 须知: 文章内容大程度参考B站王利明老师对《GPU虚拟化技术分享》的主题演讲 视频链接: https://b23.tv/uQKBpcK 1 GPU 和软件架构 GPU可以用于图形渲染&#xff0c;GPU 作为加速图形绘制的芯片时&#xff0c;它主要面向的产品主要是会集中在 PC 和游戏两个市场…

代理模式(四):代理模式效果与适用场景

15.7 代理模式效果与适用场景 代理模式是常用的结构型设计模式之一&#xff0c;它为对象的间接访问提供了一个解决方案&#xff0c;可以对对象的访问进行控制。代理模式类型较多&#xff0c;其中远程代理、虚拟代理、保护代理等在软件开发中应用非常广泛。 15.7.1 模式优点 代理…

Nginx 反向代理、负载均衡、虚拟主机

文章目录 一、反向代理1、代理原理2、正/反向代理的区别&#xff08;1&#xff09;正向代理&#xff08;2&#xff09;反向代理 3、配置Nginx-Proxy&#xff08;1&#xff09;代理模块&#xff08;2&#xff09;代理配置&#xff08;3&#xff09; proxy 代理实例 二、负载均衡…

设计模式之代理模式

定义 代理模式又叫委托模式&#xff0c;是为某个对象提供一个代理对象&#xff0c;并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。 我们使用代理对象来代替对真实对象(real object)的访问&#xff0c;这样就可以在不修改原真实对象的前提下&a…

java设计模式9:Proxy(3)虚拟代理

加载延迟可以提高软件的友好程度。 当一个真实的主题对象的加载需要耗费资源时&#xff0c;一个虚拟代理对象可以代替真实对象接受请求。一旦接到请求&#xff0c;代理对象马上打出一段“正在加载”的信息&#xff0c;并在适当的时候加载真实主题对象&#xff0c;也就是模块或…

设计模式:(代理模式)

1.定义 在代理模式&#xff08;Proxy Pattern&#xff09;中&#xff0c;一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。 在代理模式中&#xff0c;我们创建具有现有对象的对象&#xff0c;以便向外界提供功能接口。 简单来说&#xff1a;代理模式为其他对象提…

Nginx 虚拟主机与反向代理

一、虚拟主机 虚拟主机使用的是特殊的软硬件技术&#xff0c;它把一台运行在因特网上的服务器主机分成一台台“虚拟”的主机&#xff0c;每台虚拟主机都可以是一个独立的网站&#xff0c;可以具有独立的域名&#xff0c;具有完整的Intemet服务器功能&#xff08;WWW、FTP、Emai…

【设计模式】学习笔记16:代理模式之虚拟代理(实现CD封面加载器)

本文出自 http://blog.csdn.net/shuangde800 在上篇中&#xff0c;我们学习了代理模式&#xff0c;并用Java RMI实现了一个最简单的远程代理。 实际上代理模式并不仅仅应用与远程代理&#xff0c;还有很多其他的应用。 比如&#xff1a;虚拟代理。 代理模式可以以很多形式出现…

代理模式(三):远程代理,虚拟代理,缓冲代理

15.4 远程代理 远程代理(Remote Proxy)是一种常用的代理模式&#xff0c;它使得客户端程序可以访问在远程主机上的对象&#xff0c;远程主机可能具有更好的计算性能与处理速度&#xff0c;可以快速响应并处理客户端的请求。远程代理可以将网络的细节隐藏起来&#xff0c;使得客…

虚拟机 全局代理 主机代理_比较虚拟代理与真实代理的性能

云计算的关键基础是虚拟化。 面向云的设计人员&#xff0c;开发人员和管理员需要问自己的一个问题是&#xff1a;“虚拟化组件的性能水平如何与其“真实”物理对应物相提并论&#xff1f;” “如果存在负面差距&#xff0c;我该如何克服呢&#xff1f;” 本文介绍了在虚拟机&a…

设计模式——代理模式(虚拟代理)

代理模式的类型分为: (1)虚拟代理 (2)远程代理 (3)智能指引 (4)保护代理 这一篇主要讲虚拟代理,想要知道其他类型讲解的小伙伴可以去我其他博客翻一翻哦。 首先来理解一波虚拟代理,啥叫虚拟代理? 举个很常见也很通俗的例子,咱们平时抽奖的时候,是不是都想要抽个…

[转载]虚拟代理模式(Virtualnbsp;Proxy)

虚拟代理模式(Virtualnbsp;Proxy) 第25章 虚拟代理模式(Virtual Proxy) 描述&#xff1a; 虚拟代理模式 (Virtual Proxy)是一种节省内存的技术&#xff0c;它建议创建那些占用大量内存或处理复杂的对象时&#xff0c;把创建这类对象推迟到使用它的时候。在特定的应用 中&#x…