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

目录

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 {
    @Override
    public 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;
    //定义登陆的类型是为了在后面的校验中 去选择使用哪一个realm
    private String loginType;

    public LoginToken(String phone, String loginType){
        this.phone=phone;
        this.loginType=loginType;
    }

    //认证规则,与realm中的认证对应
    @Override
    public 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
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        //做Realm的一个校验
        assertRealmsConfigured();
        //获取前端传递过来的token
        LoginToken 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为phone
            if(realm.getName().contains(loginType)){   //就能分开这两个realm
                typeRealms.add(realm);
            }
        }

        if(typeRealms.size()==1){
            return doSingleRealmAuthentication(typeRealms.iterator().next(), loginToken);
        }else{
            return doMultiRealmAuthentication(typeRealms, loginToken);
        }
    }
}

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

    //@Qualifier("userRealm") UserRealm userRealm
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Collection<Realm> realms) {
        DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager();
        SecurityManager.setRealms(realms);
        return SecurityManager;
    }

    //该bean为username和password验证realm
    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        //注册MD5加密
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return userRealm;
    }

    //该bean为phone验证realm
    @Bean
    public PhoneRealm phoneRealm() {
        return new PhoneRealm();
    }

    /**
     * 系统自带的Realm管理,主要针对多realm
     */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator() {
        //自己重写的ModularRealmAuthenticator
        LoginTypeModularRealmAuthenticator modularRealmAuthenticator = new LoginTypeModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

(4)编写PhoneRealm:

@Slf4j
public class PhoneRealm extends AuthorizingRealm {

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //认证
    @Override
    protected 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)
    @Documented
    public @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> {
    @Override
    public 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中的认证对应
    @Override
    public 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,方便进行参数的读取。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于Spring Boot集成Shiro,你可以按照以下步骤进行操作: 1. 首先,在你的Spring Boot项目中添加Shiro的依赖。你可以在pom.xml文件中添加以下依赖关系: ```xml <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> ``` 2. 创建一个Shiro的配置类,用于配置Shiro的相关组件和属性。可以使用`@Configuration`注解来标记该类作为配置类,并使用`@EnableShiroAnnotation`注解来启用Shiro的注解支持。 ```java @Configuration @EnableShiroAnnotation public class ShiroConfig { // 配置Shiro的相关组件和属性 // ... } ``` 3. 在上述配置类中,可以配置Shiro的Realm、Session管理器、缓存管理器等组件。你可以根据自己的需求选择相应的实现类并进行配置。 ```java @Configuration @EnableShiroAnnotation public class ShiroConfig { @Bean public Realm realm() { // 配置自定义的Realm实现类 // ... return realm; } @Bean public SessionManager sessionManager() { // 配置自定义的Session管理器实现类 // ... return sessionManager; } @Bean public CacheManager cacheManager() { // 配置自定义的缓存管理器实现类 // ... return cacheManager; } // 其他配置项... } ``` 4. 在主配置类中,添加`@Import`注解来引入Shiro的配置类。 ```java @SpringBootApplication @Import(ShiroConfig.class) public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` 5. 在需要进行权限控制的地方,使用Shiro的注解来标记需要进行权限验证的方法或类。例如,可以使用`@RequiresRoles`注解来限制具有特定角色的用户才能访问方法。 ```java @RestController public class YourController { @RequiresRoles("admin") @GetMapping("/admin") public String admin() { return "Hello, admin!"; } } ``` 这样,你就成功地集成Spring BootShiro,并可以进行基于角色的权限控制了。当然,以上只是一个简单的示例,你可以根据自己的需求进行更详细的配置和使用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值