本文目录导读:

Spring Cloud Security 主要用来解决微服务架构下的安全认证和授权问题,在微服务中,服务是分散的、无状态的,传统的基于 Session 的认证方式(如 Spring Security+HttpSession)不再适用。
核心难点在于:
- 用户状态不共享:服务 A 认证了,服务 B 不知道。
- 服务间调用安全:服务 A 调用服务 B,B 如何验证 A 是合法的,而不是黑客伪造的请求?
- 统一认证 vs. 独立校验:谁负责颁发“通行证”?谁负责检查“通行证”?
Spring Cloud Security 通常与 Spring Cloud Gateway、Spring Security OAuth2(以及后续的 Spring Authorization Server)结合,构建出一套完整的解决方案。
最主流的微服务安全架构模式
目前业界最成熟、最常用的模式是:JWT + OAuth2 + 网关统一认证 + 资源服务本地校验。
架构角色
- 客户端(Client):浏览器、手机 App、第三方应用。
- 网关(Gateway):系统的统一入口,负责身份认证(验证用户名密码)和初步校验(JWT Token 合法性),并将解析后的用户信息传递到下游。
- 授权服务器(Authorization Server):负责颁发 Token(通行证),登录成功就给一个 JWT。
- 资源服务器(Resource Server):业务微服务(订单服务、用户服务),不负责认证,只负责校验 Token 的权限。
认证流程(标准 OAuth2 密码模式/客户端模式)
sequenceDiagram
participant 客户端
participant 网关 as 网关(Gateway)
participant 授权服务 as 授权服务器
participant 订单服务 as 订单服务(资源)
客户端->>+网关: 1. 发送登录请求(用户名, 密码)
网关->>+授权服务: 2. 转发认证请求到 Authorization Server
授权服务-->>-网关: 3. 验证成功,返回 JWT Token
网关-->>-客户端: 4. 返回 JWT Token
客户端->>+网关: 5. 发送业务请求(Header: Bearer JWT)
网关->>网关: 6. Token 合法性校验(签名、过期时间)
网关->>+订单服务: 7. 转发请求(携带解析出的用户信息, 如 userId)
订单服务->>订单服务: 8. 资源服务本地校验(角色、权限)
订单服务-->>-网关: 9. 返回业务数据
网关-->>-客户端: 10. 返回数据
具体技术实现(Spring Cloud 主流技术栈)
引入核心依赖
在 pom.xml 中,核心依赖通常包括:
<!-- Spring Boot 与 Security (核心) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OAuth2 资源服务器 (用于校验 JWT Token) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- (可选) 如果你自己搭建授权服务器 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>
授权服务器配置 (Authorization Server)
负责颁发 JWT Token,通常使用 @EnableAuthorizationServer(Spring Security OAuth2 旧版)或 Spring Authorization Server(新版,Spring Security 5.7+ 推荐)。
核心配置(简化版):
@Configuration
@EnableAuthorizationServer // 旧版注解,新版需用 NimbusJwkMatcher 等
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-client") // 客户端 ID
.secret("{noop}my-secret") // 客户端秘钥
.authorizedGrantTypes("password", "client_credentials", "refresh_token")
.scopes("read", "write")
.accessTokenValiditySeconds(3600); // Token 过期时间
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager) // 注入 AuthenticationManager
.accessTokenConverter(jwtAccessTokenConverter()); // 配置 JWT 转换器
}
// 配置 JWT 签名密钥(通常用非对称密钥 RSA,这里为演示用对称密钥)
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("my-secret-key-which-is-very-long"); // 正式环境请用 RSA
return converter;
}
}
网关安全配置 (Gateway + Security)
网关负责拦截所有入站请求,进行 Token 校验,并将用户信息传递给下游。
@Configuration
@EnableWebFluxSecurity // 注意:Gateway 是 WebFlux,用这个注解
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/auth/**", "/login/**", "/public/**").permitAll() // 白名单
.anyExchange().authenticated() // 其他所有请求都需要认证
)
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt); // 开启 JWT 校验
// .csrf().disable(); // 微服务架构通常禁用 CSRF
return http.build();
}
}
传递用户信息到下游:
网关在转发请求给微服务时,需要将解析出的用户信息(如用户ID、角色)放到 Header 中。
@Component
public class UserInfoFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 从 SecurityContext 获取 Authentication 对象
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.flatMap(auth -> {
if (auth instanceof JwtAuthenticationToken) {
JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) auth;
Jwt jwt = jwtAuth.getToken();
String userId = jwt.getClaimAsString("user_id");
// 将用户信息放入请求头,转发给下游微服务
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-User-Id", userId)
.header("X-User-Roles", auth.getAuthorities().toString())
.build();
return chain.filter(exchange.mutate().request(request).build());
}
return chain.filter(exchange);
});
}
}
微服务(资源服务器)配置
每个微服务只需要校验 Token 的合法性,并基于用户信息做权限校验。
@Configuration
@EnableWebSecurity // 注意:普通微服务是 Servlet,用这个注解
public class ResourceServerConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwkSetUri;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
// 示例:只有 ADMIN 角色才能访问 /admin/**
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
// 自定义 JWT 解析,从 Token 中提取角色
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthorities = new JwtGrantedAuthoritiesConverter();
grantedAuthorities.setAuthorityPrefix("ROLE_"); // 设置角色前缀
grantedAuthorities.setAuthoritiesClaimName("roles"); // JWT 中存放角色的 Claim 名称
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(grantedAuthorities);
return converter;
}
}
服务间调用安全 (内部调用)
当服务 A 调用服务 B 时,也需要携带 Token,可以通过 RestTemplate 或 WebClient 拦截器实现。
// 使用 RestTemplate 调用另一个微服务
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 在调用逻辑中:从当前上下文获取 Token,并放入 Header
public void callServiceB() {
// 获取当前用户的 Token
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String token = ((JwtAuthenticationToken) authentication).getToken().getTokenValue();
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token); // 设置 Bearer Token
HttpEntity<String> entity = new HttpEntity<>(headers);
restTemplate.exchange("http://service-b/api/data", HttpMethod.GET, entity, String.class);
}
微服务安全的挑战与最佳实践
| 挑战 | 解决方案 |
|---|---|
| Token 如何不用每次都查数据库验证? | 使用 JWT,JWT 自带签名,网关和服务端只需要验证签名即可,无需查库做 RPC 调用。 |
| 如何高效管理密钥? | 使用 非对称密钥 (RSA),授权服务器签发 Token(持有私钥),所有服务通过公钥验签(可以放在授权服务器的 /actuator/jwk-set 端点,配置 jwks-url),避免每次更新密钥都要重启所有服务。 |
| 如何避免重复解密 Token 影响性能? | 只在网关层做一次完整的 JWT 校验,下游微服务不解析 JWT,而是信任网关传递的 X-User-* Header,但这种方式有安全风险(内网可能被攻击)。推荐:每个服务自行解析 JWT(性能损耗很小,安全级别更高)。 |
| 如何支持第三方登录? | 使用 OAuth2 授权码模式,先跳转到第三方(如微信、GitHub),拿到授权码,再换取 Token。 |
| 如何实现 API 级权限? | 结合 @PreAuthorize、@PostAuthorize 或 @Secured 注解,在方法级别进行细粒度控制。 |
Spring Cloud Security 在微服务中的最佳实践就是 JWT + OAuth2 + 网关统一认证 + 资源服务本地校验。
- 授权服务器:专门负责签发票据(JWT)。
- 网关:守卫入口,负责转发和初步校验(白名单、Token 结构)。
- 资源服务器(微服务):只校验签名和权限,无状态,易扩展。
这种架构保证了前后端分离、服务无状态、易于水平扩展,是构建生产级微服务安全体系的基石。