使用Spring Security资源服务器来保护Spring Cloud微服务

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 使用Spring Security资源服务器来保护Spring Cloud微服务

我在上一篇对资源服务器进行了简单的阐述,让大家对资源服务器的概念有了简单的认识,今天我将用实际例子来演示单体应用改造为Spring Cloud微服务时的资源服务器实现。

资源服务器改造

Spring Security实战干货的DEMO为例子,原本它是一个单体应用,认证和授权都在一个应用中使用。改造为独立的服务后,原本的认证就要剥离出去(这个后续再讲如何实现),服务将只保留基于用户凭证(JWT)的访问控制功能。接下来我们将一步步来实现该能力。

所需依赖

在Spring Security的基础上,我们需要加入新的依赖来支持OAuth2 Resource Server和JWT。我们需要引入下面几个依赖库:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 资源服务器 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<!-- jose -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>

Spring Security 5.x 移除了OAuth2.0授权服务器,保留了OAuth2.0资源服务器。

JWT解码

要校验JWT就必须实现对JWT的解码功能,在Spring Security OAuth2 Resource Server模块中,默认提供了解码器,这个解码器需要调用基于:

spring.security.oauth2.resourceserver

配置下的元数据来生成解码配置,这里的配置大部分是调用授权服务器开放的well-known断点,包含了解析验证JWT一系列参数:

  • jwkSetUri 一般是授权服务器提供的获取JWK配置的well-known端点,用来校验JWT Token。
  • jwsAlgorithm 指定jwt使用的算法,默认 RSA-256
  • issuerUri 获取OAuth2.0 授权服务器元数据的端点。
  • publicKeyLocation 用于解码的公钥路径,作为资源服务器来说将只能持有公钥,不应该持有私钥。

为了实现平滑过渡,默认的配置肯定不能用了,需要定制化一个JWT解码器。接下来我们一步步来实现它。

分离公私钥

资源服务器只能保存公钥,所以需要从之前的jks文件中导出一个公钥。

keytool -export -alias felordcn -keystore <jks证书全路径>  -file <导出cer的全路径>

例如:

keytool -export -alias felordcn -keystore D:\keystores\felordcn.jks  -file d:\keystores\publickey.cer

把分离的cer公钥文件放到原来jks文件的路径下面,资源服务器不再保存jks

自定义jwt解码器

spring-security-oauth2-jose是Spring Security的jose规范依赖。我将根据该类库来实现自定义的JWT解码器。

/**
 * 基于Nimbus的jwt解码器,并增加了一些自定义校验策略
 *
 * @param validator the validator
 * @return the jwt decoder
 */
@SneakyThrows
@Bean
public JwtDecoder jwtDecoder(@Qualifier("delegatingTokenValidator") DelegatingOAuth2TokenValidator<Jwt> validator) {
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    // 从classpath路径读取cer公钥证书来配置解码器
    ClassPathResource resource = new ClassPathResource(this.jwtProperties.getCertInfo().getPublicKeyLocation());
    Certificate certificate = certificateFactory.generateCertificate(resource.getInputStream());
    PublicKey publicKey = certificate.getPublicKey();
    NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build();
    nimbusJwtDecoder.setJwtValidator(validator);
    return nimbusJwtDecoder;
}

上面的解码器基于我们的公钥证书,同时我还自定义了一些校验策略。不得不说Nimbus的jwt类库比jjwt要好用的多。

自定义资源服务器配置

接下来配置资源服务器。

核心流程和概念

资源服务器其实也就是配置了一个过滤器BearerTokenAuthenticationFilter来拦截并验证Bearer Token。验证通过而且权限符合要求就放行,不通过就不放行。

和之前不太一样的是验证成功后凭据不再是UsernamePasswordAuthenticationToken而是JwtAuthenticationToken

@Transient
public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken<Jwt> {
 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 private final String name;
 /**
  * Constructs a {@code JwtAuthenticationToken} using the provided parameters.
  * @param jwt the JWT
  */
 public JwtAuthenticationToken(Jwt jwt) {
  super(jwt);
  this.name = jwt.getSubject();
 }
 /**
  * Constructs a {@code JwtAuthenticationToken} using the provided parameters.
  * @param jwt the JWT
  * @param authorities the authorities assigned to the JWT
  */
 public JwtAuthenticationToken(Jwt jwt, Collection<? extends GrantedAuthority> authorities) {
  super(jwt, authorities);
  this.setAuthenticated(true);
  this.name = jwt.getSubject();
 }
 /**
  * Constructs a {@code JwtAuthenticationToken} using the provided parameters.
  * @param jwt the JWT
  * @param authorities the authorities assigned to the JWT
  * @param name the principal name
  */
 public JwtAuthenticationToken(Jwt jwt, Collection<? extends GrantedAuthority> authorities, String name) {
  super(jwt, authorities);
  this.setAuthenticated(true);
  this.name = name;
 }
 @Override
 public Map<String, Object> getTokenAttributes() {
  return this.getToken().getClaims();
 }
 /**
  * jwt 中的sub 值  用户名比较合适
  */
 @Override
 public String getName() {
  return this.name;
 }
}

这个我们改造的时候要特别注意,尤其是从SecurityContext获取的时候用户凭证信息的时候。

资源管理器配置

从Spring Security 5的某版本开始不需要再集成适配类了,只需要这样就能配置Spring Security,资源管理器也是这样:

@Bean
SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
    return http.authorizeRequests(request -> request.anyRequest()
                .access("@checker.check(authentication,request)"))
                .exceptionHandling()
                .accessDeniedHandler(new SimpleAccessDeniedHandler())
                .authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
                .and()               .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .build();
    }

这里只需要声明使用JWT校验的资源服务器,同时配置好定义的401端点和403处理器即可。这里我加了基于SpEL的动态权限控制,这个再以往都讲过了,这里不再赘述。

JWT个性化解析

从JWT Token中解析数据并生成JwtAuthenticationToken的操作是由JwtAuthenticationConverter来完成的。你可以定制这个转换器来实现一些个性化功能。比如默认情况下解析出来的权限都是带SCOPE_前缀的,而项目用ROLE_,你就可以通过这个类兼容一下老项目。

@Bean
     JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
//        如果不按照规范  解析权限集合Authorities 就需要自定义key
//        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("scopes");
//        OAuth2 默认前缀是 SCOPE_     Spring Security 是 ROLE_
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        // 设置jwt中用户名的key  默认就是sub  你可以自定义
        jwtAuthenticationConverter.setPrincipalClaimName(JwtClaimNames.SUB);
        return jwtAuthenticationConverter;
    }

这里基本上就改造完成了。你受保护的资源API将由Bearer Token来保护。

在实际生产中建议把资源服务器封装为依赖集成到需要保护资源的的服务中即可。

附加说明

为了测试资源服务器,假设我们有一个颁发令牌的授权服务器。这里简单模拟了一个发令牌的方法用来获取Token:

/**
 * 资源服务器不应该生成JWT 但是为了测试 假设这是个认证服务器
 */
@SneakyThrows
@Test
public void imitateAuthServer() {
    JwtEncoder jwsEncoder = new NimbusJwsEncoder(jwkSource());
    JwtTokenGenerator jwtTokenGenerator = new JwtTokenGenerator(jwsEncoder);
    OAuth2AccessTokenResponse oAuth2AccessTokenResponse = jwtTokenGenerator.tokenResponse();
    System.out.println("oAuth2AccessTokenResponse = " + oAuth2AccessTokenResponse.getAccessToken().getTokenValue());
}
@SneakyThrows
private JWKSource<SecurityContext> jwkSource() {
    ClassPathResource resource = new ClassPathResource("felordcn.jks");
    KeyStore jks = KeyStore.getInstance("jks");
    String pass = "123456";
    char[] pem = pass.toCharArray();
    jks.load(resource.getInputStream(), pem);
    RSAKey rsaKey = RSAKey.load(jks, "felordcn", pem);
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
}

相关的DEMO已经上传,你可以通过关注公众号“码农小胖哥”,回复 resourceserver获取资源服务器实现的DEMO。

好了,今天的学习就到这里!如果您学习过程中如遇困难?可以加入我们超高质量的Spring技术交流群,参与交流与讨论,更好的学习与进步!更多Spring Cloud教程可以点击直达!,欢迎收藏与转发支持!

目录
相关文章
|
1月前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
168 5
|
28天前
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
154 69
从单体到微服务:如何借助 Spring Cloud 实现架构转型
|
5月前
|
Java UED Sentinel
微服务守护神:Spring Cloud Sentinel,让你的系统在流量洪峰中稳如磐石!
【8月更文挑战第29天】Spring Cloud Sentinel结合了阿里巴巴Sentinel的流控、降级、熔断和热点规则等特性,为微服务架构下的应用提供了一套完整的流量控制解决方案。它能够有效应对突发流量,保护服务稳定性,避免雪崩效应,确保系统在高并发下健康运行。通过简单的配置和注解即可实现高效流量控制,适用于高并发场景、依赖服务不稳定及资源保护等多种情况,显著提升系统健壮性和用户体验。
103 1
|
11天前
|
运维 监控 Java
为何内存不够用?微服务改造启动多个Spring Boot的陷阱与解决方案
本文记录并复盘了生产环境中Spring Boot应用内存占用过高的问题及解决过程。系统上线初期运行正常,但随着业务量上升,多个Spring Boot应用共占用了64G内存中的大部分,导致应用假死。通过jps和jmap工具排查发现,原因是运维人员未设置JVM参数,导致默认配置下每个应用占用近12G内存。最终通过调整JVM参数、优化堆内存大小等措施解决了问题。建议在生产环境中合理设置JVM参数,避免资源浪费和性能问题。
36 3
|
5月前
|
Cloud Native Java Nacos
微服务时代的新宠儿!Spring Cloud Nacos实战指南,带你玩转服务发现与配置管理,拥抱云原生潮流!
【8月更文挑战第29天】Spring Cloud Nacos作为微服务架构中的新兴之星,凭借其轻量、高效的特点,迅速成为服务发现、配置管理和治理的首选方案。Nacos(命名和配置服务)由阿里巴巴开源,为云原生应用提供了动态服务发现及配置管理等功能,简化了服务间的调用与依赖管理。本文将指导你通过五个步骤在Spring Boot项目中集成Nacos,实现服务注册、发现及配置动态管理,从而轻松搭建出高效的微服务环境。
315 0
|
5月前
|
负载均衡 应用服务中间件 持续交付
微服务架构下的Web服务器部署
【8月更文第28天】随着互联网应用的不断发展,传统的单体应用架构逐渐显露出其局限性,特别是在可扩展性和维护性方面。为了解决这些问题,微服务架构应运而生。微服务架构通过将应用程序分解成一系列小型、独立的服务来提高系统的灵活性和可维护性。本文将探讨如何在微服务架构中有效部署和管理Web服务器实例,并提供一些实际的代码示例。
163 0
|
3月前
|
人工智能 运维 Kubernetes
87cloud案例分析:阿里云国际服务器如何支持在线教育
87cloud案例分析:阿里云国际服务器如何支持在线教育
|
4月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
188 5
|
4月前
|
Java API 对象存储
微服务魔法启动!Spring Cloud与Netflix OSS联手,零基础也能创造服务奇迹!
这段内容介绍了如何使用Spring Cloud和Netflix OSS构建微服务架构。首先,基于Spring Boot创建项目并添加Spring Cloud依赖项。接着配置Eureka服务器实现服务发现,然后创建REST控制器作为API入口。为提高服务稳定性,利用Hystrix实现断路器模式。最后,在启动类中启用Eureka客户端功能。此外,还可集成其他Netflix OSS组件以增强系统功能。通过这些步骤,开发者可以更高效地构建稳定且可扩展的微服务系统。
70 1
|
4月前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
83 2