使用阿里云的短信服务进行登录&实现登录拦截的功能(前后端分离) ---- (Session存储验证码&用户信息)

使用阿里云的短信服务进行登录&实现登录拦截的功能(Session存储

开通短信服务

进入阿里云官网,进行下述操作。

在这里插入图片描述

点击免费开通。

在这里插入图片描述

点击免签名/模板审核的API发送测试。因为这里没有营业执照,所以就使用的是测试版的。
在这里插入图片描述

这里绑定测试手机号码
在这里插入图片描述

添写下述信息即可

在这里插入图片描述

点击调用API发送短信
在这里插入图片描述

获取AccessKey

点击控制台
在这里插入图片描述

点击AccessKey管理

在这里插入图片描述

点击继续使用AccessKey里边获取AccessKey ID 和 AccessKey Secret
在这里插入图片描述

搭建前端环境

login.html页面。这里我只是进行功能的实现,页面没有进行美化。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="http://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script>
<body>
<a href="#" id="toAuthentication">测试不认证是否可以调到authentication.html页面</a><br/>
<a href="#" id="toNoAuthentication">测试不认证是否可以调到此页面index.html</a>
<form>
    <div>
        <label>手机号:</label><input name="mobile" id="mobile" type="text">
        <button type="button" id="getCode">获取短信验证码</button>
    </div>
    <div>
        <label>验证码:</label>
        <input name="code" id="code" type="text">

    </div>
    <div>
        <button type="button" id="submits">提交</button>
    </div>
</form>
</body>
<script type="text/javascript">
    $("#getCode").click(function () {
        sendCode($("#getCode"));
    });
    $("#submits").click(function () {
        submit();
    });
    $("#toAuthentication").click(function () {
        toAuthentication(1);
    });

    $("#toNoAuthentication").click(function () {
        toAuthentication(2);
    });

    // 测试是否能到认证的页面
    function toAuthentication(num) {
        if(num == 1){
            $.ajax({
                url: "http://localhost:8081/authentication",
                type: "get",
                xhrFields:{
                    withCredentials:true
                },
                success: function (res) {
                    if(res.success){
                        location.href="authentication.html"
                    }
                },
                error: function () {
                }
            })
        }else if(num == 2){
            $.ajax({
                url: "http://localhost:8081/index",
                type: "get",
                xhrFields:{
                    withCredentials:true
                },
                success: function (res) {
                    if(res.success){
                        location.href="index.html"
                    }
                },
                error: function () {
                }
            })
        }

    }
    //用ajax发送验证码
    function sendCode(obj) {
        var phone = $("#mobile").val();
        var result = isPhoneNum();
        if (result) {
            $.ajax({
                url: "http://localhost:8081/sendCode",
                data: {phone: phone},
                dataType: "json",
                type: "post",
                xhrFields:{
                  withCredentials:true
                },
                success: function (res) {
                    if (res) {
                        setTime(obj);//开始倒计时
                        alert("验证码发送成功");
                    }
                },
                error: function () {
                    alert("验证码发送失败");
                }
            })
        }
    }

    // 提交表单
    function submit() {
        var phone = $("#mobile").val();
        var code = $("#code").val();
        console.log(phone + "   " + code)
        var result = isPhoneNum();
        if (result) {
            $.ajax({
                url: "http://localhost:8081/login",
                data: {phone: phone, code: code},
                dataType: "json",
                type: "post",
                xhrFields:{
                    withCredentials:true
                },
                success: function (res) {
                    console.log(res)
                    if (res.success) {
                        location.href = "index.html";
                    }else {
                        alert(res.errorMessage)
                    }
                    getInfo();
                },
                error: function () {
                }
            })
        }
    }

    // 获取个人信息
    function getInfo() {
        $.ajax({
            url: "http://localhost:8081/get",
            type: "get",
            xhrFields:{
                withCredentials:true
            },
            success: function (res) {
                console.log(res)
            },
            error: function () {
            }
        })
    }

    //使用递归来实现
    var countdown = 60;

    function setTime(obj) {
        if (countdown == 0) {
            obj.prop('disabled', false);
            obj.text("点击获取验证码");
            countdown = 60;
            return;
        } else {
            obj.prop('disabled', true);  // 设置不可点击的属性
            obj.text("(" + countdown + "s)后重新发送");
            countdown--;
        }
        setTimeout(function () {
            setTime(obj)
        }, 1000) //每1000毫秒执行一次
    }


    //校验手机
    function isPhoneNum() {
        var phone = $("#mobile").val();
        var reg = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;
        if (!reg.test(phone)) {
            alert('请输入有效的手机号码!');
            return false;
        } else {
            return true;
        }
    }
</script>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>欢迎来到首页</h1>
</body>
</html>

authentication.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>需认证的页面</title>
</head>
<body>
<h1>认证成功后到达的页面</h1>
</body>
</html>

搭建后端环境

创建一个SpringBoot工程。这里SpringBoot版本为2.7.5。

这是最终的项目结构。接下来一步一步讲解。
在这里插入图片描述

创建表

create table t_user(
	id int primary key auto_increment,
	phone char(11) not null
)

导入依赖。

 <!-- mysql驱动 -->
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
 </dependency>
 <!-- 小辣椒 -->
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <optional>true</optional>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
 </dependency>
 <!-- mybatis-plus依赖 -->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.5.2</version>
 </dependency>
 <!-- SpringBoot 的依赖 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!-- 工具包 -->
 <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.8.5</version>
 </dependency>

application.properties文件的配置

server.port=8081
# 数据库的配置
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_csdn

# mybatis-plus的配置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

实体类

创建实体类

package com.zhang.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
    private Integer id;
    private String phone;
}

创建LoginFormVo类。主要是来负责接收登录表单的属性

package com.zhang.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginFormVo {
    private String phone;
    private String code;
}

创建UserDTO对象。是数据传输对象。

package com.zhang.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.omg.CORBA.PRIVATE_MEMBER;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
    private Integer id;
}

Dao层

创建UserMapper类

package com.zhang.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

业务层

UserService接口

package com.zhang.service;
import com.zhang.util.Result;
import com.zhang.vo.LoginFormVo;
import javax.servlet.http.HttpSession;
public interface UserService {
    /**
     * 发送验证码
     * @param phone
     * @param session
     * @return
     */
    Result sendCode(String phone, HttpSession session);

    /**
     * 登录
     * @param loginFormVo
     * @param session
     * @return
     */
    Result login(LoginFormVo loginFormVo, HttpSession session);

    /**
     * 获取ThreadLocal中的信息
     * @return
     */
    Result getInfo();
}

UserService的实现类UserServiceImpl

package com.zhang.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zhang.config.SendInfo;
import com.zhang.dto.UserDTO;
import com.zhang.mapper.UserMapper;
import com.zhang.pojo.User;
import com.zhang.service.UserService;
import com.zhang.util.Constant;
import com.zhang.util.RegexPattern;
import com.zhang.util.Result;

import com.zhang.util.UserDTOThreadLocal;
import com.zhang.vo.LoginFormVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
@Service
public class UserServiceImpl implements UserService {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);

    @Resource
    private UserMapper userMapper;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1. 效验手机号格式是否正确
        Boolean checkResult = RegexPattern.checkPhone(phone);
        // 2. 效验失败,返回手机号格式有误
        if(!checkResult){
            return Result.fail("手机格式有误!");
        }
        // 3. 正确  --->  则随机生成6位的验证码
        String code = RandomUtil.randomNumbers(4);
        // 4. 将验证码存到session中
        session.setAttribute(Constant.LOGIN_CODE,code);
        // 5. 如果有第三方接口,则发送验证码
        SendInfo.sendCode(phone,code);
        log.info("发送的验证码为:" + code);
        // 6. 返回结果
        return Result.ok();
    }

    @Override
    public Result login(LoginFormVo loginFormVo, HttpSession session) {
        // 1. 效验手机号格式是否正确
        String phone = loginFormVo.getPhone();
        Boolean checkResult = RegexPattern.checkPhone(phone);
        // 2. 效验失败,返回手机号格式有误
        if(!checkResult){
            return Result.fail("手机格式有误!");
        }
        // 3. 正确  ---> 从session中取出验证码
        String code = (String) session.getAttribute(Constant.LOGIN_CODE);
        log.info("login_session中的验证码为:" + session.getAttribute(Constant.LOGIN_CODE));
        // 4. 判断验证码是否正确
        if(code == null || !code.equals(loginFormVo.getCode())){
            // 5. 不正确  --> 返回
            return Result.fail("验证码不正确!");
        }
        // 6. 正确  --->  通过手机号查询用户是否存在
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("phone",loginFormVo.getPhone());
        User user = userMapper.selectOne(wrapper);
        // 7. 不存在  --->  自动注册
        if(user == null){
            user = new User();
            user.setPhone(phone);
            userMapper.insert(user);
        }
        // 8. 存在 --> 注意,这里将信息存储到封装的DTO类中,DTO包含User类的部分属性
        // 将user对象封装为UserFTO,这样只暴露出用户的不重要的信息,更加安全
        UserDTO userDTO = new UserDTO();
        BeanUtil.copyProperties(user,userDTO);
        // 9. 将UserDTO对象信息存储到session中
        session.setAttribute(Constant.LOGIN_USER,userDTO);
        // 10. 返回结果
        return Result.ok();
    }

    @Override
    public Result getInfo() {
        UserDTO user = UserDTOThreadLocal.getInfo();
        log.info(user.toString());
        return Result.ok(user);
    }
}

控制层

UserController类

package com.zhang.controller;

import com.zhang.service.UserService;
import com.zhang.util.Result;
import com.zhang.vo.LoginFormVo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

@RestController
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 发送手机验证码
     * @param phone
     * @param session
     * @return
     */
    @RequestMapping("/sendCode")
    public Result sendCode(String phone, HttpSession session){
        return userService.sendCode(phone,session);
    }

    /**
     * 登录
     * @param loginFormVo
     * @param session
     * @return
     */
    @RequestMapping("/login")
    public Result login(LoginFormVo loginFormVo, HttpSession session){
        return userService.login(loginFormVo,session);
    }

    /**
     * 获取信息
     * @return
     */
    @RequestMapping("/get")
    public Result getInfo(){
        return userService.getInfo();
    }

    /**
     * 测试,不认证看是否会拦截,这个是默认跳转到authentication.html页面
     * @return
     */
    @RequestMapping("/authentication")
    public Result authentication(){
        return Result.ok();
    }
    /**
     * 测试,不认证看是否会拦截,这个是跳转到index.html页面
     * @return
     */
    @RequestMapping("/index")
    public Result index(){
        return Result.ok();
    }
}

配置类

LoginIntercept拦截器类

package com.zhang.config;
import com.zhang.dto.UserDTO;
import com.zhang.util.Constant;
import com.zhang.util.UserDTOThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
// 拦截器
public class LoginIntercept implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取session
        HttpSession session = request.getSession();
        // 2. 获取用户信息
        UserDTO userDTO = (UserDTO) session.getAttribute(Constant.LOGIN_USER);
        // 3. 判断用户是否存在
        if(userDTO == null){
            // 4. 不存在 -->  则拦截,返回401状态
            response.setStatus(401);
            return false;
        }
        // 5. 存在 -->  将其保存在ThreadLocal中
        UserDTOThreadLocal.save(userDTO);
        // 6. 放行
        return true;
    }
}

MvcConfig 配置类

package com.zhang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    /**
     * 解决跨域问题
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路由
        registry.addMapping("/**")
                // 是否发送cookie
                .allowCredentials(true)
                // 放行哪些资源
                //.allowedOrigins("*") 出现错误,使用下面这种
                .allowedOriginPatterns("*") // 设置允许跨域请求的域名
                .allowedMethods(new String[]{"GET","POST","PUT","DELETE"})
                .allowedHeaders("*")
                .exposedHeaders("*");
    }

    /**
     * 配置拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginIntercept()) // 设置登录拦截器
                .excludePathPatterns("/login","/index","/sendCode");    // 这些路径不需要通过过滤器
    }
}

工具类

常量类

package com.zhang.util;

public interface Constant {
    /**
     * 验证码
     */
    public static final String LOGIN_CODE = "code";
    /**
     * Session中存储的用户对象
     */
    public static final String LOGIN_USER = "login_user";
    /**
     * 阿里云的AccessKey ID
     */
    public static final String ACCESS_KEY_ID = "你自己的AccessKey ID";
    /**
     * 阿里云的AccessKey SECRET
     */
    public static final String ACCESS_KEY_SECRET = "你自己的AccessKey SECRET";
}

RegexPattern类。效验格式是否正确

package com.zhang.util;
public class RegexPattern {
    /**
     * 效验手机格式是否正确
     * @param phone
     * @return
     */
    public static Boolean checkPhone(String phone){
        String phoneRegex = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
        if(phone.matches(phoneRegex)){
            return true;
        }
        return false;
    }
}

Result 类,封装的结果集。

package com.zhang.util;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Boolean success;  // 是否成功
    private String errorMessage;    // 返回错误消息
    private Object data;    // 返回数据
    public static Result ok(){
        return new Result(true,null,null);
    }
    public static Result ok(Object data){
        return new Result(true,null,data);
    }
    public static Result fail(String errorMessage){
        return new Result(false,errorMessage,null);
    }
}

UserDTOThreadLocal 类,操作ThreadLocal的。

package com.zhang.util;

import com.zhang.dto.UserDTO;
public class UserDTOThreadLocal {
    private static final ThreadLocal<UserDTO> local = new ThreadLocal<>();
    public static void save(UserDTO userDTO){
        local.set(userDTO);
    }
    public static void remove(){
        local.remove();
    }
    public static UserDTO getInfo(){
        return local.get();
    }
}

SendInfo类,封装发送短信验证码的工具类。

package com.zhang.util;

import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.tea.TeaException;
import com.aliyun.teautil.Common;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;

public class SendInfo {

    public static Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
        Config config = new com.aliyun.teaopenapi.models.Config()
                // 必填,您的 AccessKey ID
                .setAccessKeyId(accessKeyId)
                // 必填,您的 AccessKey Secret
                .setAccessKeySecret(accessKeySecret);
        // 访问的域名
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new Client(config);
    }

    public static void sendCode(String phone,String code) {
        try {
            Client client = createClient(Constant.ACCESS_KEY_ID, Constant.ACCESS_KEY_SECRET);
            SendSmsRequest sendSmsRequest = new SendSmsRequest()
                    .setSignName("阿里云短信测试")
                    .setTemplateCode("SMS_154950909")
                    .setPhoneNumbers(phone)
                    .setTemplateParam("{\"code\":\""+ code +"\"}");
            RuntimeOptions runtime = new RuntimeOptions();
            // 复制代码运行请自行打印 API 的返回值
            client.sendSmsWithOptions(sendSmsRequest, runtime);
        } catch (TeaException error) {
            // 如有需要,请打印 error
            com.aliyun.teautil.Common.assertAsString(error.message);
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 如有需要,请打印 error
            Common.assertAsString(error.message);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

☞^O^☜♞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值