爆火!Spring Boot 3.4 @HttpExchange 強勢來襲,一套搞定遠程調(diào)用 + 登錄認證 + Token續(xù)簽
本篇文章不僅展示如何使用@HttpExchange優(yōu)雅封裝遠程調(diào)用接口,還將結(jié)合 Spring Security、JWT、Redis 實現(xiàn)一體化登錄認證 + Token 續(xù)簽機制,并進一步集成:
- ? Token 自動注入
- ? 失敗自動重試
- ? RefreshToken 自動續(xù)簽
- ? Redis 緩存中間件管理 Token 生命周期 通過這些高級能力,你將看到一種真正“零侵入、強解耦、具備生產(chǎn)力”的服務(wù)通信與認證架構(gòu)。
核心依賴配置(Spring Boot 3.4.2)
<dependencies>
<!-- Webflux:用于 HttpExchange + WebClient -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 緩存工具(可選) -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
application.yml 示例:
server:
port:8080
spring:
redis:
host: localhost
port:6379
jwt:
secret: icoderoad-secret
access-token-expire:600# 單位:秒
refresh-token-expire:1800# 單位:秒
JWT 工具類支持 AccessToken + RefreshToken:
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.access-token-expire}")
private long accessExpire;
@Value("${jwt.refresh-token-expire}")
private long refreshExpire;
// 生成 Token
public String generateToken(String username, boolean isRefreshToken) {
long expireTime = isRefreshToken ? refreshExpire : accessExpire;
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + expireTime * 1000))
.signWith(Keys.hmacShaKeyFor(secret.getBytes()))
.compact();
}
public Claims parseToken(String token) throws JwtException {
return Jwts.parserBuilder()
.setSigningKey(secret.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean isTokenExpired(String token) {
return parseToken(token).getExpiration().before(new Date());
}
public long getAccessExpireMillis() {
return accessExpire * 1000;
}
}
登錄接口返回 Access + Refresh 雙 Token:
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
Authentication auth = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
String accessToken = jwtUtils.generateToken(request.getUsername(), false);
String refreshToken = jwtUtils.generateToken(request.getUsername(), true);
// 存入 Redis
redisTemplate.opsForValue().set("access:" + accessToken, request.getUsername(),
jwtUtils.getAccessExpireMillis(), TimeUnit.MILLISECONDS);
redisTemplate.opsForValue().set("refresh:" + refreshToken, request.getUsername(),
jwtUtils.getAccessExpireMillis() * 3, TimeUnit.MILLISECONDS);
return ResponseEntity.ok(Map.of("accessToken", accessToken, "refreshToken", refreshToken));
}
Token 續(xù)簽邏輯(刷新接口):
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody Map<String, String> body) {
String oldRefreshToken = body.get("refreshToken");
try {
Claims claims = jwtUtils.parseToken(oldRefreshToken);
String username = claims.getSubject();
// 校驗 Redis
String redisUser = redisTemplate.opsForValue().get("refresh:" + oldRefreshToken);
if (!username.equals(redisUser)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("無效的刷新Token");
}
// 新的 AccessToken
String newAccessToken = jwtUtils.generateToken(username, false);
redisTemplate.opsForValue().set("access:" + newAccessToken, username,
jwtUtils.getAccessExpireMillis(), TimeUnit.MILLISECONDS);
return ResponseEntity.ok(Map.of("accessToken", newAccessToken));
} catch (JwtException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token非法或已過期");
}
}
@HttpExchange 自動傳遞 Token 并支持異常重試:
@Configuration
public class RemoteClientConfig {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Bean
public RemoteService remoteService(JwtUtils jwtUtils) {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:8888")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + fetchTokenFromRedis())
.defaultStatusHandler(HttpStatusCode::isError, clientResponse -> {
return Mono.error(new RuntimeException("遠程服務(wù)異常"));
})
.filter(retryFilter())
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(RemoteService.class);
}
private String fetchTokenFromRedis() {
Set<String> keys = redisTemplate.keys("access:*");
return keys != null && !keys.isEmpty() ? keys.iterator().next().substring(7) : "";
}
private ExchangeFilterFunction retryFilter() {
return ExchangeFilterFunction.ofResponseProcessor(response -> {
if (response.statusCode().is5xxServerError()) {
return Mono.error(new IllegalStateException("服務(wù)不可用"));
}
return Mono.just(response);
});
}
}
總結(jié)
在本文中,我們基于 Spring Boot 3.4,構(gòu)建了一個功能完整的遠程服務(wù)通信與認證體系:
- 使用@HttpExchange實現(xiàn)了極簡、聲明式的 HTTP 接口定義;
- 借助 Spring WebClient 實現(xiàn)了底層 HTTP 請求執(zhí)行;
- 通過 Spring Security + JWT + Redis 構(gòu)建了完整的登錄認證、Token 續(xù)簽與緩存管理機制;
- 實現(xiàn)了 RefreshToken 自動續(xù)簽機制,提升了系統(tǒng)的安全性與用戶體驗;
- 支持 Token 自動透傳、異常重試、參數(shù)對象映射等高級特性;
這一整套架構(gòu)的優(yōu)勢在于:
- 輕量純凈無需 Feign 等三方依賴,原生 Spring 實現(xiàn);
- 擴展性強支持異步響應(yīng)、參數(shù)自動注入、個性化異常處理;
- 認證鏈路可控可追溯Token 生命周期完全掌握在自己手中;
- 性能可期Token 與用戶狀態(tài)存于 Redis,讀寫高效,支撐高并發(fā)訪問場景。
如果你正在構(gòu)建一個現(xiàn)代化微服務(wù)系統(tǒng),并希望拋棄冗余依賴、提升遠程調(diào)用體驗與認證安全性,不妨從本文架構(gòu)出發(fā),打造一套真正面向未來的通信體系。