1. 概述

Spring Cloud Security 模块为 Spring Boot 应用提供了基于令牌的安全特性。它主要解决两个核心问题:

✅ 简化基于 OAuth2 的单点登录(SSO)实现
✅ 支持在资源服务器间传递令牌
✅ 通过嵌入式 Zuul 代理配置下游认证

本文将通过一个 Spring Boot 客户端应用、一个授权服务器和一个作为资源服务器的 REST API,演示如何配置这些特性。注意:为简化演示,我们只使用一个客户端应用——实际生产环境中至少需要两个客户端才能体现 SSO 的价值。

2. 快速搭建云安全应用

2.1 添加依赖

首先在客户端应用中添加核心依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

该依赖会自动引入 spring-cloud-starter-security

2.2 配置授权服务器

我们选择自建授权服务器(部署在 http://localhost:7070/authserver),使用 JWT 令牌。同时需要配置资源服务器(端口 9000)提供用户信息接口:

spring:
  security:
    oauth2:
      client:
        registration:
          baeldung:
            client-id: authserver
            client-secret: passwordforauthserver
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          baeldung:
            token-uri: http://localhost:7070/authserver/oauth/token
            authorization-uri: http://localhost:7070/authserver/oauth/authorize
            user-info-uri: http://localhost:9000/user

2.3 启用 OAuth2 登录

在配置类中添加安全配置:

@Configuration
public class SiteSecurityConfigurer {
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // ...   
        http.oauth2Login();    
        // ... 
    }
}

⚠️ 所有需要认证的请求将自动重定向到授权服务器。确保 spring-boot-starter-security 已在类路径中。

3. 传递访问令牌

当客户端需要调用下游服务时,需要将 OAuth2 令牌转发出去。Spring Security 提供了 OAuth2AuthorizedClientService,我们可以基于它创建 RestTemplate 拦截器:

@Bean
public RestOperations restTemplate(OAuth2AuthorizedClientService clientService) {
    return new RestTemplateBuilder().interceptors((ClientHttpRequestInterceptor) 
        (httpRequest, bytes, execution) -> {
        OAuth2AuthenticationToken token = 
        OAuth2AuthenticationToken.class.cast(SecurityContextHolder.getContext()
            .getAuthentication());
        OAuth2AuthorizedClient client = 
            clientService.loadAuthorizedClient(token.getAuthorizedClientRegistrationId(), 
            token.getName());
            httpRequest.getHeaders()
                .add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken()
                    .getTokenValue());
        return execution.execute(httpRequest, bytes);
    }).build();
}

✅ 配置后,请求会自动携带令牌,并在令牌过期时自动刷新。

4. 使用 RestTemplate 传递 OAuth 令牌

4.1 资源服务器接口

在资源服务器中定义受保护接口:

@GetMapping("/person")
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
public @ResponseBody Person personInfo(){        
    return new Person("张三", "北京", "中国", 29, "男");       
}    

4.2 客户端调用

在客户端应用中通过 RestTemplate 调用:

@Autowired
private RestOperations restOperations;

@GetMapping("/personInfo")
public ModelAndView person() { 
    ModelAndView mav = new ModelAndView("personinfo");
    String personResourceUrl = "http://localhost:9000/person";
    mav.addObject("person", 
      restOperations.getForObject(personResourceUrl, String.class));       
    
    return mav;
}

✅ 令牌会自动通过拦截器添加到请求头中。

5. 配置 Zuul 令牌中继

5.1 添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

5.2 启用 Zuul 代理

在配置类添加注解:

@EnableZuulProxy
@Configuration
public class SiteSecurityConfigurer {
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // ...   
        http.oauth2Login();    
        // ... 
    }
}

5.3 路由配置

application.yml 中添加:

zuul:
  sensitiveHeaders: Cookie,Set-Cookie  
  routes:
    resource:
      path: /api/**
      url: http://localhost:9000
    user: 
      path: /user/**
      url: http://localhost:9000/user

✅ 所有发往 /api 的请求会被代理到资源服务器,同时自动传递令牌。用户信息接口 /user 也需要单独配置路由。


原始标题:An Intro to Spring Cloud Security