当前位置: 首页 > news >正文

SpringCloudOAuth2+JWT:微服务统⼀认证方案

目录

一、概念

1.1 问题背景

1.2 概念

1.3 核心价值与优势

1.3 功能边界

1.4 应用场景

二、原理

2.1 核心概念解析

2.1.1 OAuth 2.0 (Open Authorization 2.0)

2.1.2 JWT (JSON Web Token)

2.2 架构与核心组件

2.3 核心原理

2.3.1 获取 JWT 令牌 (Authentication)

2.3.2 访问受保护资源 (Authorization)

三、使用

3.1 使用步骤

3.1.1 环境准备与依赖配置

3.1.2 搭建OAuth2认证服务 (auth-service)

3.1.3 配置API网关 (api-gateway)

3.1.4 实现业务微服务 (user-service)

3.1.5 JWT 的最佳实践与增强安全

3.2 配置项详解


一、概念

1.1 问题背景

在微服务架构下,传统的单体应用Session方案(将用户状态存储在服务器内存或Redis中)面临巨大挑战:

  • 状态持久化问题:每个微服务都是无状态的,Session难以共享和维护。

  • 跨域问题:服务分散在不同域名下,Cookie携带困难。

  • 性能瓶颈:每次请求都需要查询中央Session存储,增加网络开销和延迟。

Spring Cloud OAuth2 + JWT 的组合完美解决了这些问题:

  1. 无状态 (Stateless):JWT自包含所有信息,服务端无需存储会话信息。

  2. 单点登录 (SSO):一个认证中心,所有微服务(资源服务器)都信任其颁发的Token。

  3. 解耦与集中管理:认证和授权逻辑被抽离到专门的认证服务中,各业务微服务只需关注自身业务。

  4. 安全与灵活性:OAuth2提供了完善的授权流程,JWT提供了轻量级且安全的令牌格式。

1.2 概念

首先,这个方案是由三个核心概念组成的:

  • 微服务架构:一种将单个应用程序拆分为一组小型、独立服务的设计风格。每个服务运行在自己的进程中,服务间通过轻量级机制(如 HTTP API)通信。

  • OAuth 2.0:一个授权框架,而非认证协议。它专注于解决第三方应用用户的授权下,安全地访问用户资源的问题。它定义了四种授权模式,最常用的是密码模式(用于第一方可信应用)和授权码模式(用于第三方应用)。

  • JWT:一种令牌格式。全称是 JSON Web Token,它是一种紧凑的、URL安全的方式,用于在双方之间传输信息。这些信息可以被验证和信任,因为它是数字签名的。

Spring Cloud OAuth2 是 Spring 官方对 OAuth 2.0 协议的实现,它极大地简化了在 Spring Boot/Cloud 应用中构建授权服务器和资源服务器的过程。

因此,这个方案的核心是:使用 Spring Cloud OAuth2 构建符合 OAuth 2.0 标准的授权服务器,并让其颁发 JWT 格式的令牌(Access Token)。

Spring Cloud OAuth2 + JWT 是微服务架构下构建安全、高效、统一认证授权体系的黄金标准。它通过中心化认证、分布式校验的模式,完美契合了微服务的分布式、无状态特性。虽然在令牌管理上存在一些挑战(如注销),但通过合理的设计(短有效期、黑名单等)可以有效规避。

1.3 核心价值与优势

  • 无状态性与解耦

    • 传统痛点:在单体应用中,通常使用 Session 机制。用户登录后,服务器会创建一个 Session 并将 Session ID 通过 Cookie 返回给客户端。服务器需要存储这个 Session 信息,在集群环境下需要做 Session 同步,增加了复杂度和耦合度。

    • 解决方案:JWT 是自包含的,所有必要的用户身份和权限信息都存储在令牌本身。资源服务器只需要验证 JWT 的签名并解析其内容即可获取用户信息,无需每次都向授权服务器或数据库查询。这使得服务彻底无状态,易于水平扩展。

  • 安全的单点登录与统一认证

    • 传统痛点:在微服务体系中,有几十甚至上百个服务。用户不可能在每个服务上都登录一次。

    • 解决方案:建立一个统一的授权服务中心。用户只需在授权中心登录一次,即可获得一个 Access Token。凭借这个 Token,用户就可以访问所有其它微服务(资源服务器)。这完美实现了单点登录。

  • 精细化的授权管理

    • OAuth 2.0 的核心是授权,它通过 Scope 的概念来控制客户端(如 Web前端、移动APP)的访问权限范围(例如:只能读信息、可以写信息等)。

    • JWT 的 payload 中可以自定义声明,通常我们会把用户的角色信息放入其中。资源服务器可以根据角色信息进行更细粒度的访问控制。

  • 性能与可扩展性

    • 由于 JWT 的自包含特性,资源服务器本地验证令牌即可,避免了大量的网络IO(如查询数据库或远程调用认证服务)。这在海量请求下极大地减轻了授权服务器的压力,提升了系统整体性能。

  • 多客户端支持

    • OAuth 2.0 协议原生支持多种客户端类型,如Web应用、浏览器、手机APP、IoT设备等。一套认证授权体系可以服务于所有前端平台。

1.3 功能边界

它能做/负责的:

  • 身份认证:验证用户是谁(通过用户名密码、短信码等)。

  • 访问授权:颁发令牌,并定义该令牌的访问范围和作用域。

  • API保护:校验访问资源服务的令牌是否有效、是否过期、是否有权访问。

  • 单点登录:作为整个系统的认证中心。

它不能做/不擅长的:

  • 用户管理:用户的注册、资料维护、密码重置等业务功能。它依赖一个“用户详情服务”来获取用户信息。

  • 权限模型的设计:权限模型是RBAC(角色基于访问控制)还是ABAC(属性基于访问控制)?这属于业务设计范畴,方案只提供了实现工具(如将角色放入JWT)。

  • JWT令牌的注销:这是JWT的一个著名缺点。由于无状态,服务器无法在颁发后直接让单个JWT失效。通常的解决方案是:

    • 设置较短的令牌有效期,并配合使用 Refresh Token 来更新。

    • 使用令牌黑名单,但这又引入了状态存储,部分违背了无状态的初衷。

  • 传输层安全:它不保证传输过程中的安全。必须使用 HTTPS 来加密传输令牌,防止令牌被窃取。

  • 防止令牌泄露:如果 Access Token 被泄露,攻击者可以在有效期内冒充用户。因此需要妥善保管令牌。

1.4 应用场景

这个方案几乎适用于所有中大型的、基于 Spring Cloud 技术栈的分布式系统。

  • 企业级后台管理系统

    • 多个后台服务(用户管理服务、订单服务、库存服务等)需要统一的登录入口和权限控制。管理员登录一次,即可在各个子系统间切换。

  • 前后端分离应用

    • 前端(Vue/React/Angular) + 后端多个微服务API。前端应用使用密码模式授权码模式获取 JWT,之后在调用所有 API 时在 HTTP Header 中携带该 Token。

  • 第三方应用授权

    • 允许用户使用本系统的账号授权登录第三方网站或APP(类似“微信登录”)。这是 OAuth 2.0 最原始的目的,使用授权码模式

  • 移动端APP

    • APP 启动后引导用户登录,获取 Token 后保存在本地,后续所有请求都携带此 Token。

  • 服务与服务之间的内部调用

    • 虽然更推荐使用更简单的认证方式(如基于API Gateway的密钥),但也可以使用 OAuth 2.0 的客户端凭证模式,为每个微服务客户端颁发令牌,用于服务间的认证。

二、原理

2.1 核心概念解析

2.1.1 OAuth 2.0 (Open Authorization 2.0)

  • 概念:一个开放的授权框架标准(注意,它不是认证协议,但常被用于构建认证方案)。

  • 核心问题:解决第三方应用用户的授权下,安全地访问该用户在服务提供商的受保护资源的问题。例如,用微信登录某个论坛、授权滴滴访问你的微信头像。

  • 四种授权模式:

    • 授权码模式 (Authorization Code):最安全、最常用,适用于有后端的Web应用。

    • 密码模式 (Resource Owner Password Credentials):用户把用户名密码直接交给客户端(受信任的官方应用),客户端用其换取Token。在我们的微服务架构中,内部系统通常采用此模式

    • 隐藏式 (Implicit):适用于纯前端SPA应用,无后端。

    • 客户端凭证 (Client Credentials):适用于机器对机器的认证,比如微服务之间的调用。

  • 摒弃密码模式: 在新项目中,即使内部系统也应使用授权码模式客户端凭证模式,安全性更高。

2.1.2 JWT (JSON Web Token)

  • 概念: 一个开放的、紧凑的、自包含的(self-contained)标准,用于在网络各方之间安全地传输信息作为JSON对象。

  • 结构: 由三部分组成,用点符号.连接:Header.Payload.Signature

    • Header:通常包含令牌类型(如JWT)和使用的签名算法(如HMAC SHA256或RSA)。

    • Payload:包含声明(Claims),即关于实体(通常是用户)和其他数据的语句。常见的声明有 sub(用户ID)、exp(过期时间)、authorities(权限列表)等。

    • Signature:对前两部分的签名,用于验证消息在传递过程中是否被篡改,以及验证令牌的发行者。

  • 关键特性:自包含

    • 传统Session方案中,服务端需要存储Session信息,每次请求需要查询Session存储来验证用户状态。

    • JWT无需服务端存储。所有必要信息(用户ID、权限等)都直接存储在令牌本身的Payload里。服务端只需用密钥验证签名有效,即可信任其中的内容。这使得它非常适合无状态的分布式系统。

  • JWT 令牌的流转

    • 用户登录,客户端携带用户名密码请求 认证服务 的 /oauth/token 端点。

    • 认证服务验证通过,生成包含用户信息和权限的 JWT 令牌并返回。

    • 客户端将 JWT 令牌存储在本地(如 LocalStorage/Cookie),并在后续请求的 Authorization Header 中携带(Bearer <token>)。

    • 网关 拦截请求,验证 JWT 签名和有效性,并转发请求到具体的业务服务。

    • 业务服务 从请求头中获取 JWT,解析出用户信息,处理业务逻辑并返回结果。

2.2 架构与核心组件

下面是每个核心组件的详细职责:

  1. 认证授权服务 (Auth Service):这是 OAuth2 体系中的认证服务器(Authorization Server),是系统的安全核心。

    • 职责:负责验证用户身份(如用户名/密码)、客户端身份(如 client_idclient_secret),并颁发 JWT 格式的 Access Token 和 Refresh Token

    • 关键依赖spring-cloud-starter-oauth2spring-securityJWT 库(如 jjwt)。

  2. API 网关 (Gateway Service):作为系统的唯一入口,是所有客户端请求的统一流量入口和第一道安全防线

    • 职责拦截所有外部请求,进行 Token 的统一校验(签名、有效期)、路由转发到正确的微服务,并可能承担权限预检、限流等功能。

    • 关键依赖spring-cloud-starter-gateway 或 Zuul

  3. 资源服务 (Resource Services):即各个业务微服务(如用户服务、订单服务),是 OAuth2 体系中的资源服务器(Resource Server)

    • 职责处理具体的业务逻辑。它们信任认证服务颁发的 JWT。网关校验通过后,会将解析后的 JWT 中的用户信息(如用户名、权限)传递给资源服务,资源服务据此进行业务处理和可能的更细粒度的权限控制。

    • 关键依赖spring-security-oauth2-resource-server 或 spring-security-oauth2-jose

    • 关键原则:安全相关的逻辑只存在于认证服务和网关服务中,其他服务无安全逻辑,只是单纯地提供服务。

  4. 客户端 (Client):请求资源的终端,如 Web 前端、手机 App、小程序等。

    • 职责:获取用户的认证凭证,向认证服务请求 Token,并在后续请求中在 HTTP Header(通常是 Authorization: Bearer <token>)中携带 JWT Token

在已经使用API网关进行统一鉴权的情况下,资源服务中仍需安全配置:

维度API网关 (前台保安)资源服务 (办公室门禁)
主要职责入口级检查、路由转发、流量控制业务级权限控制、数据权限
检查内容Token是否有效、是否过期用户是否有权限执行特定操作
验证方式验签、过期检查、黑名单检查角色/权限验证、业务规则验证
失败响应直接返回401/403,不会转发请求可返回更具体的业务错误信息

用一个比喻来解释:API网关就像大厦前台的保安,而资源服务的安全配置就像各个办公室的门禁系统

2.3 核心原理

2.3.1 获取 JWT 令牌 (Authentication)

请求:客户端(如前端APP)向认证服务器的 /oauth/token 端点发起POST请求(通常基于密码模式)。

POST /oauth/token
Content-Type: application/x-www-form-urlencodedgrant_type=password&
username=user&
password=pass&
client_id=client_app_id&
client_secret=client_app_secret

认证服务器处理

  • 验证 client_id 和 client_secret 的有效性。

  • 验证 username 和 password 的有效性(委托给 UserDetailsService)。

  • 验证通过后,生成JWT:

    • 创建Payload(包含标准声明如 subexpiat 和自定义声明如 user_nameauthorities)。

    • 用配置的密钥(如RSA私钥或HMAC密钥) 对Header和Payload进行签名,生成Signature。

    • 组合成最终的JWT字符串。

  • 将JWT作为响应返回给客户端。

{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type": "bearer","expires_in": 43199,"scope": "read write"
}

    2.3.2 访问受保护资源 (Authorization)

    请求:客户端在后续请求的Header中携带JWT。

    GET /orders/123
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

    资源服务器处理

    • 拦截请求OAuth2AuthenticationProcessingFilter 会拦截携带Bearer Token的请求。

    • 提取并验证JWT

      • 解密JWT的Header,获取签名算法。

      • 使用与认证服务器约定好的密钥(如RSA公钥或相同的HMAC密钥)对Header和Payload部分重新计算签名。

      • 将计算出的签名与JWT中的Signature进行对比。如果一致,证明Token是合法且未被篡改的。

      • 检查过期时间 (exp) 等声明。

    • 构建安全上下文:解析JWT Payload中的用户信息和权限信息(如 user_nameauthorities),并将其填充到 SecurityContextHolder 中,供后续业务逻辑使用(如 @PreAuthorize("hasRole('ADMIN')"))。

    • 执行业务逻辑:至此,资源服务器已经完全信任JWT中的身份,开始执行具体的Controller逻辑。

    三、使用

    3.1 使用步骤

    基于SpringCloud OAuth2、JWT、Spring Gateway和MySQL搭建统一认证和授权方案,能有效管理微服务架构下的安全访问和流量控制。

    3.1.1 环境准备与依赖配置

    创建Spring Cloud项目:使用Spring Initializr创建父工程,然后创建认证服务(如 auth-service)、网关服务(如 api-gateway)和业务微服务(如 user-service)。

    添加依赖:在各服务的 pom.xml 中添加必要依赖。

    • 认证服务 (auth-service):需要 Spring Security OAuth2JWTSpring Data JPAMySQL驱动 等。

    <!-- OAuth2 授权服务器 -->
    <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <!-- JWT -->
    <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId> <!-- 处理JOSE协议,包含JWT -->
    </dependency>
    <!-- Spring Data JPA -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- MySQL 驱动 -->
    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
    </dependency>
    • 网关服务 (api-gateway):需要 Spring Cloud GatewaySpring Security OAuth2 资源服务器Spring Boot Data Redis Reactive(用于限流)等。

    <!-- Spring Cloud Gateway -->
    <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- OAuth2 资源服务器 -->
    <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId>
    </dependency>
    <!-- Spring Security JWT (用于资源服务器解析JWT) -->
    <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId>
    </dependency>
    <!-- 响应式Redis依赖 (用于限流) -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    • 业务微服务 (如 user-service):需要 Spring WebSpring Security OAuth2 资源服务器(如果服务自身也想解析JWT进行细粒度控制)或其他安全框架(如Spring Security、Shiro)。若只想接收网关转发来的用户信息,可考虑不直接引入安全框架。

    <!-- Spring Web -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 若业务服务需要自行解析JWT进行细粒度鉴权,则添加资源服务器依赖 -->
    <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId>
    </dependency>

    3.1.2 搭建OAuth2认证服务 (auth-service)

    认证服务负责用户身份验证和JWT令牌的颁发。

    • 配置数据源与JPA:在 application.yml 中配置MySQL数据库连接、JPA以及OAuth2客户端信息(也可存入数据库)。

    spring:datasource:url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: your_passworddriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: updateshow-sql: true
    • 配置OAuth2授权服务器:创建一个配置类 AuthorizationServerConfig,继承 AuthorizationServerConfigurerAdapter
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate AuthenticationManager authenticationManager; // 需要配置AuthenticationManager@Autowiredprivate DataSource dataSource;@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;@Autowiredprivate TokenEnhancer jwtTokenEnhancer; // 自定义Token增强器,用于在JWT中添加额外信息// 配置客户端详情信息(可以基于内存或JDBC)@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.jdbc(dataSource); // 从数据库读取客户端配置// 内存示例:// clients.inMemory()//        .withClient("client_app")//        .secret(passwordEncoder.encode("123456")) // 客户端密码需加密//        .scopes("read", "write")//        .authorizedGrantTypes("password", "refresh_token")//        .accessTokenValiditySeconds(3600)//        .refreshTokenValiditySeconds(86400);}// 配置令牌端点(Token Endpoint)的安全约束与认证管理器@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {TokenEnhancerChain enhancerChain = new TokenEnhancerChain();enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore()) // 使用JWT令牌存储,Token不会直接存储,JWT的特性.accessTokenConverter(jwtAccessTokenConverter).tokenEnhancer(enhancerChain);}// 配置令牌端点的安全约束@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.checkTokenAccess("isAuthenticated()"); // 允许已认证的请求检查令牌security.allowFormAuthenticationForClients(); // 允许客户端使用表单认证}@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(jwtAccessTokenConverter()); // 使用JWT存储令牌}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("your-secret-key"); // 设置签名密钥(HS256),生产环境请使用强密码或RSA密钥对// 使用RSA:converter.setKeyPair(keyPair());return converter;}// 可选:自定义Token增强器,向JWT中添加额外信息(如用户ID、更多权限详情等)@Beanpublic TokenEnhancer jwtTokenEnhancer() {return (accessToken, authentication) -> {Map<String, Object> additionalInfo = new HashMap<>();// 添加自定义声明// additionalInfo.put("user_id", ((UserDetails) authentication.getPrincipal()).getSomeId());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);return accessToken;};}
    }
    • 配置Spring Security:定义用户详情服务(UserDetailsService)和密码编码器。
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService; // 需要实现自己的UserDetailsService@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}// 其他安全配置,如放行oauth/token端点等@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/oauth/**").permitAll() // 放行认证端点.anyRequest().authenticated();}
    }
    • 实现 UserDetailsService 接口,从数据库加载用户信息。
    @Service
    public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository; // 假设你的UserRepository@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("User not found: " + username);}// 假设你的User实体实现了UserDetails,或者你需要在这里构建一个UserDetails对象// 这里需要返回包括用户名、密码、权限等信息的UserDetails对象return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()) // 数据库中的密码应该是加密后的.authorities(getAuthorities(user)) // 从用户对象中获取权限/角色.build();}private Collection<? extends GrantedAuthority> getAuthorities(User user) {// 根据你的用户权限/角色结构返回GrantedAuthority集合// 例如:return user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());return Collections.emptyList(); // 示例}
    }

    3.1.3 配置API网关 (api-gateway)

    网关负责路由转发、JWT验证、限流等。

    • 配置路由与JWT资源服务器:在 application.yml 中配置路由规则和JWT公钥(如果使用RSA)或签名密钥(如果使用HS256)。
    spring:cloud:gateway:routes:- id: user-service-routeuri: lb://user-service # 假设已集成服务发现(如Nacos)predicates:- Path=/api/users/**filters:- name: RequestRateLimiter # 限流过滤器args:redis-rate-limiter.replenishRate: 10 # 每秒产生的令牌数redis-rate-limiter.burstCapacity: 20 # 令牌桶总容量key-resolver: "#{@userKeyResolver}" # 限流键解析器Bean(按用户限流)- StripPrefix=1 # 去掉路径的第一部分(例如 /api)- id: auth-service-route # 认证服务路由,通常获取token的端点需要暴露,也可直接不经过网关uri: lb://auth-servicepredicates:- Path=/oauth/token# 注意:获取token的端点通常不需要JWT验证,网关安全配置需要放行security:oauth2:resourceserver:jwt:secret-key: your-secret-key # HS256的密钥,与认证服务一致# 如果使用RSA:# public-key-location: classpath:public.key# 或者通过issuer-uri从认证服务发现jwks端点(推荐)# issuer-uri: http://auth-serviceredis:host: localhostport: 6379database: 0
    • 配置网关安全:创建一个配置类,定义Spring Security规则,保护除认证端点外的其他路由。
    @Configuration
    @EnableWebFluxSecurity // 注意是WebFlux
    public class GatewaySecurityConfig {@Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http.authorizeExchange(exchanges -> exchanges.pathMatchers("/oauth/token").permitAll() // 放行获取token的端点.anyExchange().authenticated() // 其他所有请求都需要认证).oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt) // 启用JWT资源服务器.csrf().disable();return http.build();}
    }

    Spring Cloud Gateway会自动验证JWT的有效性和过期时间。

    • 实现限流配置:创建一个按用户(从JWT中获取 subject)限流的键解析器。
    @Configuration
    public class RateLimiterConfig {@BeanKeyResolver userKeyResolver() {return exchange -> {// 从SecurityContext中获取认证信息,其中包含JWT解析后的内容return exchange.getPrincipal().map(Principal::getName) // 获取用户名(JWT的subject).defaultIfEmpty("anonymous"); // 如果没有认证用户,使用"anonymous"};}
    }
    • (可选) 实现黑名单/令牌失效:可以创建一个全局过滤器,检查JWT是否在黑名单(如Redis中)以实现注销失效。
    @Component
    public class JwtBlacklistFilter implements GlobalFilter, Ordered {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);if (authHeader != null && authHeader.startsWith("Bearer ")) {String jwtToken = authHeader.substring(7);// 从JWT中解析出jti (JWT ID) 或其他唯一标识// 这里需要解析JWT,可以使用JwtHelper(注意:资源服务器已经解析过,避免重复解析,可以考虑在之前的过滤器中设置属性)// 简单示例:检查jti是否在黑名单Set中String jti = parseJtiFromToken(jwtToken); // 需要实现parseJtiFromToken方法if (jti != null && redisTemplate.hasKey("jwt_blacklist:" + jti)) {exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}}return chain.filter(exchange);}private String parseJtiFromToken(String jwtToken) {// 使用JwtHelper解析JWT,获取claims,然后取出jti声明// 注意:这只是示例,Gateway的OAuth2资源服务器已经验证并解析了JWT,可以考虑在其后添加此过滤器,并从SecurityContext获取信息避免重复解析。try {String[] splitToken = jwtToken.split("\\.");String claimsStr = new String(Base64.getUrlDecoder().decode(splitToken[1]));Map<String, Object> claims = new ObjectMapper().readValue(claimsStr, Map.class);return (String) claims.get("jti");} catch (Exception e) {return null;}}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE + 1; // 在认证过滤器之后执行}
    }

    3.1.4 实现业务微服务 (user-service)

    业务微服务处理具体业务,并进行更细粒度的权限控制。

    • 配置JWT资源服务器(可选):如果微服务想自行解析JWT进行细粒度鉴权,需要在 application.yml 中配置与网关相同的JWT设置。
    spring:security:oauth2:resourceserver:jwt:secret-key: your-secret-key # 与认证服务、网关一致# issuer-uri: http://auth-service # 或者使用issuer-uri

    并在安全配置中启用资源服务器:

    @Configuration
    @EnableWebSecurity
    public class ResourceServerConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests(authorize -> authorize.antMatchers("/users/me").hasAuthority("SCOPE_profile") // 示例:需要scope.antMatchers(HttpMethod.GET, "/users/**").hasRole("USER") // 示例:需要角色.antMatchers(HttpMethod.POST, "/users").hasRole("ADMIN").anyRequest().authenticated()).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);}
    }
    • 从JWT中获取用户信息:在控制器中,可以使用 @AuthenticationPrincipal 注入JWT解析后的主体(通常是用户名或一个 Jwt 对象)。
    @RestController
    @RequestMapping("/users")
    public class UserController {@GetMapping("/me")public String getCurrentUser(@AuthenticationPrincipal Jwt jwt) {// 从JWT claims中获取信息String username = jwt.getSubject();String customClaim = (String) jwt.getClaims().get("custom_claim");return "Hello, " + username + ". Your custom claim: " + customClaim;}// 或者如果主体是用户名字符串// public String getCurrentUser(@AuthenticationPrincipal(expression = "subject") String username) { ... }
    }
    • 实现业务逻辑权限校验:在Service层或自定义切面中,根据业务规则进行更复杂的权限判断。

    • 统一异常处理与返回格式:使用 @RestControllerAdvice 和 @ExceptionHandler 捕获异常,并返回统一的、更具体的业务错误信息格式。
    @RestControllerAdvice
    public class GlobalExceptionHandler {// 处理业务异常@ExceptionHandler(BusinessException.class)public ResponseEntity<ResultData<?>> handleBusinessException(BusinessException e) {// ResultData 是你定义的统一返回体ResultData<?> resultData = ResultData.fail(e.getErrorCode(), e.getMessage());return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultData); // 或其他合适的状态码}// 处理访问拒绝(权限不足)@ExceptionHandler(AccessDeniedException.class)public ResponseEntity<ResultData<?>> handleAccessDeniedException(AccessDeniedException e) {ResultData<?> resultData = ResultData.fail("403", "没有权限执行此操作");return ResponseEntity.status(HttpStatus.FORBIDDEN).body(resultData);}// 处理其他异常...@ExceptionHandler(Exception.class)public ResponseEntity<ResultData<?>> handleOtherException(Exception e) {ResultData<?> resultData = ResultData.fail("500", "系统内部错误: " + e.getMessage());return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultData);}
    }// 统一的返回结果类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ResultData<T> {private String code;private String message;private T data;private long timestamp = System.currentTimeMillis();public static <T> ResultData<T> success(T data) {return new ResultData<>("200", "成功", data, System.currentTimeMillis());}public static <T> ResultData<T> fail(String code, String message) {return new ResultData<>(code, message, null, System.currentTimeMillis());}
    }

    3.1.5 JWT 的最佳实践与增强安全

    • 合理设置 Token 有效期:Access Token 设置较短的有效期(如 15-30 分钟),Refresh Token 设置较长的有效期(如 7 天)。使用 Refresh Token 来获取新的 Access Token,减少 Access Token 泄露的风险。

    • 使用非对称加密 (RS256):避免对称加密密钥共享带来的风险。

    • 避免在 JWT Payload 中存放敏感信息:因为 Payload 只是 Base64 编码,并非加密。

    • 实现 Token 黑名单/注销机制:虽然 JWT 本身是无状态的,但如果需要实现用户注销使 Token 立即失效,可以维护一个黑名单(如使用 Redis)。认证服务在注销时将未过期的 Token 加入黑名单,网关和资源服务在验证 Token 时需检查黑名单(这会引入一定的状态,需权衡)。

    • 强制使用 HTTPS:防止 Token 在传输过程中被窃取。

    • 密钥管理是生命线: 尤其是私钥,绝不能硬编码在代码中或提交到Git。应使用配置中心、环境变量或K8s Secret等方式管理。

    • 保护好你的端点: 认证服务器的端点(如 /oauth/authorize/oauth/token)本身也需要做好安全防护。

    • 自定义登录接口:默认的 Token 端点 (/oauth/token) 可能不符合你的 RESTful 风格或项目需求。你可以通过配置覆盖它:

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.pathMapping("/oauth/token", "/api/auth/login");
    }
    • 监控和日志: 详细记录令牌的颁发和验证日志,便于审计和排查问题。

    • 新项目技术选型注意:

      • Spring官方已不再积极开发 spring-security-oauth2,并推出了新的 Spring Authorization Server 项目来替代它。

      • 新项目强烈建议直接采用 Spring Authorization Server。它功能更强大、符合最新规范、并且与Spring生态集成更好。需要学习的是新的配置方式(如 RegisteredClientRepository 等)。

    3.2 配置项详解

    在 Spring Cloud OAuth2 (Spring Authorization Server 或已停更的 Spring Security OAuth2) 中,配置主要围绕 @EnableAuthorizationServer@EnableResourceServer 等注解以及相关的 Configurer 类进行。JWT 作为令牌格式,其配置主要与 JwtAccessTokenConverter 和 TokenStore 相关。

    授权服务器 (Authorization Server) 配置:授权服务器负责对用户身份进行认证,并颁发访问令牌 (Access Token) 和刷新令牌 (Refresh Token)。

    配置类别具体配置项说明/可选值示例或默认值
    客户端详情clientIdOAuth2 客户端标识符,必填"web-app"
    ClientDetailsServiceConfigurerclientSecret客户端密钥,必填(对于 confidential 客户端)"{bcrypt}..."
    scope客户端申请的权限范围["read", "write"]
    authorizedGrantTypes支持的授权模式["authorization_code", "password", "refresh_token", "implicit"]
    authorities客户端自身的权限(如 ROLE_CLIENT)["ROLE_CLIENT"]
    redirectUris授权码模式中,认证后重定向的 URI["https://myapp.com/login"]
    accessTokenValiditySeconds访问令牌有效期(秒)3600 (1小时)
    refreshTokenValiditySeconds刷新令牌有效期(秒)2592000 (30天)
    autoApprove是否自动批准特定 scope 的授权true 或 "read"
    令牌管理tokenStore令牌存储方式,使用 JWT 时设置为 JwtTokenStorenew JwtTokenStore(jwtAccessTokenConverter())
    AuthorizationServerEndpointsConfigurertokenEnhancer令牌增强器,用于生成 JWTjwtAccessTokenConverter()
    authenticationManager密码模式需要注入的认证管理器@Autowired AuthenticationManager
    userDetailsService用于刷新令牌和密码模式时加载用户信息@Autowired UserDetailsService
    authorizationCodeServices授权码的存储方式(内存或数据库)new InMemoryAuthorizationCodeServices()
    tokenServices定义令牌服务的属性,如是否支持刷新令牌DefaultTokenServices
    端点URLpathMapping自定义认证、令牌等端点的 URL.pathMapping("/oauth/token", "/api/auth/token")
    AuthorizationServerEndpointsConfigurer
    JWT 特定配置signingKey对称加密的签名密钥(HS256)"my-secret-key"
    JwtAccessTokenConverterkeyPair非对称加密的密钥对(RS256)keyPair() (从 keystore 加载)
    verifierKey非对称加密时,用于资源服务器验证的公钥"-----BEGIN PUBLIC KEY-----..."
    accessTokenConverter自定义 JWT 与 OAuth2 认证信息之间的转换逻辑new CustomAccessTokenConverter()
    其他安全配置allowFormAuthenticationForClients是否允许客户端使用表单认证(否则为 Basic Auth)true
    AuthorizationServerSecurityConfigurertokenKeyAccess定义访问 /oauth/token_key 端点的权限(获取公钥)"permitAll()" 或 "isAuthenticated()"
    checkTokenAccess定义访问 /oauth/check_token 端点的权限(验证令牌)"isAuthenticated()"

    资源服务器 (Resource Server) 配置:资源服务器提供受保护的 API 资源,负责验证访问令牌并授权。

    配置类别具体配置项说明/可选值示例或默认值
    资源IDresourceId资源标识符,需与令牌中 aud 声明匹配(可选)"order-service"
    ResourceServerConfigurer
    令牌服务tokenServices定义如何解析和验证令牌(RemoteTokenServices 或 DefaultTokenServices)通常注入与授权服务器相同的 TokenStore
    ResourceServerConfigurertokenStore推荐方式:直接注入 TokenStore(如 JwtTokenStore)来本地验证 JWT@Autowired TokenStore
    JWT 验证配置signingKey对称加密时,设置与授权服务器相同的签名密钥"my-secret-key"
    JwtAccessTokenConverterverifierKey非对称加密时,设置授权服务器的公钥"-----BEGIN PUBLIC KEY-----..."
    jwt直接提供一个 JwtDecoder (Spring Security 5.x+)NimbusJwtDecoder.withPublicKey(publicKey).build()
    访问规则antMatchers(...).permitAll()配置不需要认证的公开端点.antMatchers("/api/public/**").permitAll()
    HttpSecurityantMatchers(...).hasRole("ADMIN")配置需要特定权限才能访问的端点.antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
    antMatchers(...).authenticated()配置任何认证用户都可访问的端点.anyRequest().authenticated()
    access使用 SpEL 表达式进行复杂的权限控制.access("@permissionService.check(authentication, request)")

    客户端 (Client Application / SPA) 配置:客户端是请求访问受保护资源的应用(如前端 Vue/React 应用、移动 App、其他服务)。

    配置类别具体配置项说明/可选值示例或默认值
    客户端信息client-id在授权服务器注册的 clientId"web-app"
    (application.yml)client-secret在授权服务器注册的 clientSecret"secret"
    scope申请的权限范围"read write"
    认证服务器信息access-token-uri获取 Token 的端点地址http://auth-server/oauth/token
    (application.yml)user-authorization-uri获取授权码的端点地址(授权码模式)http://auth-server/oauth/authorize
    用户信息user-info-uri获取当前用户信息的端点地址http://auth-server/user/me
    (application.yml)pre-established-redirect-uri预配置的重定向 URIhttps://myclient.com/login
    令牌处理jwt.key-value(如果使用对称加密)配置签名密钥以本地解析 JWTmy-secret-key
    jwt.key-uri(如果使用非对称加密)配置获取公钥的端点http://auth-server/oauth/token_key

    网关 (API Gateway) 配置:网关作为所有流量的入口,通常承担着统一鉴权令牌中继的角色。

    配置类别具体配置项说明/可选值示例(如 Spring Cloud Gateway)
    路由规则predicates定义哪些请求需要经过认证/转发到认证服务- Path=/api/**
    (application.yml)filters在过滤器中进行令牌校验和中继- StripPrefix=1
    令牌校验自定义全局过滤器在网关层统一验证 JWT 的有效性和权限,避免无效请求打到后端服务实现 GlobalFilter, 使用 ReactiveJwtDecoder 解析 JWT
    令牌中继自定义全局过滤器将解析后的 JWT 中的身份信息(如用户名、权限)以 HTTP Header 形式转发给下游微服务在 ServerWebExchange 的请求中添加 X-User-NameX-User-Authorities 等 Header
    白名单whitelist配置直接放行、不需要认证的路由(如登录、注册、静态资源)在自定义过滤器的逻辑中排除 /auth/login/public/** 等路径

    http://www.dtcms.com/a/394331.html

    相关文章:

  • LeetCode 分类刷题:2517. 礼盒的最大甜蜜度
  • 深度学习优化器进阶:从SGD到AdamW,不同优化器的适用场景
  • C++ 之 【C++的IO流】
  • truffle学习笔记
  • 现代循环神经网络
  • vlc播放NV12原始视频数据
  • ThinkPHP8学习篇(七):数据库(三)
  • 链家租房数据爬虫与可视化项目 Python Scrapy+Django+Vue 租房数据分析可视化 机器学习 预测算法 聚类算法✅
  • MQTT协议知识点总结
  • C++ 类和对象·其一
  • TypeScript里的类型声明文件
  • 【LeetCode - 每日1题】设计电影租借系统
  • Java进阶教程,全面剖析Java多线程编程,线程安全,笔记12
  • DCC-GARCH模型与代码实现
  • 实验3掌握 Java 如何使用修饰符,方法中参数的传递,类的继承性以及类的多态性
  • 【本地持久化】功能-总结
  • 深入浅出现代FPU浮点乘法器设计
  • LinkedHashMap 访问顺序模式
  • 破解K个最近点问题的深度思考与通用解法
  • 链式结构的特性
  • 报表1-创建sql函数get_children_all
  • 9月20日 周六 农历七月廿九 哪些属相需要谨慎与调整?
  • godot实现tileMap地图
  • 【Unity+VSCode】NuGet包导入
  • QEMU虚拟机设置网卡模式为桥接,用xshell远程连接
  • Week 17: 深度学习补遗:Boosting和量子逻辑门
  • 【论文速递】2025年第13周(Mar-23-29)(Robotics/Embodied AI/LLM)
  • Webpack进阶配置
  • 【LeetCode 每日一题】3227. 字符串元音游戏
  • 【图像算法 - 26】使用 YOLOv12 实现路面坑洞智能识别:构建更安全的智慧交通系统