SpringCloud第14讲:(番外篇)JWT

        JWT全称JSON Web Token,是一个开放标准(RFC7519),用来在各方之间安全地传输信息。JWT可被验证和信任,因为他是数字签名的。

一、JWT的组成

组成作用内容示例
Header(头)记录令牌类型、签名的算法等{"alg":"HS256", "type":"JWT"}
Payload(有效载荷)携带一些用户信息{"userid":"1", "username":"admin"}
Signature(签名)防止token被篡改,确保安全性计算出来的签名,一个字符串

二、JWT工具类

参考:JWT操作工具类分享_慕课手记

三、为用户中心和内容中心整合JWT

3.1、在pom.xml中添加依赖

<!--JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.7</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.7</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.7</version>
    <scope>runtime</scope>
</dependency>
     <!-- 添加jedis:Begin -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

 3.2、在application.yml中添加属性

spring:
  redis:
    # redis服务器ip
    host: 127.0.0.1
    # redis服务器端口号
    port: 6379
    # redis数据库密码
    password:
    # redis连接池配置
    jedis:
      pool:
        # 设置连接池中最大连接数
        max-active: 10000
        # 设置连接池中最大空闲连接数
        max-idle: 50
jwt:
  secret: aaaaaaabbbbbbcccccdddddaaaaaaabbbbbbcccccdddddaaaaaaabbbbbbcccccddddd
  # token有效期,单位秒,默认2周
  expire-time-in-second: 1209600

 3.3、添加JWT生成类JwtOperator

package com.example.interceptor.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
@SuppressWarnings("WeakerAccess")
@Component
public class JwtOperator {
    /**
     * 秘钥
     * - 默认aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt
     */
    @Value("${jwt.secret:aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt}")
    private String secret;
    /**
     * token有效期,单位秒
     * - 默认30分钟
     */
    @Value("${jwt.expire-time-in-second:1209600}")
    private Long expirationTimeInSecond;

    /**
     * 按要求生成token和refresh_token
     *
     * @param claims
     * @return token
     */
    public String generateTokens(Map<String, Object> claims){

        long currentTime = System.currentTimeMillis();
        Date tokenExpirationTime = new Date(currentTime + this.expirationTimeInSecond * 1000);

        String token = generateToken(claims, tokenExpirationTime);//生成token

        return token;
    }

    /**
     * 判断token是否过期
     *
     * @param token token
     * @return 未过期返回true,否则返回false
     */
    public Boolean validateTokenTime(String token) {
        Date expiration = getExpirationDateFromToken(token);
        return expiration.after(new Date()); //获取时间的毫秒数 > 当前时间的毫秒数 = true
    }

    /**
     * 从token中获取claim
     *
     * @param token token
     * @return claim
     */
    public Claims getClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(this.secret.getBytes())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
            log.error("token无效");
            throw new IllegalArgumentException("Token invalided.");
        }
    }

    /**
     * 生成token
     *
     * @param claims 用户信息
     * @param expirationTime 过期时间
     * @return token
     */
    private String generateToken(Map<String, Object> claims, Date expirationTime) {
        Date createdTime = new Date();

        byte[] keyBytes = secret.getBytes();
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdTime)
                .setExpiration(expirationTime)
                // 你也可以改用你喜欢的算法
                // 支持的算法详见:https://github.com/jwtk/jjwt#features
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 获取token的过期时间
     *
     * @param token token
     * @return 过期时间
     */
    private Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token).getExpiration();
    }

}

 3.4、添加JWT生成类RedisHelper

package com.example.interceptor.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Slf4j
@Component
public class RedisHelper {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    @Value("${spring.redis.password}")
    private String redisPassword;

    @Value("${spring.redis.jedis.pool.max-active}")
    private String redisMaxActive;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private String redisMaxIdle;

    private JedisPool jedisPool; //连接池

    /**
     * 向Redis服务器中添加数据
     *
     * @param key
     * @param value
     * @return
     */
    public String setValue(String key, String value) {
        return getJedis().set(key, value);
    }

    /**
     * 根据Key获取Redis中保存的数据
     *
     * @param key
     * @return
     */
    public String getValue(String key) {
        return getJedis().get(key);
    }

    /**
     * 根据Key删除Redis中保存的数据
     *
     * @param key
     * @return
     */
    public Long removeValue(String key) {
        return getJedis().del(key);
    }

    /**
     * 获得Jedis对象
     *
     * @return
     */
    private Jedis getJedis() {
//		log.info("redisHost:"+redisHost);
//		log.info("redisPort:"+redisPort);
//		log.info("redisPassword:"+redisPassword);
        if(jedisPool == null) {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(Integer.parseInt(redisMaxActive)); //设置最大连接数
            jedisPoolConfig.setMaxIdle(Integer.parseInt(redisMaxIdle)); //设置最大空闲连接数
            jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort); //ip+端口号
        }
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if(!"".equals(redisPassword)){
                jedis.auth(redisPassword);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(jedis != null) {
                jedis.close(); //关闭jedis连接
            }
            if(jedisPool != null) {
                jedisPool.close(); //关闭jedisPool连接
                jedisPool = null;
            }
        }
        return jedis;
    }
}

实体类

package com.example.interceptor.entity;

import lombok.Data;

@Data
public class User {
    private String id;
    private String username;
    private String password;
}

service 

package com.example.interceptor.service;

import com.example.interceptor.utils.JwtOperator;
import com.example.interceptor.utils.RedisHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
public class UserService {

    @Autowired
    private RedisHelper redisHelper;

    @Autowired
    private JwtOperator jwtOperator;

    public String validUser(String username, String password){
        String token = null;
        if("admin".equals(username) && "123".equals(password)){
            String id = "aaaaabbbcc";
            //生成token
            Map<String,Object> param = new HashMap<>();
            param.put("userId",id);
            token = jwtOperator.generateTokens(param);
            log.info("生成得token是:"+token);

            //将token保存到redis中
            redisHelper.setValue(id,token);
        }
        return token;
    }
}
TestInterceptor
package com.example.interceptor.interceptor;


import com.example.interceptor.utils.JwtOperator;
import com.example.interceptor.utils.RedisHelper;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Configuration
@Slf4j
public class TestInterceptor extends WebMvcConfigurerAdapter {

    @Autowired
    private RedisHelper redisHelper;

    @Autowired
    private JwtOperator jwtOperator;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        HandlerInterceptor handlerInterceptor = new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                log.info("preHandle请求Controller之前触发,返回值为true代表放行,false代表拦截");

                boolean isOk = false;

                String servletPath = request.getServletPath();//获取请求得路径
                if(servletPath.contains("/user/login")){
                    isOk = true;//放行
                } else {
                    try {
                        String token = request.getHeader("Authorization");
                        boolean isExpire = jwtOperator.validateTokenTime(token);
                        if(isExpire){//验证token是否被篡改,是否过期
                            Claims info = jwtOperator.getClaimsFromToken(token);
                            String[] str = info.toString().replace("{","").replace("}","").split(",");
                            String userId = str[0].replace("userId=","");

                            //从Redis中获取token,通过userId
                            String tokenFromRedis = redisHelper.getValue(userId);
                            if(tokenFromRedis.equals(token)){
                                isOk = true;
                            }
                        }
                    } catch (Exception e) {
                            response.getWriter().write("token is disabled");
                    }
                }

                return isOk;
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                log.info("posHandle请求Controller之后执行");
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                log.info("afterCompletion方法在整个Request请求完成后执行");
            }
        };
        registry.addInterceptor(handlerInterceptor);
    }
}

controller 

package com.example.interceptor.controller;

import com.example.interceptor.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/login/{username}/{password}")
    public String login(@PathVariable String username,@PathVariable String password){
        String token = userService.validUser(username, password);
        if(token != null){
            return "登录成功";
        } else {
            return "登录失败";
        }
    }

    @GetMapping("/list")
    public String[] userList(){
        String[] users = {"amdin","tom","mike"};
        return users;
    }
}