irpas技术客

redis实现登录_编程小白的笔记分享和感悟

大大的周 7627

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录 基于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<UserDTO> 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<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key); if (userMap.isEmpty()){//判断用户是否存在 response.setStatus(401); return false; } //todo 将查询到的hashMap数据转化为UserDTO UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); UserHolder.saveUser(userDTO); //todo 刷新todo的有效期 stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); //todo 放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser();//一定要remove,避免内存泄漏 } } @Configuration public class MvcConfig implements WebMvcConfigurer { @Resource private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginIntercepter(stringRedisTemplate)) .excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ); } } 优化

之前的拦截器,虽然也能起到刷新token有效期的作用,但是,那个token只有在访问需要登录验证的一些handler时候才会刷新 ,这显然是不行的,为了解决这个问题,我们在定义一个拦截器,第一个拦截器先执行,拦截所有请求,只负责刷新token的时间(有就刷新,没有就不刷新,不负责拦截),第二个负责判断用户是不是存在(每次用户登录的时候,我们都把user保存在了ThreadLocal中,只需判单ThreadLocal中是否存在即可)

拦截器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,避免内存泄漏 } }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #redis实现登录