提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 基于session实现用户登录
-
- 1、发送短信验证码
- 2、短信验证登录
- 3、使用拦截器解决登录验证
- 4、 集群下,使用session解决用户登录的问题
- redis实现用户登录
-
- 1、基于redis实现用户登录的业务流程
- 1、发送短信验证
- 3、校验用户登录状态
- 优化
-
- 拦截器1——负责刷新token有效期
- 拦截器2——负责校验用户是否登录
基于session实现用户登录
文章图片
1、发送短信验证码
文章图片
@Override
public Result sendCode(String phone, HttpSession session) {
//校验手机号
if (RegexUtils.isPhoneInvalid(phone)){
//不符合返回
return Result.fail("手机号格式错误");
}
//符合,生成验证码
String code = RandomUtil.randomNumbers(6);
//这个是hutool提供的随机生成6位数的API
//保存验证码到session
session.setAttribute("code",code);
//发送验证码(真实情况下发送验证码,要调用第三方的平台,我们这里只是模拟一下)
log.debug("发送短信验证码成功,验证码{}", code);
return Result.ok();
}
2、短信验证登录
文章图片
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//校验手机号
String phone = loginForm.getPhone();
//手机号
if (RegexUtils.isPhoneInvalid(phone)){
//不符合返回
return Result.fail("手机号格式错误");
}
//校验验证码String codeClient = loginForm.getCode();
//用户填写的验证码String codeServer = (String)session.getAttribute("code");
//服务器发送给客户端的验证码
if (StringUtils.isEmpty(codeClient)){
//不符合返回
return Result.fail("请填写验证码");
}
if (!codeClient.equals(codeServer)){
//不符合返回
return Result.fail("验证码填写有误");
}//校验一致,判断用户是否存在
User user = query().eq("phone", phone).one();
if (user==null){
//创建新用户并保存user= createUserWithPhone(phone);
}
session.setAttribute("user", BeanUtil.copyProperties(user,UserDTO.class));
return Result.ok();
}private User createUserWithPhone(String phone){
//创建用户
User user =new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//保存用户
save(user);
return user;
}
3、使用拦截器解决登录验证
文章图片
文章图片
因为一个用户的请求就对应一个线程,为了获取user的线程安全问题,我们把user绑定在线程域ThreadLocal
public class UserHolder {
private static final ThreadLocal tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}public static UserDTO getUser(){
return tl.get();
}public static void removeUser(){
tl.remove();
}
}public class LoginIntercepter implements HandlerInterceptor {@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取session
HttpSession session = request.getSession();
UserDTO user = (UserDTO)session.getAttribute("user");
if (user==null){//判断用户是否存在
response.setStatus(401);
return false;
}
UserHolder.saveUser(user);
return true;
}@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
//一定要remove,避免内存泄漏
}
}
4、 集群下,使用session解决用户登录的问题
文章图片
redis实现用户登录 1、基于redis实现用户登录的业务流程
文章图片
1、发送短信验证:不再将验证码保存在session中,而是保存在redis中,使用String类型即可,key为手机号,value为验证码
2、短信登录和注册:同样需要校验手机号和验证码、判断用户是否存在等。如果用户存在,服务器创建token并返回给客户端,用户保存在redis中,这里推荐Hash类型保存,key为token,如果用户不存在就注册用户,并保存在redis中,逻辑同上
token不推荐用手机号,不安全
文章图片
文章图片
3、校验用户登录状态的时候,请求会携带token过来,如果通过token校验用户是否存在
1、发送短信验证
@Override
public Result sendCode(String phone, HttpSession session) {
//校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
//不符合返回
return Result.fail("手机号格式错误");
}
//符合,生成验证码
String code = RandomUtil.randomNumbers(6);
//这个是hutool提供的随机生成6位数的API
/**
* 保存验证码到redis中
*
* key:login:code:手机号
* value:验证码
* 保存两分钟
*/
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
//发送验证码(真实情况下发送验证码,要调用第三方的平台,我们这里只是模拟一下)
log.debug("发送短信验证码成功,验证码{}", code);
return Result.ok();
}
2、短信登录和注册
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//校验手机号
String phone = loginForm.getPhone();
//手机号
if (RegexUtils.isPhoneInvalid(phone)) {
//不符合返回
return Result.fail("手机号格式错误");
}
//校验验证码String codeClient = loginForm.getCode();
//用户填写的验证码
//todo 从redis获取验证码
String codeServer = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (StringUtils.isEmpty(codeClient)) {
//不符合返回
return Result.fail("请填写验证码");
}
if (!codeClient.equals(codeServer)) {
//不符合返回
return Result.fail("验证码填写有误");
}//校验一致,判断用户是否存在
User user = query().eq("phone", phone).one();
if (user == null) {
//创建新用户并保存user = createUserWithPhone(phone);
}//todo 保存用户到redis中
//随机生成token,作为登录令牌
String token = UUID.randomUUID().toString(true);
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//将user转化为hashMap存储
String tokenKey = token+LOGIN_USER_KEY;
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString())
));
//设置LOGIN_USER_KEY有效期为半小时,注意的是在校验用户登录状态的时候,要更新这个时间,
/**
* session,是只要用户超过30分钟不操作,就会删除session中的数据,用户就得重新登录
* 我们这里就要模仿session的做法, 如果用户长时间不访问操作系统就让用户重新登录
*/
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
//返回token
return Result.ok(token);
}
3、校验用户登录状态
public class LoginIntercepter implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;
public LoginIntercepter(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO 获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)){
//不存在,拦截
response.setStatus(401);
return false;
}
String key = RedisConstants.LOGIN_CODE_KEY + token;
//TODO 基于token获取redis中的用户(hashMap)
Map
优化 之前的拦截器,虽然也能起到刷新token有效期的作用,但是,那个token只有在访问需要登录验证的一些handler时候才会刷新
,这显然是不行的,为了解决这个问题,我们在定义一个拦截器,第一个拦截器先执行,拦截所有请求,只负责刷新token的时间(有就刷新,没有就不刷新,不负责拦截),第二个负责判断用户是不是存在(每次用户登录的时候,我们都把user保存在了ThreadLocal中,只需判单ThreadLocal中是否存在即可)
【redis|redis实现登录】
文章图片
文章图片
拦截器1——负责刷新token有效期
package com.hmdp.config;
import com.hmdp.utils.LoginIntercepter;
import com.hmdp.utils.RefrshTokenInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercepter())
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);
//order越小,拦截器优先级越高
registry.addInterceptor(new RefrshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
}
}
拦截器2——负责校验用户是否登录
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginIntercepter implements HandlerInterceptor {@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断是否需要拦截(ThreadLocal中是否有用户)
if (UserHolder.getUser()==null){
//没有,需要拦截
response.setStatus(401);
return false;
}
//有用户放行
return true;
}@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
//一定要remove,避免内存泄漏
}
}
推荐阅读
- 北亚数据恢复NTFS文件系统误操作导致raid5阵列中的分区被格式化的逆向操作服务器数据恢复方法
- Java中使用OpenCV进行图像处理|S13(增亮)
- Java中使用OpenCV进行图像处理|S14(清晰度增强)
- Java中的图像处理S14(两个图像的比较)
- Java中的图像处理S12(对比度增强)
- Java中的图像处理S11(图像的更改方向)
- Java中的图像处理S10(为图像加水印)
- Java中的图像处理S9(人脸检测)
- Java中的图像处理S8(创建镜像)