基于springBoot的手机号验证码登录操作及其扩展

前期概要

Shiro 是一个开源的 Java 安全框架,由 Apache 开发和维护。Shiro 可以帮助开发人员快速实现安全特性,包括身份认证、授权、加密和会话管理等。Shiro 的目标是简化 Java 安全编程,并提供更好的开发体验。

Shiro 的主要特点如下:

易于学习和使用:Shiro 设计简单,易于学习和使用。
灵活性高:Shiro 可以适用于任何应用场景,支持多种应用程序类型。
安全性高:Shiro 的安全性能比较优越,提供了多种方式来保护应用程序的安全性。
扩展性好:Shiro 框架提供了可扩展性插件机制,可以方便地扩展框架的功能,满足不同的需求。
企业级支持:Shiro 是从 Apache 基金会获得支持的框架,拥有众多企业用户。

在对 Shiro 框架有了基本认识之后,我们以智慧小区系统作为实际案例,实现了一个通过 Shiro 框架加固安全性的手机号验证码登录功能,为小区会员提供了便捷、安全的登录方法。

技术栈

Spring Boot、Nacos、Redis、Shiro、MQ、阿里云短信发送服务

smart-community-member

配置文件

在Resource包下编写bootsrap.yml文件,通过nacos远程拉取配置中心的配置文件

bootstrap.yml
spring:
  application:
    name: smart-community-member
  profiles:
    active: @project.active@
  cloud:
    nacos:
      config:
        server-addr: @NACOS-HOST@:@NACOS-PORT@
        namespace: @NACOS-NS@
        file-extension: yml
        shared-configs:
          - data-id: common-db.yml
    stream:
      rocketmq:
        binder:
          name-server: 你的服务端口
        producer-out-0:
          producer:
            group: member-sms-group
      bindings:
        producer-out-0:
          destination: member-sms-topic
  redis:
    host: 127.0.0.1
    timeout: 5s
    connect-timeout: 3s
    port: 6379
    password: ""
    database: 1
    jedis:
      pool:
        max-wait: 5s

seata:
  tx-service-group: member_register_tx_group
  service:
    vgroup-mapping:
      member_register_tx_group: default
  config:
    type: nacos
    nacos:
      server-addr: @NACOS-HOST@:@NACOS-PORT@
      group: DEFAULT_GROUP
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      server-addr: @NACOS-HOST@:@NACOS-PORT@
      group: DEFAULT_GROUP

nacos远程配置中心配置

server:
  port: 8090
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 你自己的端口号
  datasource:
    druid:
      url:  jdbc:mysql://localhost:3306/smart-member
      username: "root"
      password: "123"
      max-active: 8
micro:
  servers:
    integral:
      name: "smart-community-integral"
      path: "/integral"

控制层

    /**
     * 通过手机号发送短信验证码
     */

    @GetMapping("/verify/code")
    @ApiOperation("通过手机号发送短信验证码")
    @ApiImplicitParam(name = "phone",value = "手机号")
    public ResponseEntity<String> verify(String phone){
        return ResponseEntity.success(memberService.getverified(phone));
    }

    /**
     * 登录校验
     */
    @PostMapping("/login")
    @ApiOperation("登录校验")
    @ApiImplicitParams({@ApiImplicitParam(name = "phone",value = "手机号"),         
    @ApiImplicitParam(name = "code",value = "验证码")})
    public ResponseEntity<String> login(String phone,String code){
        return ResponseEntity.success(memberService.login(phone,code));
    }

业务实现层

    /**
     * 生成验证码
     * 保存到redis中 设置过期时间 60s
     *
     * @param phone
     * @return
     */
    @Override
    public String getverified(String phone) {
        //1.去redis数据库中查询手机号是否存在验证码
        Object phoneNumber = redisDao.get(CODE_SUFFIX_KEY + phone);
        if (ObjectUtils.isEmpty(phoneNumber)){
            //不存在就生成验证码
            String code = RandomUtil.randomNumbers(4);
            redisDao.set(CODE_SUFFIX_KEY+phone,code,Duration.ofMinutes(1));
            //通过MQ异步消息发送验证码 stream流实现统一的消息发送验证管理
            streamBridge.send("producer-out-0", MessageBuilder.withPayload(PhoneMessageDto.builder().phone(phone).code(code).build()).build());
        }
        return "success";
    }

    /**
     * 验证码登录
     * @param phone
     * @param code
     * @return
     */
    @Override
    public String login(String phone, String code) {

        SecurityUtils.getSubject().login(new UsernamePasswordToken(phone,code));
        return SecurityUtils.getSubject().getSession().getId().toString();
    }

JavaConfig

@Configuration
public class ShiroConfiguration {


    @Bean
    public PhoneAndCodeRealm realm(){
        return new PhoneAndCodeRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager(PhoneAndCodeRealm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();

        definition.addPathDefinition("/member/verify/code", "anon");
        definition.addPathDefinition("/member/login", "anon");
        // 其他路径都需要认证才能访问
        definition.addPathDefinition("/**", "authc");
        return definition;
    }
    
}

自定义Realm

public class PhoneAndCodeRealm extends AuthenticatingRealm {

    @Resource
    private MemberService memberService;

    @Resource
    private RedisDao redisDao;


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String phone = (String) token.getPrincipal();
        //根据手机号key获取验证码
        Object code =  redisDao.get("code:verify:phone:"+phone);

        //判断验证码是否正确
        if (ObjectUtils.isEmpty(code)){
            throw new MemberException(ResponseCode.CODE_ERROR);
        }
        return new SimpleAuthenticationInfo(phone,code.toString(),null,phone);
    }
}

smart-community-sms-aliyun

配置文件

sms:
  aliyun:
    access-key-id: "你自己的id"
    access-key-secret: "你自己的secret"
    endpoint: "dysmsapi.aliyuncs.com"
    sign-name: "智慧小区"
    template-code: "自己的code"
    template-name: code
spring:
  cloud:
    stream:
      function:
        definition: consumer
      rocketmq:
        binder:
          name-server: 你自己的服务
        bindings:
          consumer-in-0:
            consumer:
              messageModel: BROADCASTING
      bindings:
        consumer-in-0:
          destination: member-sms-message
          group: broadcast-consumer
server:
  port: 8887

javaConfig

@Configuration
@EnableConfigurationProperties(AliyunSmsProperties.class)
public class AliyunSmsConfig {

    @Autowired
    private AliyunSmsProperties smsProperties;
    @Bean
    public Client client() throws Exception {
       Config config = new Config();
       config.setAccessKeyId(smsProperties.getAccessKeyId());
       config.setAccessKeySecret(smsProperties.getAccessKeySecret());
       config.setEndpoint(smsProperties.getEndpoint());
       return new Client(config);

   }
}

@Data
@ConfigurationProperties("sms.aliyun")
public class AliyunSmsProperties {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String signName;
    private String templateCode;
    private String templateName;
}


DTO层

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PhoneMessageDTO {
    /**
     * 手机号
     */
    private String phone;
    /**
     * 验证码
     */
    private String code;
}

listener层

@Service
public class SmsListener {

    @Resource
    private SmsService smsService;

    @Bean
    public Consumer<Message<PhoneMessageDTO>> consumer(){
        return phoneMsg -> {
            smsService.sendMsg(phoneMsg.getPayload().getPhone(),phoneMsg.getPayload().getCode());
        };
    }
}

service实现层

@Service
public class SmsServiceimpl implements SmsService {

    @Resource
    private Client client;

    @Resource
    private AliyunSmsProperties smsProperties;

    @Resource
    private ObjectMapper objectMapper;


    @Override
    public void sendMsg(String phone, String code) {

        try {
            //  1.创建hashMap集合
            HashMap map = new HashMap();
            // 2.将验证码放入map集合中
            map.put("code",code);
            // 3.创建发送短信的请求对象
            SendSmsRequest smsRequest = new SendSmsRequest();
            // 4.设置手机号码
            smsRequest.setPhoneNumbers(phone);
            // 5.设置短信模板
            smsRequest.setTemplateCode(smsProperties.getTemplateCode());
            // 6.设置签名
            smsRequest.setSignName(smsProperties.getSignName());
            // 7.设置模板参数
            smsRequest.setTemplateParam(objectMapper.writeValueAsString(map));
            // 8.发送短信
            client.sendSms(smsRequest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试

验证码的发送

在redis中查看验证码是否生成

使用手机号验证码登录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值