基于JWT的用户身份认证
# 基于 session 的用户身份验证
验证过程:
服务端验证浏览器携带的用户名和密码,验证通过后,生成用户凭证保存在服务端(
session
),浏览器再次访问时,服务端查询session
,实现登录状态保持。
缺点:
随着用户的增多,服务端压力增大;
若浏览器
cookie
被攻击者拦截,容易收到跨站请求伪造攻击;分布式系统下扩展性不强
# 基于 Token 的用户身份验证
验证过程:
服务端验证浏览器所携带的用户名和密码,验证通过后生成用户令牌(
token
)并返回给浏览器,浏览器(前端会将token
放入header
或者以post
的形式)再次访问时携带token
,服务端校验token
并返回相关的数据。
优点:
- token 不存储在服务器,不会造成服务器的压力
- token 可以存储在非
cookie
中,安全性高 - 分布式系统下扩展性强
# Java 实现
引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
1
2
3
4
5
2
3
4
5
token
生成和验证类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;
// 自定义异常
import com.wjstar.project.domain.exception.ConditionException;
import java.util.Calendar;
import java.util.Date;
public class TokenUtil {
public static final String ISSUER = "签发者";
public static String generateToken(Long userId) throws Exception {
// 定义加密算法
// 使用 RSA 加密
// 传入公钥和私钥
Algorithm algorithm = Algorithm.RSA256(
RSAUtil.getPublicKey(),
RSAUtil.getPrivateKey()
);
// 生成过期时间用
Calendar calendar = Calendar.getInstance();
// 设置当前时间
calendar.setTime(new Date());
// 设置过期时间 30秒
calendar.add(Calendar.SECOND, 30);
return JWT.create().withKeyId(String.valueOf(userId))
.withIssuer(ISSUER)
.withExpiresAt(calendar.getTime())
.sign(algorithm);
}
/**
* 验证用户令牌
* @param token String
* @return Long
*/
public static Long verifyToken(String token) {
RSAKeyProvider keyProvider;
Algorithm algorithm = null;
// 这个使用 try catch 包裹,不能直接放在方法上抛出
try {
// 定义加解密算法
algorithm = Algorithm.RSA256(
RSAUtil.getPublicKey(),
RSAUtil.getPrivateKey()
);
JWTVerifier verifier = JWT.require(algorithm).build();
// 解密后的jwt
DecodedJWT decodedJWT = verifier.verify(token);
// 获取到相关的用户id
String userId = decodedJWT.getKeyId();
return Long.valueOf(userId);
} catch (TokenExpiredException e) {
// token 过期
throw new ConditionException("555", "token过期!");
} catch (Exception e) {
throw new ConditionException("非法用户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
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
从前端发送的请求头里获取token
并解析用户身份
import com.wjstar.project.domain.exception.ConditionException;
import com.wjstar.project.service.util.TokenUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Component
public class UserSupport {
public Long getCurrentUserId() {
// 从 header 里获取 token
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
// 获取到token
assert requestAttributes != null;
String token = requestAttributes.getRequest().getHeader("token");
Long userId = TokenUtil.verifyToken(token);
if (userId < 0) {
throw new ConditionException("非法用户");
}
return userId;
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
获取到userId
之后就可以进行一系列的查询数据库,判断用户是否存在,或者拿到用户信息等操作。
# RSA 加密工具类
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* RSA加密
* 非对称加密,有公钥和私钥之分,公钥用于数据加密,私钥用于数据解密。加密结果可逆
* 公钥一般提供给外部进行使用,私钥需要放置在服务器端保证安全性。
* 特点:加密安全性很高,但是加密速度较慢
*/
public class RSAUtil {
private static final String PUBLIC_KEY = "可以自己整一个公钥";
// 可以自己整一个私钥
private static final String PRIVATE_KEY = "";
public static void main(String[] args) throws Exception {
String str = RSAUtil.encrypt("123456");
System.out.println(str);
}
public static String getPublicKeyStr() {
return PUBLIC_KEY;
}
public static RSAPublicKey getPublicKey() throws Exception {
byte[] decoded = Base64.decodeBase64(PUBLIC_KEY);
return (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(decoded));
}
public static RSAPrivateKey getPrivateKey() throws Exception {
byte[] decoded = Base64.decodeBase64(PRIVATE_KEY);
return (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decoded));
}
public static RSAKey generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
String privateKeyString = new String(Base64.encodeBase64(privateKey.getEncoded()));
return new RSAKey(privateKey, privateKeyString, publicKey, publicKeyString);
}
public static String encrypt(String source) throws Exception {
byte[] decoded = Base64.decodeBase64(PUBLIC_KEY);
RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(1, rsaPublicKey);
return Base64.encodeBase64String(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8)));
}
public static Cipher getCipher() throws Exception {
byte[] decoded = Base64.decodeBase64(PRIVATE_KEY);
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(2, rsaPrivateKey);
return cipher;
}
public static String decrypt(String text) throws Exception {
Cipher cipher = getCipher();
byte[] inputByte = Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8));
return new String(cipher.doFinal(inputByte));
}
public static class RSAKey {
private RSAPrivateKey privateKey;
private String privateKeyString;
private RSAPublicKey publicKey;
public String publicKeyString;
public RSAKey(RSAPrivateKey privateKey, String privateKeyString, RSAPublicKey publicKey, String publicKeyString) {
this.privateKey = privateKey;
this.privateKeyString = privateKeyString;
this.publicKey = publicKey;
this.publicKeyString = publicKeyString;
}
public RSAPrivateKey getPrivateKey() {
return this.privateKey;
}
public void setPrivateKey(RSAPrivateKey privateKey) {
this.privateKey = privateKey;
}
public String getPrivateKeyString() {
return this.privateKeyString;
}
public void setPrivateKeyString(String privateKeyString) {
this.privateKeyString = privateKeyString;
}
public RSAPublicKey getPublicKey() {
return this.publicKey;
}
public void setPublicKey(RSAPublicKey publicKey) {
this.publicKey = publicKey;
}
public String getPublicKeyString() {
return this.publicKeyString;
}
public void setPublicKeyString(String publicKeyString) {
this.publicKeyString = publicKeyString;
}
}
}
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
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
编辑 (opens new window)
上次更新: 2022/03/06, 16:05:04