开发短信验证登录功能

开发流程:

发送短信模块

​ 当点击我的之后会弹出登录页面,要求输入手机号获取验证码,当输入手机号点击发送验证码后,通过抓包发现浏览器会发送如下报文

image-20240227195645100

可以看到,浏览器会以post方式向服务器发送请求,并携带请求参数:phone=xxx。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Result sendCode(String phone, HttpSession session){
//校验手机号是否合法
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
//如果不合法
if(phoneInvalid){
return Result.fail("手机号不合法,请重新输入");
}
//如果合法,则生成验证码(一个六位数随机数),并保存在session中
String code = RandomUtil.randomNumbers(6);
session.setAttribute("code",code);
//m
log.debug("成功发送验证码:" + code);
return Result.ok();
}
登录模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 用户登录功能
* @param loginFormDTO
* @param session
* @return
*/
public Result login(LoginFormDTO loginFormDTO,HttpSession session){
//验证手机号是否正确
String phone = loginFormDTO.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号格式错误");
}
//验证码是否一致
String code = loginFormDTO.getCode();
String cachecode = (String)session.getAttribute("code");
if(code == null || !cachecode.equals(code)){
return Result.fail("验证码错误");
}
//数据库中是否有该用户
User user = query().eq("phone", phone).one();
if(user == null){
user = createUserWithPhone(phone);
//保存用户
save(user);
}
//将用户保存到session中
session.setAttribute("user",user);
return Result.ok();
}
校验登录

问题:拦截器和过滤器有什么区别?拦截器的作用是什么?为什么要使用ThreadLocal?详细介绍一下ThreadLocal?

​ 拦截器是SpringMVC中提供的类,也是用于拦截请求,和过滤器不同的是,它拦截的是DispatcherServlet和控制器方法之间的请求。而过滤器拦截的是用户和Servlet间的请求。而且过滤器是基于Servlet的,它只能用于web程序,而拦截器是Spring的组件,由Spring控制,不仅能用于web程序,还能用于其他程序。

​ 拦截器是基于java反射机制(动态代理实现的),而过滤器是基于回调函数(doFilter方法)实现的,当一个过滤器执行完后,会调用doFilter方法返回执行上一级Filter

​ 鉴于两者都能够拦截请求,因此常被用来作为鉴权登录模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.hmdp.utils;

import com.hmdp.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
* @author dch
* @since 2024-02-28
* 校验用户登录
*/
public class UserLoginInteceptorHandler implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取session
HttpSession session = request.getSession();
//查看session中是否有用户信息
User user = (User) session.getAttribute("user");
if(user == null){
//进行拦截
response.setStatus(401);//401表示未得到授权
return false;
}
UserHolder.saveUser(user);
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hmdp.config;

import com.hmdp.utils.UserLoginInteceptorHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InceptorConfig implements WebMvcConfigurer {
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserLoginInteceptorHandler())
.excludePathPatterns(
//放行请求,这些请求不做拦截
"/user/login",
"/user/code",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/voucher-order/**"
);
}
}
如何保护用户敏感信息

​ 从图中可以看出,当前系统中,当我们登录时,会将用户的一些敏感信息暴露在浏览器中,这非常危险。因此我们需要解决该问题,那么常用的解决方案就是,定义两个用户实体,一个是数据库用户实体,另一个是返回给浏览器的用户实体,后者相对于前者缺少了用户的敏感信息,更加安全,如下图所示。