用户加密与JWT拦截器Token的学习(其实变成了redis学习)
本文最后更新于86 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

学了几个项目,这些东西都是随便看了看就过过去了,没有系统了解其原理,感觉很模式,今天就来学习一下

Session登录

发送验证码

 /**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 发送短信验证码并保存验证码
        return Result.fail("功能未完成");
    }

服务层

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //保存验证码到session
        String code = RandomUtil.randomNumbers(6);
        session.setAttribute("code", code);
        //发送验证码
        log.debug("发送验证码成功,验证码:{}", code);
        //返回ok
        return Result.ok();
    }

用户登录

 @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        //  实现登录功能

        return userService.login(loginForm, session);
    }
 @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //校验手机号
        if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {
            return Result.fail("手机号格式错误");
        }
        //校验验证码
        String code = (String) session.getAttribute("code");
        //不一致报错
        if (code == null || !code.equals(loginForm.getCode())) {
            return Result.fail("验证码错误");
        }
        //一致,根据手机号查询用户
        User user = query().eq("phone", loginForm.getPhone()).one();
        //判断用户是否存在
        if (user == null) {
            //不存在,创建新用户并保存
            user = createUserWithPhone(loginForm.getPhone());
            save(user);
            log.debug("创建用户成功:{}", user);
        }
        //保存用户信息到session
        session.setAttribute("user", user);
        return Result.ok();
    }

    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
        return user;
    }

这些完成后还要有登录凭证,登录校验拦截器

ThreadLocal拦截

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取session
        HttpSession session = request.getSession();
        //获取session中的用户
        Object user = session.getAttribute("user");
        //判断用户是否存在
        if (user == null){
            //不存在,拦截,返回401
            response.setStatus(401);
            return false;
        }
        //存在,保存用户信息到ThreadLocal
        UserHolder.saveUser((UserDTO) user);
        //放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}




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();
    }
}
package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                );
    }
}

集群session共享问题

session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失问题。

多个TOMcat服务器不会共享session

  //保存验证码到session
        String code = RandomUtil.randomNumbers(6);
        //session.setAttribute("code", code);
          //保存验证码到redis,并设置过期时间
        stringRedisTemplate.opsForValue().set("login:code:" + phone, code,2, TimeUnit.MINUTES);
  //保存用户信息到session
        UserDTO userDTO = new UserDTO();
        BeanUtil.copyProperties(user, userDTO);
        //session.setAttribute("user",userDTO);
        //把用户存到redis
        //生成随机token
        String token = UUID.randomUUID().toString(true);
        //将userDTIO转换为HashMap
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
        //添加到redis
        stringRedisTemplate.opsForHash().putAll("login:token:"+token, userMap);
        //设置token有效期
        stringRedisTemplate.expire("login:token:"+token,LOGIN_CODE_TTL,TimeUnit.MINUTES);

拦截器token

  String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            response.setStatus(401);
            return false;
        }
        //获取token中的用户
        Map<Object, Object> UserDtoMap = stringRedisTemplate.opsForHash().entries("login:token:" + token);
        UserDTO user = BeanUtil.fillBeanWithMap(UserDtoMap, new UserDTO(), false);

确保都是String类型的

//将userDTIO转换为HashMap,Long类型要转换为String类型
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), 
                CopyOptions.create().setIgnoreNullValue(true).
                        setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));

只拦截了登录等请求,其他请求无法刷新有效期,新定义一个拦截器

if (StrUtil.isNotBlank(shopJson)) {
//存在,返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//命中了空值缓存,返回错误信息
if (shopJson != null) {
return Result.fail("商户不存在");
}

缓存穿透,传递空值要这样判断.

逻辑过期

 private Shop queryWithLogicalExpire(Long id) {
        //从redis从查询商户
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if (StrUtil.isBlank(shopJson)) {
            //不存在,返回null
            return null;
        }
        //命中,需要判断过期时间
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        //判断是否过期
        //未过期,直接返回商户信息
        if (expireTime.isAfter(LocalDateTime.now())){
            return shop;
        }
        //已过期,缓存重建
        //获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLocked = tryLock(lockKey);
        //判断是否获取锁成功
        if (isLocked){
            //成功,获取锁成功,开启独立线程,实现缓存重建,根据id查询数据库
            CACH_REBUILD_EXECUTOR.submit(() -> {
                try {
                    saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    unLock(lockKey);
                }
            });
            
        }
        //返回
        return shop;
    }

设置一个逻辑过期类

@Data
public class RedisData {
    // 过期时间
    private LocalDateTime expireTime;
    private Object data;
}
private void saveShop2Redis(Long id, Long expireSeconds){
         //查询店铺数据
         Shop shop =getById(id);
         //封装逻辑过期时间
         RedisData redisData = new RedisData();
         redisData.setData(shop);
         redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
         //写入redis
         stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
     }


乐观锁与悲观锁

Redission可重入锁

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇