1、背景
当了解这篇文章授权服务器后,对授权服务器有一定的认识,那么授权服务器生成token后,该怎么用呢,这就涉及到资源服务器,现在给大家简单介绍实现过程。
2、方案
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>
2.1 基于官网配置
首先先配置 issuer-uri ,这里指向是授权服务器的地址
# 授权服务器地址
spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9000
关于过滤器链的配置:
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class ResourceServerConfig {// @formatter:off@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.securityMatcher("/messages/**").authorizeHttpRequests().requestMatchers("/messages/**").hasAuthority("SCOPE_message.read").and().oauth2ResourceServer().jwt();return http.build();}// @formatter:on}
资源服务器将使用此 issuer-uri 进一步进行自我配置,发现授权服务器的公钥,并传入用于验证JWT的JwtDecoder。此过程的结果是授权服务器必须启动并接收请求才能使资源服务器成功启动。
若如果资源服务器必须能够独立于授权服务器启动,那么可以提供jwk-set-uri
。这将是我们进一步在OAuth2安全配置中添加属性:
# 资源服务器必须能够独立于授权服务器启动
spring.security.oauth2.resourceserver.jwt.jwk-set-uri= http://localhost:9000/oauth2/jwks
至此,资源服务器可以正常使用。若是你的系统比较庞大,每次请求都会请求授权服务器,这会给授权服务器带来压力,具体的实现方案可以根据实际情况而定。
2.2 确定加密方式
授权服务器的操作:
# JWK 配置
cloud:jwk:rsa:# 公钥public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzt2qx7HwpblnbwtRV9ZiaXwvUO4JlOYYVFEDmINxp5d+Nbih8hmOrlM+4qhr5Ej1Gc+D+TJ2oNoN/wtUaGP38Kf21UhiEbRedRfdQvOEuDXmScLUBd4tJ9dH4+i5XFSnkZi3L0rlTmPLpv8SuJSnNg/POjxmBxBAnJq4qmm2c7bXqcXOa8oAQYMtBHtuA6hhbbD2tpZEr4tM010eDPrZysFeVKNFlNV9fgBL5H2s7GRqVslHEmU6vQfQGCXL2Z+jDv8WH3k3kneV7EL24IipMDjmPzxk5zw6L8uzKxANuLVnh3mCDkqnaPMN4TBTSqE5muKR5SHyGF0/dqxodmanWwIDAQAB# 私钥private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDO3arHsfCluWdvC1FX1mJpfC9Q7gmU5hhUUQOYg3Gnl341uKHyGY6uUz7iqGvkSPUZz4P5Mnag2g3/C1RoY/fwp/bVSGIRtF51F91C84S4NeZJwtQF3i0n10fj6LlcVKeRmLcvSuVOY8um/xK4lKc2D886PGYHEECcmriqabZzttepxc5rygBBgy0Ee24DqGFtsPa2lkSvi0zTXR4M+tnKwV5Uo0WU1X1+AEvkfazsZGpWyUcSZTq9B9AYJcvZn6MO/xYfeTeSd5XsQvbgiKkwOOY/PGTnPDovy7MrEA24tWeHeYIOSqdo8w3hMFNKoTma4pHlIfIYXT92rGh2ZqdbAgMBAAECggEAHBX2GbO70lGdofLIQfk466JxLXc2tNEX94aiStgkcQESoR+RWTSFbdSejUPEQOkeNzljQnef/znBEbdg5+VpZHKhgsFCPgNzmZ6Pq1yIUJb8zldAgGVy/bLvBJn0ZKftB57+K/0VV0hu/hwpLLH+ESB3Xaw/8UXlx9KtL3Hifst94yMNh2+g1qHK7BIBxVlZmm8y137lt/ZJCCKf7zDOBFFmm/Hy71K7C6uOGmRmJ7Thq7f2dJx8iecPvMv7Bzd+LzvuZdJjowZkzMXPv85hUEpbToEQUHt4FReWWk87OUSRaXUZVONZvzUHMrYqN84tvONSa+swnxGWvjK2rRcSdQKBgQDyFeqQGPzJQY2u0rhMuj12yDTssSV7xagw88KYkaWpireuSEjxJpIEhRxjO3Ow0TYvZM9t7g/6gvuYh30/FIND4AwAuRtjAjKzXfOlIG7dJMBEk5yY1tloGwxak8sa8z+H+jw9cJik+X5j6MULNzUb709UPgMstxEVrCfhgEal/QKBgQDawYXIq1q43qIuFdjFOnZ6qBHtqWtIi8lZcOSUtPuqivIVSJt7AnkNhR9MfhkgaEuoL55wBR0BA+DoZWFmObgi5Y+Udb2+nlZVufY/ksNyZ1i+4ITKhzns7qjG2W0KMh7MG0biNbTtBy54WQp+Myog1UbuXMqyMSIunwRhOC5WNwKBgQDaDK+IN1mJlUgezaI/SgkOsmopP645e+Fwpj8C2T1UJqQnkOhSfaFL/PGC2AvumaKqsay8oY823z/rNS604K8TNfzZseFfHp24Pcm1VC9HdVDQ8/w7FlogkSxhcXmhvrPcsKIN3RtAjZEwQHsrDQEDNlxHzsthPUtgL/6D+NypqQKBgQDWN2Y8ENkBajrk6E3jLZHyIP2Vfy9o1kJxTKT6npRH+FKB1x0yg0Rpoe/5Zw5R9vAHncIILmmtoc+vo/+SLHmN6sEEf5w0uzcOMdHbhWvRbzSvjotbDLsDst4iA67MqjIQa7GpOTCYYEz4WzF0TcQ2bMRODR4NCyJPBzcxwONXVwKBgH8iJJtdmftjFqOkqNLT6XzLdPE4grkSzd2/RGiK003L3n0CxE2UqzNW/oSikAO5Jc/Tsxv6FtL9o/0Kjhut05C96Ku1oJfm7QI9ORSgCl/9Lv97GMw4E9nCTsgXqND6FMI7UqdgywpAo2hJSyE7N4vieulSJWJd5GaW/ubILwAM
将配置的公钥私钥加载到bean中,如下:
@Autowiredprivate JwkKeyProperties jwkKeyProperties;@Beanpublic JWKSource<SecurityContext> jwkSource() {RSAKey rsaKey = new RSAKey.Builder(jwkKeyProperties.rsaPublicKey()).privateKey(jwkKeyProperties.privateKey()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}
至此,授权服务器固定好公钥私钥后,我们将这些公钥、私钥用到资源服务器,如下:
# JWK 配置
cloud:jwk:rsa:# 公钥public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzt2qx7HwpblnbwtRV9ZiaXwvUO4JlOYYVFEDmINxp5d+Nbih8hmOrlM+4qhr5Ej1Gc+D+TJ2oNoN/wtUaGP38Kf21UhiEbRedRfdQvOEuDXmScLUBd4tJ9dH4+i5XFSnkZi3L0rlTmPLpv8SuJSnNg/POjxmBxBAnJq4qmm2c7bXqcXOa8oAQYMtBHtuA6hhbbD2tpZEr4tM010eDPrZysFeVKNFlNV9fgBL5H2s7GRqVslHEmU6vQfQGCXL2Z+jDv8WH3k3kneV7EL24IipMDjmPzxk5zw6L8uzKxANuLVnh3mCDkqnaPMN4TBTSqE5muKR5SHyGF0/dqxodmanWwIDAQAB# 私钥private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDO3arHsfCluWdvC1FX1mJpfC9Q7gmU5hhUUQOYg3Gnl341uKHyGY6uUz7iqGvkSPUZz4P5Mnag2g3/C1RoY/fwp/bVSGIRtF51F91C84S4NeZJwtQF3i0n10fj6LlcVKeRmLcvSuVOY8um/xK4lKc2D886PGYHEECcmriqabZzttepxc5rygBBgy0Ee24DqGFtsPa2lkSvi0zTXR4M+tnKwV5Uo0WU1X1+AEvkfazsZGpWyUcSZTq9B9AYJcvZn6MO/xYfeTeSd5XsQvbgiKkwOOY/PGTnPDovy7MrEA24tWeHeYIOSqdo8w3hMFNKoTma4pHlIfIYXT92rGh2ZqdbAgMBAAECggEAHBX2GbO70lGdofLIQfk466JxLXc2tNEX94aiStgkcQESoR+RWTSFbdSejUPEQOkeNzljQnef/znBEbdg5+VpZHKhgsFCPgNzmZ6Pq1yIUJb8zldAgGVy/bLvBJn0ZKftB57+K/0VV0hu/hwpLLH+ESB3Xaw/8UXlx9KtL3Hifst94yMNh2+g1qHK7BIBxVlZmm8y137lt/ZJCCKf7zDOBFFmm/Hy71K7C6uOGmRmJ7Thq7f2dJx8iecPvMv7Bzd+LzvuZdJjowZkzMXPv85hUEpbToEQUHt4FReWWk87OUSRaXUZVONZvzUHMrYqN84tvONSa+swnxGWvjK2rRcSdQKBgQDyFeqQGPzJQY2u0rhMuj12yDTssSV7xagw88KYkaWpireuSEjxJpIEhRxjO3Ow0TYvZM9t7g/6gvuYh30/FIND4AwAuRtjAjKzXfOlIG7dJMBEk5yY1tloGwxak8sa8z+H+jw9cJik+X5j6MULNzUb709UPgMstxEVrCfhgEal/QKBgQDawYXIq1q43qIuFdjFOnZ6qBHtqWtIi8lZcOSUtPuqivIVSJt7AnkNhR9MfhkgaEuoL55wBR0BA+DoZWFmObgi5Y+Udb2+nlZVufY/ksNyZ1i+4ITKhzns7qjG2W0KMh7MG0biNbTtBy54WQp+Myog1UbuXMqyMSIunwRhOC5WNwKBgQDaDK+IN1mJlUgezaI/SgkOsmopP645e+Fwpj8C2T1UJqQnkOhSfaFL/PGC2AvumaKqsay8oY823z/rNS604K8TNfzZseFfHp24Pcm1VC9HdVDQ8/w7FlogkSxhcXmhvrPcsKIN3RtAjZEwQHsrDQEDNlxHzsthPUtgL/6D+NypqQKBgQDWN2Y8ENkBajrk6E3jLZHyIP2Vfy9o1kJxTKT6npRH+FKB1x0yg0Rpoe/5Zw5R9vAHncIILmmtoc+vo/+SLHmN6sEEf5w0uzcOMdHbhWvRbzSvjotbDLsDst4iA67MqjIQa7GpOTCYYEz4WzF0TcQ2bMRODR4NCyJPBzcxwONXVwKBgH8iJJtdmftjFqOkqNLT6XzLdPE4grkSzd2/RGiK003L3n0CxE2UqzNW/oSikAO5Jc/Tsxv6FtL9o/0Kjhut05C96Ku1oJfm7QI9ORSgCl/9Lv97GMw4E9nCTsgXqND6FMI7UqdgywpAo2hJSyE7N4vieulSJWJd5GaW/ubILwAM
接下来是资源服务器的过滤器链配置:
@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableWebSecurity
@Configuration
public class ResourceServerConfig {// @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")String issuerUri;@Autowiredprivate JwkKeyProperties jwkKeyProperties;@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> authorize// SecurityConstant.IGNORE_PERM_URLS 不需要权限认证.requestMatchers(RedisOAuth2Constant.IGNORE_PERM_URLS).permitAll()// 其他的需要权限认证.anyRequest().authenticated())// 资源服务配置秘钥.oauth2ResourceServer().jwt(oauth2ResourceServer -> {RSAPublicKey rsaPublicKey = jwkKeyProperties.rsaPublicKey();NimbusJwtDecoder.PublicKeyJwtDecoderBuilder publicKeyJwtDecoderBuilder = NimbusJwtDecoder.withPublicKey(rsaPublicKey);NimbusJwtDecoder nimbusJwtDecoder = publicKeyJwtDecoderBuilder.build();oauth2ResourceServer.decoder(nimbusJwtDecoder);// 解析jwtoauth2ResourceServer.jwtAuthenticationConverter(getJwtToUserAuthenticationConverter());});return http.build();}public JwtToUserAuthenticationConverter getJwtToUserAuthenticationConverter(){return new JwtToUserAuthenticationConverter();}}
每次请求资源服务器的时候,手动根据公钥解析、转行jwt。这时候需要实现 Converter 接口,实现如下:
@Slf4j
//@Component
public class JwtToUserAuthenticationConverter implements Converter<Jwt, UsernamePasswordAuthenticationToken> {@Overridepublic UsernamePasswordAuthenticationToken convert(@NotNull Jwt jwt) {System.out.println("------"+ JSON.toJSONString(jwt));/*** 方案一:从 jwt 中的 claims - >authorities 获取权限信息* 方案二: 根据 jwt.getSubject() 登录名去查数据库对应的用户权限*/CustomUserDetails details = new CustomUserDetails();// todo 调用用户接口String subject = jwt.getSubject();log.info("从jwt获取的用户名:{}",subject);details.setUsername(subject);/*** hasRole 对应的是 ROLE_admin* hasAuthority 对应的是 user:list** 在做权限的时候需要注意 security 权限前缀*/details.setRoles(List.of("admin","user:list","user"));return new UsernamePasswordAuthenticationToken(details, jwt, details.getAuthorities());}}
至此,资源服务器的授权操作及jwt的解析完成。
3、结论
不管根据哪种方案实现,其实都需要了解授权过程。也就是授权(token中存放用户名、授权信息),接着资源服务器的token处理过程,可以直接请求授权服务器,由授权服务器验证;也可以在授权服务器确定固定的公钥私钥,资源服务器自己根据公钥解析token,获取jwt,最后获取用户信息。
以上的源码来源自己开源项目:
oauth2-resource资源服务器
oauth2-server 授权服务器