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

简介: 使用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教程可以点击直达!,欢迎收藏与转发支持!

目录
相关文章
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1629 3
|
监控 Java 数据库
从零学 Dropwizard:手把手搭轻量 Java 微服务,告别 Spring 臃肿
Dropwizard 整合 Jetty、Jersey 等成熟组件,开箱即用,无需复杂配置。轻量高效,启动快,资源占用少,内置监控、健康检查与安全防护,搭配 Docker 部署便捷,是构建生产级 Java 微服务的极简利器。
1228 117
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
753 0
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
593 0
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
577 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
12月前
|
负载均衡 Java API
《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
488 0
Spring中最大化@Lazy注解,实现资源高效利用
|
消息中间件 存储 Java
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ安装
本教程介绍ActiveMQ的安装与基本使用。首先从官网下载apache-activemq-5.15.3版本,解压后即可完成安装,非常便捷。启动时进入解压目录下的bin文件夹,根据系统选择win32或win64,运行activemq.bat启动服务。通过浏览器访问`http://127.0.0.1:8161/admin/`可进入管理界面,默认用户名密码为admin/admin。ActiveMQ支持两种消息模式:点对点(Queue)和发布/订阅(Topic)。前者确保每条消息仅被一个消费者消费,后者允许多个消费者同时接收相同消息。
684 0
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ安装