1、首先最基础的User实体类,使用了lombok,所以省略了getter、setter方法
@Data
public class UserInfo implements Serializable {
    private Integer id;
    //用户名
    private String username;
    //密码不需要被序列化存入redis
    private transient String password;
    //登录过期时间
    private Long expireTime;
    //登录成功后的token
    private String token;
    //权限集
    private Set<String> permissions;
    public UserInfo() {
    }
    public UserInfo(String username, String password) {
        this.username = username;
        this.password = password;
    }
    public UserInfo(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
} 
2、redis基础操作,登录成功后,信息保存到redis中
@Component
public class RedisCache {
    @Autowired
    public RedisTemplate redisTemplate;
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
    /**
     * 缓存带有过期时间的对象
     * @param key       缓存的键值
     * @param value     缓存的值
     * @param time      过期时间
     * @param timeUnit  时间单位
     */
    public <T> void setCacheObjectExpire(final String key, final T value, final Long time, final TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key,value,time,timeUnit);
    }
}
 
3、登录成功后token相关处理
@Component
public class JwtTokenService {
    @Autowired
    RedisCache redisCache;
    //前端请求token对应的header中的key
    private final static String TOKEN_KEY =  "C-Token";
    //jwt加密密钥
    private final static String SIGNING_KEY =  "xice202304181537";
    //密码过期时间
    private final static Long EXPIRE_TIME = 30L;
    //密码过期时间单位
    private final static TimeUnit EXPIRE_TIME_UNIT = TimeUnit.MINUTES;
    //token刷新时间间隔
    private final static Long REFRESH_EXPIRE_TIME = 20 * 60 * 1000L;
    /**
     * 登录成功,生成token
     * @param userInfo
     * @return
     */
    public String createToken(UserInfo userInfo){
        //生成uuid,用着redis的key、token
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        //生成token
        Map claims = new HashMap(1);
        claims.put(TOKEN_KEY,uuid);
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, SIGNING_KEY).compact();
        //保存token
        userInfo.setToken(token);
        //这是过期时间
        userInfo.setExpireTime(System.currentTimeMillis() + REFRESH_EXPIRE_TIME * 2);
        //获取权限资源,此处应该查询数据库,方便测试,直接写死
        Set<String> permissions = new HashSet<>();
        permissions.add("/user/getUserInfo");
        userInfo.setPermissions(permissions);
        //存入redis
        redisCache.setCacheObjectExpire(uuid,userInfo,EXPIRE_TIME,EXPIRE_TIME_UNIT);
        return token;
    }
    /**
     * 根据token获取用户信息
     * @param request
     * @return
     */
    public UserInfo getUserByToken(HttpServletRequest request){
        String token = request.getHeader(TOKEN_KEY);
        Claims claims = Jwts.parser()
                .setSigningKey(SIGNING_KEY)
                .parseClaimsJws(token)
                .getBody();
        //解密token
        String uuid = (String) claims.get(TOKEN_KEY);
        //通过uuid获取redis中存的用户信息
        return redisCache.getCacheObject(uuid);
    }
    /**
     * 刷新token
     * @param userInfo
     */
    public void refreshToken(UserInfo userInfo){
        long now = System.currentTimeMillis();
        //当过期时间与当时时间小于指定的刷新时间间隔是,延长redis中信息的时间
        if(userInfo.getExpireTime() - now  <= REFRESH_EXPIRE_TIME){
            System.out.println("刷新token...");
            redisCache.setCacheObjectExpire(userInfo.getToken(),userInfo,EXPIRE_TIME,EXPIRE_TIME_UNIT);
        }
    }
} 
4、声明注解,只有加了自定义注解的方法,才进行权限验证
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CAuth {
} 
5、使用HandlerInterceptor拦截器进行权限验证,详细方法
@Configuration
public class AuthConfig implements HandlerInterceptor {
    @Autowired
    JwtTokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //静态资源不拦截
        if(handler instanceof ResourceHttpRequestHandler){
            return true;
        }
        //获取注解
        Annotation[] annotations = ((HandlerMethod) handler).getMethod().getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            //添加了CAuth注解的方法需要登录后才能访问
            if(annotation.annotationType().isAssignableFrom(CAuth.class)){
                //查看是否登录
                UserInfo loginUser = tokenService.getUserByToken(request);
                if(ObjectUtils.isEmpty(loginUser)){
                    response.setContentType("text/html;charset=utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.print("请先登录!");
                    writer.flush();
                    writer.close();
                    return false;
                }else{
                    //判断是否有权限访问当前资源
                    String requestURI = request.getRequestURI();
                    return loginUser.getPermissions().stream().allMatch(perm -> {
                        if (requestURI.equalsIgnoreCase(perm)) {
                            return true;
                        }
                        response.setContentType("text/html;charset=utf-8");
                        PrintWriter writer = null;
                        try {
                            writer = response.getWriter();
                            writer.print("您没有权限进行此操作!");
                            writer.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            writer.close();
                        }
                        return false;
                    });
                }
            }
        }
        return true;
    }
} 
6、添加拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    AuthConfig authConfig;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authConfig);
    }
} 
7、登录和验证方法
@RestController
@RequestMapping("user")
public class UserInfoController {
    @Autowired
    JwtTokenService jwtTokenService;
    @RequestMapping("login")
    public String login(@RequestBody UserInfo userInfo){
        if("xice".equals(userInfo.getUsername())&&"123456".equals(userInfo.getPassword())){
            String token = jwtTokenService.createToken(userInfo);
            return token;
        }else{
            return "用户名或密码不正确!";
        }
    }
    @CAuth
    @RequestMapping("getUserInfo")
    public UserInfo getUserInfo(HttpServletRequest request){
        UserInfo userInfo = jwtTokenService.getUserByToken(request);
        jwtTokenService.refreshToken(userInfo);
        return userInfo;
    }
    @CAuth
    @RequestMapping("test")
    public void test(HttpServletRequest request){
        System.out.println("test。。。");
    }
} 
登录请求 /user/login 没有添加 @CAuth注解,所以不会进行拦截,登录成功后返回一个token值,后续请求需要在header中增加C-Token:token参数

/user/getUserInfo:登陆后有此资源权限,可以获取到当前用户信息

/user/test:无此资源权限,会返回 “您没有权限进行此操作!”


















