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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
| // OAuth2授权服务器
@RestController
@RequestMapping("/oauth2")
public class OAuth2AuthorizationServer {
private final AuthorizationCodeService authCodeService;
private final AccessTokenService accessTokenService;
private final ClientService clientService;
// 授权码存储
@Service
public static class AuthorizationCodeService {
private final RedisTemplate<String, String> redisTemplate;
private final Random random = new SecureRandom();
public AuthorizationCodeService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 生成授权码
public String generateAuthCode(String clientId, String userId, String redirectUri,
String scope) {
String code = generateRandomCode();
AuthCodeInfo authCodeInfo = new AuthCodeInfo();
authCodeInfo.setClientId(clientId);
authCodeInfo.setUserId(userId);
authCodeInfo.setRedirectUri(redirectUri);
authCodeInfo.setScope(scope);
authCodeInfo.setExpiresAt(System.currentTimeMillis() + 600_000); // 10分钟
String key = "auth_code:" + code;
String value = JsonUtils.toJson(authCodeInfo);
redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(10));
return code;
}
// 验证并消费授权码
public AuthCodeInfo validateAndConsumeAuthCode(String code, String clientId,
String redirectUri) {
String key = "auth_code:" + code;
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
throw new RuntimeException("Invalid or expired authorization code");
}
AuthCodeInfo authCodeInfo = JsonUtils.fromJson(value, AuthCodeInfo.class);
// 验证客户端ID和重定向URI
if (!authCodeInfo.getClientId().equals(clientId) ||
!authCodeInfo.getRedirectUri().equals(redirectUri)) {
throw new RuntimeException("Invalid client or redirect URI");
}
// 检查是否过期
if (System.currentTimeMillis() > authCodeInfo.getExpiresAt()) {
throw new RuntimeException("Authorization code expired");
}
// 删除授权码(一次性使用)
redisTemplate.delete(key);
return authCodeInfo;
}
private String generateRandomCode() {
byte[] bytes = new byte[32];
random.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
}
// 访问令牌服务
@Service
public static class AccessTokenService {
private final JWTManager jwtManager;
private final RedisTemplate<String, String> redisTemplate;
public AccessTokenService(JWTManager jwtManager,
RedisTemplate<String, String> redisTemplate) {
this.jwtManager = jwtManager;
this.redisTemplate = redisTemplate;
}
// 生成访问令牌
public TokenResponse generateTokens(String userId, String clientId, String scope) {
Map<String, Object> claims = new HashMap<>();
claims.put("client_id", clientId);
claims.put("scope", scope);
String accessToken = jwtManager.generateToken(userId, claims);
String refreshToken = generateRefreshToken();
// 存储刷新令牌
RefreshTokenInfo refreshTokenInfo = new RefreshTokenInfo();
refreshTokenInfo.setUserId(userId);
refreshTokenInfo.setClientId(clientId);
refreshTokenInfo.setScope(scope);
refreshTokenInfo.setExpiresAt(System.currentTimeMillis() + 2592000000L); // 30天
String key = "refresh_token:" + refreshToken;
String value = JsonUtils.toJson(refreshTokenInfo);
redisTemplate.opsForValue().set(key, value, Duration.ofDays(30));
TokenResponse response = new TokenResponse();
response.setAccessToken(accessToken);
response.setRefreshToken(refreshToken);
response.setTokenType("Bearer");
response.setExpiresIn(3600); // 1小时
response.setScope(scope);
return response;
}
// 使用刷新令牌生成新的访问令牌
public TokenResponse refreshAccessToken(String refreshToken) {
String key = "refresh_token:" + refreshToken;
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
throw new RuntimeException("Invalid refresh token");
}
RefreshTokenInfo refreshTokenInfo = JsonUtils.fromJson(value, RefreshTokenInfo.class);
if (System.currentTimeMillis() > refreshTokenInfo.getExpiresAt()) {
redisTemplate.delete(key);
throw new RuntimeException("Refresh token expired");
}
return generateTokens(refreshTokenInfo.getUserId(),
refreshTokenInfo.getClientId(),
refreshTokenInfo.getScope());
}
private String generateRefreshToken() {
return UUID.randomUUID().toString().replace("-", "");
}
}
// 授权端点
@GetMapping("/authorize")
public ResponseEntity<String> authorize(@RequestParam String clientId,
@RequestParam String redirectUri,
@RequestParam String responseType,
@RequestParam(required = false) String scope,
@RequestParam(required = false) String state,
HttpServletRequest request) {
try {
// 验证客户端
ClientInfo client = clientService.getClientById(clientId);
if (client == null || !client.getRedirectUris().contains(redirectUri)) {
return ResponseEntity.badRequest().body("Invalid client or redirect URI");
}
// 检查用户是否已登录
String userId = (String) request.getAttribute("userId");
if (userId == null) {
// 重定向到登录页面
String loginUrl = "/login?redirect=" +
URLEncoder.encode(request.getRequestURL().toString() + "?" +
request.getQueryString(), StandardCharsets.UTF_8);
return ResponseEntity.status(302).header("Location", loginUrl).build();
}
// 生成授权码
String authCode = authCodeService.generateAuthCode(clientId, userId, redirectUri, scope);
// 构建重定向URL
StringBuilder redirectUrl = new StringBuilder(redirectUri);
redirectUrl.append("?code=").append(authCode);
if (state != null) {
redirectUrl.append("&state=").append(state);
}
return ResponseEntity.status(302).header("Location", redirectUrl.toString()).build();
} catch (Exception e) {
return ResponseEntity.badRequest().body("Authorization failed: " + e.getMessage());
}
}
// 令牌端点
@PostMapping("/token")
public ResponseEntity<TokenResponse> token(@RequestParam String grantType,
@RequestParam(required = false) String code,
@RequestParam(required = false) String redirectUri,
@RequestParam(required = false) String refreshToken,
@RequestParam String clientId,
@RequestParam String clientSecret) {
try {
// 验证客户端凭证
if (!clientService.validateClient(clientId, clientSecret)) {
return ResponseEntity.status(401).build();
}
TokenResponse response;
switch (grantType) {
case "authorization_code":
response = handleAuthorizationCodeGrant(code, clientId, redirectUri);
break;
case "refresh_token":
response = handleRefreshTokenGrant(refreshToken);
break;
default:
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
private TokenResponse handleAuthorizationCodeGrant(String code, String clientId,
String redirectUri) {
AuthCodeInfo authCodeInfo = authCodeService.validateAndConsumeAuthCode(code, clientId, redirectUri);
return accessTokenService.generateTokens(authCodeInfo.getUserId(), clientId, authCodeInfo.getScope());
}
private TokenResponse handleRefreshTokenGrant(String refreshToken) {
return accessTokenService.refreshAccessToken(refreshToken);
}
}
// 数据类定义
class AuthCodeInfo {
private String clientId;
private String userId;
private String redirectUri;
private String scope;
private long expiresAt;
// getter和setter方法
public String getClientId() { return clientId; }
public void setClientId(String clientId) { this.clientId = clientId; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getRedirectUri() { return redirectUri; }
public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; }
public String getScope() { return scope; }
public void setScope(String scope) { this.scope = scope; }
public long getExpiresAt() { return expiresAt; }
public void setExpiresAt(long expiresAt) { this.expiresAt = expiresAt; }
}
class RefreshTokenInfo {
private String userId;
private String clientId;
private String scope;
private long expiresAt;
// getter和setter方法省略...
}
class TokenResponse {
private String accessToken;
private String refreshToken;
private String tokenType;
private int expiresIn;
private String scope;
// getter和setter方法省略...
}
class ClientInfo {
private String clientId;
private String clientSecret;
private Set<String> redirectUris;
private Set<String> grantTypes;
// getter和setter方法省略...
}
|