可爱静

记录生活、学习和工作

0%

JJWT框架的使用

JWT介绍

JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发送这样的信息,可以被信任为不可读(即加密的)、不可修改的(即签名)和URL - safe(即Base64编码的)。

使用场景

  • Authorization (授权) :一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。

  • Information Exchange (信息交换):对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

JWT的组成

  1. Header:标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型;

  2. Claims:Claims包含想要签署的任何信息;

  3. JSON Web Signature (JWS):在Header中指定的使用该算法的数字签名和声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Header:
{
"alg": "HS256",
"typ": "JWT"
}

Claims:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

Signature:
base64UrlEncode(Header) + "." + base64UrlEncode(Claims),

JWT的框架:JJWT

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

开始上手

demo-jjwt

  • 创建maven项目;

  • 导入依赖

1
2
3
4
5
6
<!-- jjwt  -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
  • JwtUtils
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
@Slf4j
public class JwtUtils {
public static final String ACCESS_TOKEN_HEADER = "Authorization";
public static final String REFRESH_TOKEN_HEADER = "Refresh-Token";
public static final String TOKEN_PREFIX = "Bearer ";

/**
* 默认的 用户续期时长(时间戳) 7天
*/
private static final Long DEF_RENEWAL_TIMESTAMP = 7 * 24 * 60 * 60 * 1L;

/**
* 默认的 校验时间间隔(时间戳)
*/
private static final Long DEF_CHECK_INTERVAL = 1800L;

/**
* 默认的 jwt 过期时间 1天
*/
private static final Long DEF_EXPIRATION = 24 * 60 * 60 * 1000L;
/**
* 签发的密钥
*/
private static final String SECRET = "demo.jjwt.token";
/**
* 签发单位
*/
private static final String DEF_ISS = "https://www.keaijing.com";

/**
* 令牌用户信息
*/
private static final String U_DATA = "U";


/**
* 签发token
*/
public static String issueJwt(Long phoneNum) {
Assert.notNull(phoneNum, "phoneNum is null");
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(phoneNum + "")
.setIssuer(DEF_ISS)
.setIssuedAt(DateUtil.date())
.setExpiration(DateUtil.date(System.currentTimeMillis() + DEF_EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}

/**
* 检查是否过期
*/
public static boolean checkJwtExpiration(String jwt) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(jwt.trim())
.getBody();
} catch (RuntimeException e) {
return true;
}
if (null == claims) {
return true;
}
try {
return claims.getExpiration().before(new Date());
} catch (RuntimeException ex) {
log.warn("jwt 过期啦~~~~");
}
return true;
}

public static void main(String[] args) {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzYzMzgyMzk4MSIsImlzcyI6Imh0dHBzOi8vd3d3LmtlYWlqaW5nLmNvbSIsImlhdCI6MTY0NDIyMjAwMSwiZXhwIjoxNjQ0MzA4NDAxfQ.WBX6S1HQZqZtUsjH7XGKlIRWoNCmSSLq7nn24jXSbo4PCTIqSj-Uo6XiEjhda5-Vo91_lhnwSXCb2qVctFNqjQ";
// log.info(issueJwt(17612343344L));
log.info("" + checkJwtExpiration(token));
}
}

进阶操作

JwtAuthInfo

在用户Token里存储用户信息,方便解析后直接进行校验。

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
/**
* jwt用户信息
*
* @author HiF
* @date 2022/2/9 13:49
*/
@Data
@Builder
public class JwtAuthInfo {

/**
* 用户id
*/
private String usrId;

/**
* 用户账号
*/
private String accountNumber;

/**
* 用户别称
*/
private String nickName;

/**
* 校验时间间隔(时间戳)
*/
private Long checkInterval;

/**
* jwt 过期时间
*/
private Long expiration;
}

JwtAuthManager

创建JwtAuthManager用户权限管理类,用于校验Token格式、签发Toekn等操作。

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
* jwt用户权限管理
*
* @author HiF
* @date 2022/2/9 14:03
*/
@Slf4j
public class JwtAuthManager {
public static final String ACCESS_TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";

/**
* 默认的 校验时间间隔(时间戳)
*/
private static final Long DEF_CHECK_INTERVAL = 1800L;

/**
* 默认的 jwt 过期时间 1天
*/
private static final Long DEF_EXPIRATION = 24 * 60 * 60 * 1L;
/**
* 签发的密钥
*/
private static final String SECRET = "demo.jjwt.token";
/**
* 签发单位
*/
private static final String DEF_ISS = "https://www.keaijing.com";

/**
* 令牌用户信息
*/
private static final String U_DATA = "U";

public static JwtAuthInfo getJwtAuthentication(HttpServletRequest request) {
String token = getJwt(request);
return getJwtAuthInfo(token);
}

private static String getJwt(HttpServletRequest request) {
String tokenHeader = request.getHeader(JwtAuthManager.ACCESS_TOKEN_HEADER);
if (tokenHeader == null || !tokenHeader.startsWith(JwtAuthManager.TOKEN_PREFIX)) {
return null;
}
String token = tokenHeader.replace(JwtAuthManager.TOKEN_PREFIX, "");
if (Strings.isEmpty(token)) {
return null;
}
return token.trim();
}

public static JwtAuthInfo getJwtAuthInfo(String token) {
if (null == token) {
log.error(" header 中未找到 jwt信息");
throw new BizException(MessageCodeEnum.NOT_LOGIN);
}
token = token.trim();
JwtAuthInfo JwtAuthInfo = JwtAuthInfo(token);
if (null == JwtAuthInfo) {
log.error(" jwt 解析失败");
throw new BizException(MessageCodeEnum.NOT_LOGIN);
}
return JwtAuthInfo;
}

public static JwtAuthInfo JwtAuthInfo(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.trim())
.getBody();
} catch (Exception e) {
log.warn("token 检查失败,无法刷新token。");
return null;
}
Gson gson = new Gson();
Object obj = claims.get(U_DATA);
if (null == obj) {
return null;
}
return gson.fromJson(obj.toString(), JwtAuthInfo.class);
}

/**
* 签发token
*
* @param userId
* @param checkInterval
* @param expiration
* @return java.lang.String
* @author HiF
* @date 2022/2/9 14:12
*/
public static String issueJwt(String userId,
Long checkInterval,
Long expiration) {
Assert.notNull(userId, "userId is null");
Assert.notNull(checkInterval, "checkInterval is null");
Assert.notNull(expiration, "expiration is null");
JwtAuthInfo tokenInfo = JwtAuthInfo.builder()
.usrId(userId)
.checkInterval(checkInterval)
.expiration(expiration)
.build();
return issueJwt(tokenInfo);
}

public static String issueJwt(JwtAuthInfo JwtAuthInfo) {
Gson gson = new Gson();
String u = gson.toJson(JwtAuthInfo);
HashMap<String, Object> map = new HashMap<>();
map.put(U_DATA, u);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(DEF_ISS)
.setSubject(String.valueOf(JwtAuthInfo.getUsrId()))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + JwtAuthInfo.getExpiration() * 1000))
.compact();
}

/**
* 检查是否过期
*
* @param jwt true 过期 false未过期
* @return
*/
public static boolean checkJwtExpiration(String jwt) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(jwt.trim())
.getBody();
} catch (RuntimeException e) {
return true;
}
if (null == claims) {
return true;
}
try {
return claims.getExpiration().before(new Date());
} catch (RuntimeException ex) {
log.warn("jwt 过期啦~~~~");
}
return true;
}


public static String refresh(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.trim())
.getBody();
} catch (Exception e) {
log.warn("token 检查失败,无法刷新token。");
return null;
}
try {
claims.getExpiration().before(new Date());
} catch (RuntimeException ex) {
log.warn("jwt 过期啦~~~~");
return null;
}
Object u = claims.get(U_DATA);
if (null == u) {
log.warn("jwt 无效 ~~~~");
return null;
}
Gson gson = new Gson();
JwtAuthInfo tokenInfo = gson.fromJson(u.toString(), JwtAuthInfo.class);
Long expiration = tokenInfo.getExpiration();
HashMap<String, Object> map = new HashMap<>(1);
map.put(U_DATA, u);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(DEF_ISS)
.setSubject(String.valueOf(tokenInfo.getUsrId()))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}

/**
* 创建默认条件到U_DATA
*
* @param usrId
* @return com.heifan.demo.config.auth.JwtAuthInfo
* @author HiF
* @date 2022/2/9 14:12
*/
public static JwtAuthInfo createJwtAuthInfo(String usrId) {
return JwtAuthInfo.builder()
.usrId(usrId)
.checkInterval(DEF_CHECK_INTERVAL)
.expiration(DEF_EXPIRATION)
.build();
}
}

JwtAuthorizationManager

创建jwt认证类,用于校验账号在线状态以及解析Token里的用户信息。

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
/**
* jwt认证
* @author HiF
* @date 2022/2/9 14:17
*/
@Component
@Slf4j
public class JwtAuthorizationManager {

public static final String ACCOUNT_AUTH_ONLINE_KEY = "demo:jwt:";

@Autowired
RedisCacheServiceI redisCacheService;

public String authorization(HttpServletRequest request) {
String token = getJwt(request);
if (null == token) {
return null;
}
return authorization(token);
}

private String getJwt(HttpServletRequest request) {
String tokenHeader = request.getHeader(JwtAuthManager.ACCESS_TOKEN_HEADER);
if (tokenHeader == null || !tokenHeader.startsWith(JwtAuthManager.TOKEN_PREFIX)) {
return null;
}
String token = tokenHeader.replace(JwtAuthManager.TOKEN_PREFIX, "");
if (Strings.isEmpty(token)) {
return null;
}
return token.trim();
}

public String authorization(String tokenHeader) {
String token = tokenHeader.replace(JwtAuthManager.TOKEN_PREFIX, "");
JwtAuthInfo jwtAuthInfo = getJwtAuthInfo(token);
online(token, jwtAuthInfo);
return jwtAuthInfo.getAccountNumber();
}

/**
* 校验token
* @param token
* @param jwtAuthInfo
* @return void
* @author HiF
* @date 2022/2/9 14:22
*/
public void online(String token, JwtAuthInfo jwtAuthInfo){

String cacheKey = ACCOUNT_AUTH_ONLINE_KEY + jwtAuthInfo.getAccountNumber();
String online = redisCacheService.getStr(cacheKey);

if (null == online) {
log.error("账号未登录");
throw new BizException(MessageCodeEnum.NOT_LOGIN);
}
if (!online.equals(token)) {
log.error("账号其他设备登录");
throw new BizException(MessageCodeEnum.USER_OFFLINE);
}
}

private JwtAuthInfo getJwtAuthInfo(String token) {
if (null == token) {
log.error(" header 中未找到 jwt信息");
throw new ParamException(MessageCodeEnum.NOT_LOGIN);
}
token = token.trim();
JwtAuthInfo jwtAuthInfo = JwtAuthManager.getJwtAuthInfo(token);
if (null == jwtAuthInfo) {
log.error(" jwt 解析失败");
throw new ParamException(MessageCodeEnum.NOT_LOGIN);
}
return jwtAuthInfo;
}
}

鉴权思路

  1. 自定义注解;

  2. 请求进入到切面,获取请求参数,记录请求路径及请求参数;

  3. 获取方法中声明的注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * 获取方法中声明的注解
    *
    * @Author HiF
    * @Date 2022/2/8 20:42
    */
    private MonitorAnnotation getDeclaredAnnotation(JoinPoint joinPoint) {
    // 获取方法名
    String methodName = joinPoint.getSignature().getName();
    // 反射获取目标类
    Class<?> targetClass = joinPoint.getTarget().getClass();
    // 拿到方法对应的参数类型
    Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
    // 根据类、方法、参数类型(重载)获取到方法的具体信息
    Method objMethod = null;
    try {
    objMethod = targetClass.getMethod(methodName, parameterTypes);
    } catch (NoSuchMethodException e) {
    return null;
    }
    // 拿到方法定义的注解信息
    return objMethod.getDeclaredAnnotation(MonitorAnnotation.class);
    }
  4. 如果注解鉴权属性为true,则解析用户Token中的信息;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    MonitorAnnotation monitorAnnotation = getDeclaredAnnotation(joinPoint);
    if (null != monitorAnnotation) {
    if (monitorAnnotation.auth()) {
    // 如果是http请求
    if (httpRequest) {
    String accountNumber = jwtAuthorizationManager.authorization(request);
    // 此处应该校验用户id是否为空
    if (Validator.isNull(accountNumber)) {
    throw new ParamException(MessageCodeEnum.NOT_LOGIN);
    }
    }
    }
    }