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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
简介: 使用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教程可以点击直达!,欢迎收藏与转发支持!

目录
相关文章
|
缓存 安全 算法
Spring Security OAuth 2.0 资源服务器— JWT
Spring Security OAuth 2.0 资源服务器— JWT
569 1
|
JSON 安全 Java
Spring Security OAuth 实现 GitHub 快捷登录
Spring Security 5中集成了OAuth的客户端模块,该模块包含以下三个子模块
Spring Security OAuth 实现 GitHub 快捷登录
|
设计模式 安全 NoSQL
从零开始的Spring Security Oauth2(一)
从零开始的Spring Security Oauth2(一)
6654 3
从零开始的Spring Security Oauth2(一)
|
安全 前端开发 Java
从一手资料学习--Spring Security与OAuth(二)
上回我们聊到,既然Spring官网也有提到,要学习Spring Security OAuth相关的知识,最好先学习OAuth2.0相关的知识,而官网中OAuth 2.0 Framework的链接地址对应的就是rfc6749的文档,结构是这样的
从一手资料学习--Spring Security与OAuth(二)
|
安全 Java 开发者
从一手资料学习--Spring Security与OAuth(一)
不知道大家对于上面的几个问题被问及的时候会心里发慌。强哥发现,大多数小伙伴对于一些工作中使用较少的知识,或者说是平常都在用,但是不需要自己去实现的知识,主动学习的积极性都比较低。
从一手资料学习--Spring Security与OAuth(一)
|
JSON 安全 Java
再见,Spring Security OAuth!!
本次将 《Spring Authorization Server》项目正式上线,去掉了之前的体验状态,此举恰逢 0.2.0 版本发布,这也是第一个正式支持的生产就绪版本。
再见,Spring Security OAuth!!
|
设计模式 安全 Java
从零开始的Spring Security Oauth2(三)
从零开始的Spring Security Oauth2(三)
386 0
从零开始的Spring Security Oauth2(三)
|
存储 安全 Java
Spring Security OAuth 2开发者指南译
Spring Security OAuth 2开发者指南译 介绍 这是用户指南的支持OAuth 2.0。对于OAuth 1.0,一切都是不同的,所以看到它的用户指南。
1789 0
|
安全 搜索推荐 前端开发
|
存储 安全 Java
spring security oauth2
https://connect.qq.com/manage.html#/   Spring Boot Oauth2 with H2 databasehttps://github.com/rajithd/spring-boot-oauth2使用oauth2.
3177 0