feat:网关校验Token,并将token值存入header供下游服务使用
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
//package com.xiang.xservice.gateway.service.config;
|
||||
//
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
//import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
//import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
//
|
||||
//@Configuration
|
||||
//@EnableWebFluxSecurity
|
||||
//public class GatewaySecurityConfig {
|
||||
//
|
||||
// @Bean
|
||||
// public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
// http
|
||||
// .authorizeExchange(exchanges -> exchanges
|
||||
// // ✅ 网关全放行
|
||||
// .anyExchange().permitAll()
|
||||
// )
|
||||
// .csrf(ServerHttpSecurity.CsrfSpec::disable); // 禁用 CSRF
|
||||
//
|
||||
// return http.build();
|
||||
// }
|
||||
//}
|
||||
package com.xiang.xservice.gateway.service.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class GatewaySecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
// ✅ 网关全放行
|
||||
.anyExchange().permitAll()
|
||||
)
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable); // 禁用 CSRF
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.xiang.xservice.gateway.service.config;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
@Configuration
|
||||
public class JwtConfig {
|
||||
|
||||
private RSAPrivateKey loadPrivateKey(String classpath) throws Exception {
|
||||
InputStream is = new ClassPathResource(classpath).getInputStream();
|
||||
String key = new String(is.readAllBytes(), StandardCharsets.UTF_8)
|
||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.replace("-----END PRIVATE KEY-----", "")
|
||||
.replaceAll("\\s+", "");
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key));
|
||||
return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(spec);
|
||||
}
|
||||
|
||||
private RSAPublicKey loadPublicKey(String classpath) throws Exception {
|
||||
InputStream is = new ClassPathResource(classpath).getInputStream();
|
||||
String key = new String(is.readAllBytes(), StandardCharsets.UTF_8)
|
||||
.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replace("-----END PUBLIC KEY-----", "")
|
||||
.replaceAll("\\s+", "");
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(key));
|
||||
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(spec);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource() throws Exception {
|
||||
// 使用RSA对称加密进行加密
|
||||
RSAPublicKey publicKey = loadPublicKey("keys/rsa-public.pem");
|
||||
RSAPrivateKey privateKey = loadPrivateKey("keys/rsa-private.pem");
|
||||
|
||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID("xservice")
|
||||
.build();
|
||||
|
||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||
return (jwkSelector, ctx) -> jwkSelector.select(jwkSet);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return new NimbusJwtEncoder(jwkSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,31 @@
|
||||
package com.xiang.xservice.gateway.service.core;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.security.oauth2.jwt.JwtValidationException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private final JwtDecoder jwtDecoder;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
@@ -34,8 +45,42 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
// 3. 本地验签 + 解析 claims
|
||||
Jwt jwt;
|
||||
try {
|
||||
jwt = jwtDecoder.decode(token);
|
||||
} catch (JwtValidationException e) {
|
||||
boolean isExpired = e.getErrors().stream()
|
||||
.anyMatch(err -> err.getDescription().contains("expired"));
|
||||
|
||||
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
if (isExpired) {
|
||||
log.warn("Token 已过期: path={}", path);
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
log.warn("Token 校验失败: path={}, reason={}", path, e.getMessage());
|
||||
return exchange.getResponse().setComplete();
|
||||
} catch (JwtException e) {
|
||||
log.error("Token解析异常!path:{}", path);
|
||||
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
Long userId = (Long) jwt.getClaim("userId");
|
||||
Long tenantId = (Long) jwt.getClaim("tenantId");
|
||||
String username = (String) jwt.getClaim("username");
|
||||
String traceId = UUID.randomUUID().toString();
|
||||
log.info("Token解析结果:userId:{}, tenantId:{}, username:{}", userId, tenantId, username);
|
||||
ServerHttpRequest mutatedRequest = exchange.getRequest()
|
||||
.mutate()
|
||||
.header("X-User-Id", String.valueOf(userId))
|
||||
.header("X-Trace-Id", traceId)
|
||||
.header("X-Tenant-Id", String.valueOf(tenantId))
|
||||
.header("X-Username", username)
|
||||
.headers(h -> h.remove(HttpHeaders.AUTHORIZATION)) // JWT 止步于此
|
||||
.build();
|
||||
|
||||
log.info("✅ Token 校验通过: {}", token);
|
||||
return chain.filter(exchange);
|
||||
return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user