springboot +redis+token 实现登陆、登出

前言:相信很多小伙伴在刚做程序员或者说刚学习做程序员的时候首门课程就是登陆,登陆看起来简单,其实里面也包含了很多东西。逻辑清晰,十分适合新手学习。由于种种原因我在工作很久后才接触,以前总是觉得token很难,这次终于学会了,安排!

开始入主题

登陆

入参:username,password(用户名和密码)
出参:token,username(token和用户名)
思路:拿到用户名,密码,给密码加密,去查数据库,根据业务判断异常信息,根据userid生成token,并把token放到redis中设置好过期时间,把username和token放到返回体中

service层

  1. 获取用户名
  2. 获取密码
  3. 给密码加密(这里是用的开源Hutools中的SecureUtil.md5()方法,具体请看Hutools官网 https://hutool.cn/docs/#/)
  4. 根据username去数据库查询(确保username唯一)这条实体
  5. 进行判断这个实体
  6. 这个实体是否为空(若为空,抛出“系统中无此用户”)
  7. 这个实体的状态是否为启用(若不是,抛出“这个用户处于禁用状态,请联系管理员”)
  8. 这个加密后的password和数据库这个实体的password是否相同(若不同,抛出“密码不相符”)
  9. 封装返回信息(即token和username)

这段代码

  public AdminLoginVO loginPass(AdminLoginReq adminLoginReq) {
        String name = adminLoginReq.getName();
        String password = SecureUtil.md5(adminLoginReq.getPassword());// 获取密码并加密
        Admin admin = adminMapper.selectOne(new LambdaQueryWrapper<Admin>().eq(Admin::getName, name));
        if (null == admin) {
            throw new BusinessException("系统中无此用户");
        }
        if (admin.getStatus().equals(AdminStatusEnum.DISABLE) || admin.getSysStatus().equals(AdminStatusEnum.DISABLE)) {
            throw new BusinessException("这个用户处于禁用状态,请联系管理员");
        }
        if (!password.equals(admin.getPassword())) {
            throw new BusinessException("密码不相符");
        }
        return adminLoginData(admin);
    }

注意:BusinessException使我们自己项目中封装好的异常,大家用的时候可以抛RuntimeException

封装返回信息(即token和username)

  1. 根据userid创建token(本项目中是单独写了个工具类)
  2. 将token放到redis中并设置过期时间
  3. 将token和username set到返回的VO中

talk is cheap,show your code!!

 private AdminLoginVO adminLoginData(Admin admin) {
        String adminToken = JwtToken.createToken(admin.getId().toString());
        redisTemplate.opsForValue().set(
                String.format(GODZILLA:ADMIN:TOKEN:%s, admin.getId()),//%s是占位符,这样id就拼到%s的位置上了,形成一个字符串,不会自行百度String.format
                adminToken, 60 * 2,// 设置存放在redis中的时间
                TimeUnit.MINUTES); // 这个是时间的单位
        return new AdminLoginVO().setName(admin.getName()).setAdminToken(adminToken);
    }

创建token(根据userid)
1.创建一个空map
2. map中将userid put进去
3. 声明过期时间(integer,秒为单位)
4. 调用创建token(此方法的重写)根据map和过期时间

这段代码

 public static String createToken(String userId) {
        Map<String, String> mapClaim = new HashMap<>();
        mapClaim.put("userId", userId);
        Integer expireSeconds = 2* 60 * 60;// 这里设置过期时间为两个小时
        return createToken(mapClaim, expireSeconds);
    }

创建token(根据map和过期时间)

  1. 声明结果token(null)
  2. 声明一个空map()mapHeader(用来放token的头部信息)
  3. put(“alg”,“HS256”);put(“typ”,“JWT”)
  4. 创建JWT
    在这里插入图片描述
  5. 将mapHeader放到JWT中
    在这里插入图片描述
  6. 判断传过来的map(也就是放userid的那个map)是否为空
  7. 若不是空,遍历,将他的key和value分别放到JWT的key和value
  8. 判断过期时间是否为空并且是否大于0
  9. 如果大于零,设置JWT过期时间(根据自己业务需求)springboot +redis+token 实现登陆、登出_第1张图片
  10. 设置JWT颁发时间(现在)
    springboot +redis+token 实现登陆、登出_第2张图片
  11. 私钥加密并放入JWT中
    在这里插入图片描述
  12. 返回token

奉上代码

public static String createToken(Map<String, String> map, Integer expireSeconds) {
        String token = null;
        Map<String, Object> mapHeader = new HashMap<>();
        mapHeader.put("alg", "HS256");
        mapHeader.put("typ", "JWT");
        JWTCreator.Builder jwtBuilder = JWT.create();
        jwtBuilder.withHeader(mapHeader);

        if (map != null && !map.isEmpty()) {
            Set<Map.Entry<String, String>> setEntry = map.entrySet();
            for (Map.Entry<String, String> entry : setEntry) {
                jwtBuilder.withClaim(entry.getKey(), entry.getValue());
            }
        }

        if (expireSeconds != null && expireSeconds > 0) {
            Calendar nowTime = Calendar.getInstance();
            int calendarField = Calendar.SECOND;
            nowTime.add(calendarField, expireSeconds);
            Date expiresDate = nowTime.getTime();
            jwtBuilder.withExpiresAt(expiresDate);
        }
        jwtBuilder.withIssuedAt(new Date());

        try {
            token = jwtBuilder.sign(Algorithm.HMAC256(“GODZILLALOGINtoken”));
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (JWTCreationException e) {
            e.printStackTrace();
        }
        return token;
    }

记得引入JWT 的 maven:


    com.auth0</groupId>
    java-jwt</artifactId>
    3.8.3</version>
</dependency>

有关JWTtoken( json web token):

token,俗称令牌,其实就是一个字符串
JWTtoken由三部分组成,中间用“.”相隔,这三部分分别是头部,载荷、签名

头部信息:用于描述该JWT最基本的信息,如类型及签名所用的算法等
载荷:其实就是自定义的数据,如userid,过期时间(是JWT的核心,通过这些自定义的信息才可以确定唯一token)
签名: 头部和载荷分别base64加密后,拼接起来,就形成了xxx.xxx的前两段token
前两段加入一个秘钥用HS256或其他算法加密,就形成了最后一段token(所以最后一段token是在签名这形成的)

登出:

入参:token
出参:“登出成功”等……
思路:清掉redis里的token

service层

  1. 从header里拿到token
    在这里插入图片描述
  2. 根据token解密出userid
  3. 根据userid生成redis的key
  4. 删掉redis中这个key以及他对应的信息

代码

  public void logout() {
        String token = request.getHeader("token");
        String userId = JwtToken.getUserId(token);
        String key = String.format(GODZILLA:ADMIN:TOKEN:%s, userId);
        redisTemplate.delete(key);
    }

根据token解密出userid

  1. key写死,等于userid
  2. 声明一个map()值为解密token的Claim(通过token)
  3. 如果这个map等于null,则直接返回null
  4. 否则,拿到这个map中可以为userid的value值,即Claim
  5. 如果这个Claim为空或者为null,则直接返回null
  6. 否则返回这个Claim 的String形式(这里是因为在生成token的时候Claim中,也就是自定义信息中只有userid和过期时间,所以拿的时候可以直接写死userid)

此段代码

 public static String getValue(String token, String key) {
        Map<String, Claim> claims = getClaims(token);
        if (claims == null) {
            return null;
        }
        Claim valueClaim = claims.get(key);
        if (null == valueClaim || valueClaim.asString().equals("")) {
            return null;
        }
        return valueClaim.asString();
    }

解密token Claim(根据token)

  1. 声明JWT对象(解密token根据token)
  2. 返回(三目运算),如果JWT对象为null返回null,不为null返回JWT对象的Claim

代码奉上

   public static Map<String, Claim> getClaims(String token) {
        DecodedJWT jwt = verifyToken(token);
        return jwt == null ? null : jwt.getClaims();
    }

解密token根据token
声明JWT对象为null,
JWT对象实例化
判断JWT对象,为null返回null,不为null就返回JWT对象

代码来了

   public static DecodedJWT verifyToken(String token) {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
            // e.printStackTrace(); token 校验失败, 抛出Token验证非法异常
        }

        return jwt == null ? null : jwt;
    }

如此,登陆登出是不是清晰明了了呢,登陆光有这些还是不够的,还需要设置拦截器,只有通过登陆接口才能进去才是目的,登陆+拦截器才是完整的登陆!下篇文章讲springboot如何优雅的写出登陆拦截器,敬请期待
下一篇:springboot如何优雅写登陆拦截器

你可能感兴趣的:(java,登陆)