Token验证

Token验证

学妹最近写项目,卡在了token认证上面,正好之前写过类似的,就乘此契机整理一份。

整个模块主要完成token认证、授权以及使用redis进行缓存。

很多内容都在代码里面的注释中,本文主要讲细节实现,在食用之前,请务必要对token验证流程有一个大致的印象。

话不多说,下面开锤!

pom.xml配置如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-demo-auth</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spring-cloud-demo-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

首先定义一个Token实体类

package com.demo.commons.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author: yokna
 * @apiNote: token基础类
 * @date: 2021/1/14
 * @time: 9:58
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Token implements Serializable {
    //String存储token值
    private String token;
    //设置过期时间
    private Long exp;
    //刷新token
    private String refreshToken;
    //具体的用户实体类
    private User user;
}

来看看user类里面存的都有哪些信息

package com.demo.commons.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author: yokna
 * @apiNote:
 * @date: 2021/1/14
 * @time: 10:40
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
// 表id 真实姓名  用户姓名 密码 角色
public class User implements Serializable {
    private Integer id;
    private String realname;
    private String username;
    private String password;
    private String role;
}

UserMapper

有了user,必然会有userMapper嘛,我用的是MybatisPlus,它封装了一些基本的查询,简单的查询不需要写sql语句,只需要传一个HashMap值,或者构造一个查询对象,特别方便。

package com.demo.auth.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.commons.domain.User;
import org.springframework.stereotype.Repository;

/**
 * @author: yokna
 * @apiNote:
 * @date: 2021/1/14
 * @time: 14:38
 */
@Repository
public interface UserMapper extends BaseMapper<User> {
}

接下来是tokenUtils

package com.demo.auth.util;

import com.demo.commons.domain.Commons;
import com.demo.commons.domain.Token;
import com.demo.commons.domain.User;
import com.demo.commons.utils.RedisUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Base64;
import java.util.concurrent.TimeUnit;

/**
 * @author: yokna
 * @apiNote: 用于token的工具类
 * @date: 2021/1/14
 * @time: 9:55
 */
@Component
public class TokenUtils {

    /**
     * 存储时间
     */
    private static final long DEAD_TIME = 1000 * 60 * 31;

    //注入redisUtils 用redis来存储token
    @Autowired
    private RedisUtils redisUtils;

    /**
     * 创建token,返回一个token对象,token对象里有四个属性,分别是tokenStr、过期时间、刷新token、以及user对象
     *所以我们在创建token时需要对这四个属性赋值
     * @param user 传入的用户对象
     * @return token
     */
    public Token createToken(User user) {
        //创建tokenStr
        String tokenStr = createTokenStr(user);
        //创建refreshToken,并且通过MD5加密加盐,MD5Encoder.encode()需要传一个byte[]二进制数组,此时我们将用户名+系统当前时间+token秘钥作为参数穿进去,然后截取0-16位,getbytes是以系统默认的编码格式获取String
        String refreshToken = MD5Encoder.encode((user.getUsername() + "@" + DateUtils.nowTime() + "#" + Commons.REFRESH_TOKEN_SECRET).substring(0, 16).getBytes());
        //上面我们得到了两个最重要的参数,tokenStr以及refreshToken,加上传参user及静态常量DEAD_TIME,就可以构建一个token
        Token token = new Token(tokenStr, System.currentTimeMillis() + DEAD_TIME, refreshToken, user);

        //然后用redisUtils将token存储起来,后两个参数是token在redis中的过期时间
        redisUtils.storeValue(tokenStr, token, TimeUnit.MILLISECONDS, DEAD_TIME);
        return token;
    }


    /**
     * 刷新token
     *
     * @param accessToken  token字符串
     * @param refreshToken 刷新token 的字符串
     * @return token
     */
    public Token refreshToken(String accessToken, String refreshToken) {
        //获取未过期的tokenStr,redis中存储的是K-V K是tokneStr V是token对象转换的json(我猜是json,具体是什么,不清楚)
        Token token = (Token) redisUtils.getValue(accessToken);
        //如果是空,就返回空,说明用户没有token,或者之前的token过期,在redis中被删掉
        if (token == null) return token;
        //判断refreshToken是否一致
        if (!StringUtils.isEmpty(refreshToken) && refreshToken.equals(token.getRefreshToken())) {
            //重新创建token字符串
            String tokenStr = createTokenStr(token.getUser());
            //刷新token字符串
            token.setToken(tokenStr);
            //刷新过期时间
            token.setExp(System.currentTimeMillis() + DEAD_TIME);
            //重新存储
            redisUtils.storeValue(tokenStr, token, TimeUnit.MILLISECONDS, DEAD_TIME);
            //删除之前的token
            redisUtils.delByKey(accessToken);
        }
        return token;
    }

    /**
     * 鉴权
     *
     * @return boolean
     */
    public boolean accessToken(String accessToken, String role) {
        //获取未过期的token
        Token token = (Token) redisUtils.getValue(accessToken);
        if (token != null && token.getUser().getRole().equals(role)) {
            return true;
        }
        return false;
    }

    private String createTokenStr(User user) {
        //通过Base64加密tokenStr,生成tokenStr
        return new String(
                Base64.getEncoder().encodeToString(
                        (user.getUsername() + "@" + DateUtils.nowTime() + " #" + Commons.SECRET + "$" + user.getRole()).getBytes())
        );
    }

}

redisUtils工具类主要用来初始化redis、存储数据、取数据。

package com.demo.commons.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author: yokna
 * @apiNote: 用于redis 的工具类
 * @date: 2021/1/14
 * @time: 9:48
 */
@Component
public class RedisUtils {

    //注入redis封装好的工具
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 重置redis
     */
    //基本是固定写法
    public void initRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        redisTemplate = new RedisTemplate <String, Object>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
    }


    /**
     * 删除
     *
     * @param key 关键字
     */
    public void delByKey(String key) {
        redisTemplate.delete(key);
    }


//    ---------------key val

    /**
     * 存储key val
     *
     * @param key      关键字
     * @param target   目标数据
     * @param timeUnit 时间类型
     * @param time     时间
     */
    public void storeValue(String key, Object target, TimeUnit timeUnit, long time) {
        redisTemplate.opsForValue().setIfAbsent(key, target, time, timeUnit);
    }


    /**
     * 获取value
     *
     * @param key 关键字
     * @return Object
     */
    public Object getValue(String key) {
        return redisTemplate.opsForValue().get(key);
    }


}

下面就是tokenService

package com.demo.auth.service.impl;

import com.demo.auth.mapper.UserMapper;
import com.demo.auth.service.TokenService;
import com.demo.auth.util.TokenUtils;
import com.demo.commons.domain.Commons;
import com.demo.commons.domain.Token;
import com.demo.commons.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;

/**
 * @author: yokna
 * @apiNote:
 * @date: 2021/1/14
 * @time: 14:56
 */
@Service
public class TokenServiceImpl implements TokenService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TokenUtils tokenUtils;


    @Override
    public Token authLogin(String username, String password) {
        //登录,username与password
        HashMap<String,Object> map = new HashMap<>();
        //构造mybatisplus的查询map
        map.put("username",username);
        List<User> list = userMapper.selectByMap(map);
        User user = list.get(0);
        //判断相关重要数据,抛两个异常--用户不存在或者用户密码错误
        if (user == null) {
            throw new RuntimeException(Commons.USER_NOT_FOUND);
        } else if (!user.getPassword().equals(password)) {
            throw new RuntimeException(Commons.PASSWORD_ERROR);
        }
        //经过上面的判断过滤后,到这里的用户是合法用户了,为了安全,把密码置空
        user.setPassword(null);
        //获取token
        return tokenUtils.createToken(user);
    }

    @Override
    public String grantTypeCode(String buildName) {
        return null;
    }

    @Override
    public Token refreshToken(String accessToken, String refreshTokenStr) {
        return tokenUtils.refreshToken(accessToken, refreshTokenStr);
    }
}

最后是我们的Controller层

package com.demo.auth.controller;

import com.demo.auth.service.TokenService;
import com.demo.commons.domain.Result;
import com.demo.commons.domain.Token;
import com.demo.commons.utils.ResultUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: yokna
 * @apiNote:
 * @date: 2021/1/14
 * @time: 15:07
 */
@RestController
@RequestMapping("/auth")
public class AuthTokenController {
    @Autowired
    private TokenService tokenService;

    @RequestMapping(value = "/token/password", method = {RequestMethod.GET, RequestMethod.POST})
    public Result passwordGrantType(String username, String password) {

        try {
            Token token = tokenService.authLogin(username, password);
            return ResultUtils.success(token);
        } catch (RuntimeException e) {
            String errorMessage = e.getMessage();
            return ResultUtils.fail(errorMessage, null);
        }
    }

    @RequestMapping(value = "/token/refresh_token", method = {RequestMethod.GET, RequestMethod.POST})
    public Result refreshToken(String accessToken, String refreshToken) {
        Token token = null;
        if ((token = tokenService.refreshToken(accessToken, refreshToken)) != null) {
            return ResultUtils.success(token);
        }
        return ResultUtils.fail(token);
    }
}




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值