Loading

登录功能

登录开发

核心逻辑

  • 通过mail找数据库记录
  • 获取盐,和当前传递的密码就行加密后匹配
  • 生成token令牌

JWT

  • JWT

    • JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名
    • 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息
          	  {
                    id:888,
                    name:'小D',
                    expire:10000
                }
                
                funtion 加密(object, appsecret){
                    xxxx
                    return base64( token);
                }
    
                function 解密(token ,appsecret){
    
                    xxxx
                    //成功返回true,失败返回false
                }
    
    • 优点

      • 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
      • 存储在客户端,不占用服务端的内存资源
    • 缺点

      • token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等

      • 如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥

  • JWT格式组成 头部、负载、签名

    • header+payload+signature
      • 头部:主要是描述签名算法
      • 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户
      • 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token
  • 关于jwt客户端存储

    • 可以存储在cookie,localstorage和sessionStorage里面
  • controller

@ApiOperation("用户登录")
@PostMapping("login")
public JsonData login(@ApiParam("用户登录对象") @RequestBody UserLoginRequest userLoginRequest) {
    JsonData jsonData = userService.login(userLoginRequest);
    return jsonData;
}
  • service
    /**
     * 用户登录
     * @param userLoginRequest
     * @return
     */
    @Override
    public JsonData login(UserLoginRequest userLoginRequest) {
        List<UserDO> userDOList = userMapper.selectList(new QueryWrapper<UserDO>().eq("mail", userLoginRequest.getMail()));
        if (userDOList != null && userDOList.size() == 1) {
            //已注册
            UserDO userDO = userDOList.get(0);
            String cryptPwd = Md5Crypt.md5Crypt(userLoginRequest.getPwd().getBytes(), userDO.getSecret());
            if (cryptPwd.equals(userDO.getPwd())) {
                //生成token
                LoginUser loginUser = LoginUser.builder().build();
                BeanUtils.copyProperties(userDO,loginUser);
                String accessToken = JWTUtil.geneJsonWebToken(loginUser);
                return JsonData.buildSuccess(accessToken);
            } else {
                return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
            }
        } else {
            //未注册
            return JsonData.buildResult(BizCodeEnum.ACCOUNT_UNREGISTER);
        }
    }

登录拦截器开发

  • 解密JWT
  • 传递登录用户信息
    • attribute传递
    • threadLocal传递
  • SpringBoot拦截器代码开发
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
            String accessToken = request.getHeader("token");
            if (accessToken == null) {
                accessToken = request.getParameter("token");
            }

            if (StringUtils.isNotBlank(accessToken)) {
                Claims claims = JWTUtil.checkJWT(accessToken);
                if (claims == null) {
                    //告诉登录过期,重新登录
                    CommonUtil.sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
                    return false;
                }

                Long id = Long.valueOf( claims.get("id").toString());
                String headImg = (String) claims.get("head_img");
                String mail = (String) claims.get("mail");
                String name = (String) claims.get("name");

			   //TODO 用户信息传递
                return true;
            }
        } catch (Exception e) {
            log.error("拦截器错误:{}",e);
        }
        CommonUtil.sendJsonMessage(response, JsonData.buildError("token不存在,重新登录"));
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

ThreadLocal传递信息

  • ThreadLocal
全称thread local variable(线程局部变量)功用非常简单,使用场合主要解决多线程中数据因并发产生不一致问题。

ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。 

总结起来就是:同个线程共享数据

注意:ThreadLocal不能使用原子类型,只能使用Object类型
  • 核心应用场景
ThreadLocal 用作每个线程内需要独立保存信息,方便同个线程的其他方法获取该信息的场景。

每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,比如用户登录令牌解密后的信息传递(还有用户权限信息、从用户系统获取到的用户名、用户ID)

image-20221018082425088

  • 配置token解密信息传递
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String accessToken = request.getHeader("token");

        if (accessToken == null) {
            accessToken = request.getParameter("token");
        }

        if (StringUtils.isNotBlank(accessToken)) {
            Claims claims = JWTUtil.checkJWT(accessToken);
            if (claims == null) {
                CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
                return false;
            }

            long userId = Long.valueOf(claims.get("id").toString());
            String headImg = (String) claims.get("head_img");
            String name = (String) claims.get("name");
            String mail = (String) claims.get("mail");

            LoginUser loginUser = LoginUser
                    .builder()
                    .headImg(headImg)
                    .name(name)
                    .id(userId)
                    .mail(mail).build();

            //通过ThreadLocal传递信息
            threadLocal.set(loginUser);

            return true;
        }

        CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

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

登录拦截器路径配置和开发

  • 拦截器配置
    • 拦截路径
    • 不拦截路径
@Configuration
@Slf4j
public class InterceptorConfig  implements WebMvcConfigurer {


    public  LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(loginInterceptor())
                //拦截的路径
                .addPathPatterns("/api/user/*/**","/api/address/*/**")

                //排查不拦截的路径
                .excludePathPatterns("/api/user/*/send_code","/api/user/*/captcha",
                        "/api/user/*/register","/api/user/*/login","/api/user/*/upload");

    }
}
posted @ 2022-10-18 08:33  yonugleesin  阅读(61)  评论(0编辑  收藏  举报