通用框架平台,每个分支对应子通用框架平台,禁止Merge不同分支!! 分支版本区别见项目内readme.md
whycxzp
2021-01-20 129bc8f0c9bce0306aa99fafde97689f645dbbb5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.whyc.manager;
 
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.whyc.config.JwtProperties;
import com.whyc.pojo.User;
import com.whyc.util.CommonUtil;
import com.whyc.util.EncodesUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
 
/**
 * JWT 管理:
 *  state-base: session,基于状态,用户登录状态 保存于Session中,server-client是关联的
 *  stateless-base: jwt,基于无状态,用户的状态 未保存在Session中,所以web请求可以由任何Server应答,发送的请求头要求包含jwt信息,以便服务器能理解状态
 *
 * JWT的应用场景:
 * 优点:
 *  1.基于jwt的特性:由于不可篡改和有效期固定,非常适合用于一次性验证
 *  2.用户禁用cookie时,可以使用
 *  3.在分布式会话中,某些场景可以使用jwt,因为是无状态的.
 *  4.前后端分离,cookie在跨域访问禁止携带,jwt适用于跨域认证
 * 缺点:
 *  1.如果做会话管理:
 *  -无法续签,用于会话管理需要继承其他框架比如spring security auth2 jwt
 *  -做会话管理的话,如果cookie泄露,无法拒绝 jwt验证,因为是计算机计算校验合法性;而session的话清除session可以导致登录验证失败
 *  -jwt字段长,会造成http请求开销变大
 *
 * 本项目使用jwt,需要继承续签框架
 *
 */
@Component
@Slf4j
public class JWTManager {
 
    @Autowired
    JwtProperties jwtProperties;
 
    /**
     * 签发令牌:
     *  jwt字符串三部分:
     *  1.header
     *      -字符串类型,一般为"JWT"
     *      -算法加密类型,一般为"HS256"或其他加密算法
     *  2.payload
     *      常见以下四个标准字段:
     *      st:签发时间
     *      jti:JWT唯一标识
     *      signer:签发人,一般为username或者userId
     *      exp:过期时间
     *  3.signature
     *      对1和2进行header中的算法加入签证秘钥,防止令牌被篡改
     * @param signer 签发人
     * @param ttlMillis 令牌有效时长
     * @param sessionId 唯一标识
     * @param claims 声明map,可携带其他自定义信息
     * @return
     */
    public String issueToken(String signer, long ttlMillis, String sessionId, Map<String,Object> claims){
        if(claims==null){
            claims = new HashMap<>();
        }
        long mowMillis = System.currentTimeMillis();
        //签名秘钥
        String hexEncodedSecretKey = EncodesUtil.encodeHex(this.jwtProperties.getHexEncodedSecretKey().getBytes());
 
        JwtBuilder jwtBuilder = Jwts.builder()
                .setClaims(claims)
                //唯一标识
                .setId(sessionId)
                //签发时间
                .setIssuedAt(new Date(mowMillis))
                //签发人
                .setSubject(signer)
                //签证秘钥
                .signWith(SignatureAlgorithm.HS256, hexEncodedSecretKey.getBytes());
        if(ttlMillis>=0) {
            //过期时间
            long expMillis = mowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            jwtBuilder.setExpiration(exp);
        }
 
        return jwtBuilder.compact();
    }
 
    /**
     * 解析令牌
     * @param jwt 令牌
     * @return
     */
    public Claims decodeToken(String jwt){
        String encodeHex = EncodesUtil.encodeHex(jwtProperties.getHexEncodedSecretKey().getBytes());
        //得到DefaultJwtParser
        return Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(encodeHex.getBytes())
                //可以偏移24h,过期1天内可以获取.超过1天会登录失败
                .setAllowedClockSkewSeconds(86400000)
                //设置需要解析的jwt
                .parseClaimsJws(jwt)
                .getBody();
 
    }
 
    /**
     * 判断令牌是否合法
     * @param jwt 令牌
     * @return
     */
    public boolean verifyToken(String jwt, ServletRequest request){
        boolean res = false;
        String encodeHex = EncodesUtil.encodeHex(jwtProperties.getHexEncodedSecretKey().getBytes());
        //这是官方的校验规则,可以自定义
        Algorithm algorithm = Algorithm.HMAC256(encodeHex);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            //校验不通过会抛出异常
            //判断合法的标准:1.header和payload没有被篡改
            //             2.没有过期
            //增加:首先判断jwt是否有效,在内存中存在,实际上已经将jwt状态化了
            List jwts = (List) request.getServletContext().getAttribute("jwts");
            if(jwts!=null && jwts.contains(jwt)) {
                jwtVerifier.verify(jwt);
                res = true;
            }
        }catch (TokenExpiredException expiredException){
            log.error("expiredException");
            //对于空闲时间小于30分钟的,过期了也可以通过校验,变相续签
            Claims claims = decodeToken(jwt);
            String userJson = (String) claims.get("user");
            User user = CommonUtil.toUser(userJson);
            Date date = new Date();
            //System.out.println("当前时间:"+date);
            Date date1 = new Date((Long) request.getServletContext().getAttribute("exp_" + user.getName()));
            //System.out.println("最新活跃时间:"+date1);
            if (System.currentTimeMillis()-(Long) request.getServletContext().getAttribute("exp_" + user.getName())  <= 180000) {
                //System.out.println("距离最新活跃时间小于3分钟");
                res = true;
            }
        }catch (Exception e){
            log.error("exception");
        }
        return res;
    }
 
}