使用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@Beanpublic 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;}@Overridepublic Map<String, Object> getTokenAttributes() {return this.getToken().getClaims();}/*** jwt 中的sub 值 用户名比较合适*/@Overridepublic String getName() {return this.name;}}
这个我们改造的时候要特别注意,尤其是从SecurityContext
获取的时候用户凭证信息的时候。
资源管理器配置
从Spring Security 5的某版本开始不需要再集成适配类了,只需要这样就能配置Spring Security,资源管理器也是这样:
@BeanSecurityFilterChain 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_
,你就可以通过这个类兼容一下老项目。
@BeanJwtAuthenticationConverter 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@Testpublic void imitateAuthServer() {JwtEncoder jwsEncoder = new NimbusJwsEncoder(jwkSource());JwtTokenGenerator jwtTokenGenerator = new JwtTokenGenerator(jwsEncoder);OAuth2AccessTokenResponse oAuth2AccessTokenResponse = jwtTokenGenerator.tokenResponse();System.out.println("oAuth2AccessTokenResponse = " + oAuth2AccessTokenResponse.getAccessToken().getTokenValue());}@SneakyThrowsprivate 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。
推荐关注本文作者:码农小胖哥
分享高质量编程知识,探讨IT人生
技术干货,实战技巧,面试技巧,前沿资讯一个都不能少
使用Spring Security 资源服务器来保护Spring Cloud 微服务相关推荐
- 使用Spring Security Oauth2 和 JWT保护微服务--Uaa授权服务器的编写
学习自深入理解微服务 采用Spring Security OAuth2 和 JWT的方式,Uaa服务只需要验证一次,返回JWT.返回的JWT包含了用户的所有信息,包括权限信息 从三个方面讲解: JWT ...
- 《深入理解 Spring Cloud 与微服务构建》第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统
<深入理解 Spring Cloud 与微服务构建>第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统 文章目录 <深入理解 Spring Cl ...
- Spring Security 源码分析:Spring Security 授权过程
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring I ...
- 《Spring Cloud 微服务架构进阶》读书笔记
前页 随着 DevOps 和以 Docker 为主的容器技术的发展,云原生应用架构和微服 务变得流行起来. 云原生包含的内容很多,如 DevOps.持续交付.微服务.敏捷等 第一章,微服务架构介绍 架 ...
- 实战系列-Spring Cloud微服务中三把利器Feign、Hystrix、Ribbon
导语 在之前的分享中分享过关于Fegin的底层实现原理,以及Spring Cloud OpenFegin的启动原理.在这次的分享中主要总结一下Spring Cloud 微服务架构的三把利器.对于F ...
- Spring Cloud微服务下的权限架构调研
随着微服务架构的流行,系统架构调整,项目权限系统模块开发提上日程,需要对权限架构进行设计以及技术选型.所以这段时间看了下相关的资料,做了几个对比选择. 一.架构图 初步设想的架构如下,结构很简单:eu ...
- 疯狂Spring Cloud微服务架构实战
网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击"普通下载" ...
- Spring Cloud 微服务项目操作实战流程(完结)
Spring Cloud入门项目操作实战流程 Day01~02 〇.Service - 业务服务结构 商品服务 item service,端口 8001 用户服务 user service,端口 81 ...
- Spring Cloud 微服务技术栈
Spring Cloud 简介 主要内容 微服务简介 SpringCloud 简介 SpringCloud 框架结构 SpringCloud 和 Dubbo 的对比 SpringCloud 版本号说明 ...
最新文章
- 基于相交线的双目平面SLAM
- 经过不断努力 终于获得“推荐博客”的荣誉
- jsTree设置默认节点全部展开的方法
- HDFS依然是存储的王者
- 自拍就可以得到你自己的个人贴图 Gboard打造客制化贴图
- 39. Element compareDocumentPosition() 方法
- LRC软件测试简历,C语言 LRC歌词文件解析
- oracle index logging,index在logging什么?
- 学习笔记——Servlet原理
- 基于ace admin 的左侧菜单及tab,tab支持右键菜单及与左侧菜单联动
- 数据安全风险分析及应对策略研究
- So Far Away
- 分清概念十分重要系列之--关于GPS,你需要了解这些
- HUAWEI华为荣耀MagicBook V14 I5 集显 16GB+512GB (HGE-W56)原装出厂WIN11系统恢复原厂oem系统
- 计算机音乐播放器设置,Win7系统下设置默认音乐播放器的两种方法
- s ss ss ss ss ss ss ss ss ss ss ss ss ss s
- 聂文涛食物训练与现代医学的差异
- NPL基础入门之新闻本分类数据分析Test2
- 全网最全Python项目体系练习500例(附源代码),练完可就业
- elasticsearch分词器词库热更新三种方案