如何解决使用JWT作为Spring Security OAuth2的token存储问题
导读:本文共6144字符,通常情况下阅读需要20分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 序Spring Security OAuth3的demo在前几篇文章中已经讲过了,在那些模式中使用的都是RemoteTokenService调用授权服务器来校验token,返回校验通过的用户信息供上下文中获取这种方式会加重授权服务器的负载,你想啊,当用户没授权时候获取token得找授权服务器,有token了访问资源服务器还要访问授权服务器,相当于说每次请求都要访... ...
目录
(为您整理了一些要点),点击可以直达。序
Spring Security OAuth3的demo在前几篇文章中已经讲过了,在那些模式中使用的都是RemoteTokenService调用授权服务器来校验token,返回校验通过的用户信息供上下文中获取
这种方式会加重授权服务器的负载,你想啊,当用户没授权时候获取token得找授权服务器,有token了访问资源服务器还要访问授权服务器,相当于说每次请求都要访问授权服务器,这样对授权服务器的负载会很大
常规的方式有两种来解决这个问题:
使用JWT作为Token传递
使用Redis存储Token,资源服务器本地访问Redis校验Token
使用JWT与Redis都可以在资源服务器中进行校验Token,从而减少授权服务器的工作量
JWT默认使用HMACSHA256对称加密算法,以下记录下默认算法实现与非对称RSA算法的集成,使用不同算法加解密测试方法是一致的,所以放在文章最后
授权服务器整合JWT——对称加解密算法
授权服务器整体代码结构
pom.xml中引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.2.1.RELEASE</version></dependency> <!--SpringSecurityOAuth3--><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth3</artifactId><version>2.4.0.RELEASE</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.1.0.RELEASE</version></dependency>
SecurityConfig配置,主要需要显式声明AuthenticationManager和UserDetailsService这两个bean
@Configuration@EnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@BeanpublicAuthenticationManagerauthenticationManager()throwsException{returnsuper.authenticationManager();}@BeanpublicUserDetailsServiceuserDetailsService(){//主要是配置这个Bean,用于授权服务器配置中注入returnsuper.userDetailsService();}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{//@formatter:offauth.inMemoryAuthentication().withUser("hellxz").password(passwordEncoder().encode("xyz")).authorities(Collections.emptyList());//@formatter:on}@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().anyRequest().authenticated()//所有请求都需要通过认证.and().httpBasic()//Basic提交.and().csrf().disable();//关跨域保护}}
授权服务器配置AuthorizationConfig
@Configuration@EnableAuthorizationServer//开启授权服务publicclassAuthorizationConfigextendsAuthorizationServerConfigurerAdapter{@AutowiredprivateAuthenticationManagerauthenticationManager;@AutowiredpublicUserDetailsServiceuserDetailsService;@AutowiredprivatePasswordEncoderpasswordEncoder;@Overridepublicvoidconfigure(AuthorizationServerSecurityConfigurersecurity)throwsException{//允许表单提交security.allowFormAuthenticationForClients().checkTokenAccess("permitAll()").tokenKeyAccess("permitAll()");}@Overridepublicvoidconfigure(ClientDetailsServiceConfigurerclients)throwsException{//@formatter:offclients.inMemory().withClient("client-a")//client端唯一标识.secret(passwordEncoder.encode("client-a-secret"))//client-a的密码,这里的密码应该是加密后的.authorizedGrantTypes("authorization_code","password","refresh_token")//授权模式标识,这里主要测试用password模式,另外refresh_token不是一种模式,但是可以使用它来刷新access_token(在它的有效期内).scopes("read_user_info")//作用域.resourceIds("resource1")//资源id,如不需限制资源id,注释此处即可.redirectUris("http://localhost:9001/callback");//回调地址//@formatter:on}@Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService).tokenStore(jwtTokenStore())//设置jwtToken为tokenStore.accessTokenConverter(jwtAccessTokenConverter());//设置access_token转换器}/***jwt访问token转换器*/@BeanpublicJwtAccessTokenConverterjwtAccessTokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();converter.setSigningKey("my-sign-key");//资源服务器需要配置此选项方能解密jwt的tokenreturnconverter;}/***jwt的token存储对象*/@BeanpublicJwtTokenStorejwtTokenStore(){returnnewJwtTokenStore(jwtAccessTokenConverter());}}
这里主要是在configure(AuthorizationServerEndpointsConfigurer endpoints)
授权服务的端点配置中加入JWT的tokenStore和access_token的转换器,以及这二者的声明Bean方法
这里使用的是默认对称MAC算法,即加密解密使用相同的密钥
启动类就不说了,开启@SpringBootApplicatin的main方法
资源服务器整合JWT——对称加解密算法
资源服务器主要就一个资源配置类
@Configuration@EnableResourceServerpublicclassResourceConfigextendsResourceServerConfigurerAdapter{@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}@Overridepublicvoidconfigure(HttpSecurityhttp)throwsException{//设置创建session策略http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);//@formatter:off//所有请求必须授权http.authorizeRequests().anyRequest().authenticated();//@formatter:on}@Overridepublicvoidconfigure(ResourceServerSecurityConfigurerresources){ //@formatter:off //如不需要限制资源id,请在授权配置处去除resourceIds的配置 resources.resourceId("resource1") .tokenStore(jwtTokenStore()); //@formatter:on}/***jwt访问token转换器*/@BeanpublicJwtAccessTokenConverterjwtAccessTokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();converter.setSigningKey("my-sign-key");//与授权服务器相同的signingKeyreturnconverter;}/***jwt的token存储对象*/@BeanpublicJwtTokenStorejwtTokenStore(){returnnewJwtTokenStore(jwtAccessTokenConverter());}}
配置JWT的TokenStore和AccessTokenConverter与授权服器相同,添加启动类完成配置
OAuth整合JWT——非对称加解密RSA
本部分基于对称加密部分,仅展示需要修改的部分
首先使用keytool生成jks (Java Key Store) 密钥,按提示输入姓氏等信息
keytool -genkeypair -alias hellxz-jwt -validity 3650 -keyalg RSA -keypass hellxzTest -keystore hellxz-jwt.jks -storepass hellxzTest
生成的私钥文件会在当前目录,把hellxz-jwt.jks复制到授权服务器的resources目录下
授权服务器需修改jwtAccessTokenConverter()
@BeanpublicJwtAccessTokenConverterjwtAccessTokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();KeyStoreKeyFactorystoreKeyFactory=newKeyStoreKeyFactory(newClassPathResource("hellxz-jwt.jks"),"hellxzTest".toCharArray());converter.setKeyPair(storeKeyFactory.getKeyPair("hellxz-jwt"));returnconverter;}
在hellxz-jwt.jks同目录下,执行命令生成公钥
➜ keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey
输入密钥库口令: hellxzTest
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4
ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI
7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4T
Au76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1
l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+
0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2Uglh
zQIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDUTCCAjmgAwIBAgIEePeDczANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJD
TjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEKMAgGA1UEChMB
MDEKMAgGA1UECxMBMDEOMAwGA1UEAxMFemhhbmcwHhcNMTkxMjE1MDUyOTM2WhcN
MjkxMjEyMDUyOTM2WjBZMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHYmVpamluZzEQ
MA4GA1UEBxMHYmVpamluZzEKMAgGA1UEChMBMDEKMAgGA1UECxMBMDEOMAwGA1UE
AxMFemhhbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFTvO6UVRU
FeZkPbzHAzi6Xl73IWtOguBYoeU0uWn3Tj8ZuJYGhni1wFw2rdXEsYE31U6p8/U/
kLt9GDP3lQjtKEoIqCwUUYvasCqymUwMKU39p1+zGakXTqtUiQaBx721HRDSI41x
o35v+UCsrhMC7vpCxDCf0wteXOdV9zNyVk5lJ8M2O77Um4HOq3p8Q9apqUFRVh2X
TMJQMbyKQ3WX0PSbW1JJoqmJOtTbIRQZSOL7v1SvLtjwEKURfp3gJOX1NACEvmDf
YagkRzRLbL7Rup6pSy/WdS30qIlP2SI68D5DujZPw4e6pBP2V7uS+YiLiBJfm9I2
+9lqATZSCWHNAgMBAAGjITAfMB0GA1UdDgQWBBQF96rK7n0XufnvtJuH9tD9Ixza
6zANBgkqhkiG9w0BAQsFAAOCAQEAuMzWZJhej6+4TGgodQKQ5L5RBtOUbesxA1Ue
s9iA4m/jNZnVCXJE0nY47YVzBCIkIsYALswGooMj1PIJxEMpggXVmIuiJpaPgg+4
sthzISxKzX0ru8IrJTapaglMi74ai6S73LTBSke9GEPgWWnbtdUZoUSiSNt1oJ0J
EhFHdPuzxc36neDFRBOBxW4w3qhsTlKTN2wJm1nLV96nFKmqJhQJhhKt6ihe7hMg
qWxzNsWAqv9gJNdKZt5teqwNKT6H7r1NX5oJkJ0Kn1dZy0O3rDDd5E0KDKkMtwOh
3deJH6Uvtt/dw/drzJlByNDEPp6hYGQu2dW5JG5uiHuzFHnJeA==
-----END CERTIFICATE-----
复制公钥部分到public.cert放到资源服务器的resources目录
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4
ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI
7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4T
Au76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1
l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+
0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2Uglh
zQIDAQAB
-----END PUBLIC KEY-----
修改资源服务器jwtAccessTokenConverter()方法
@BeanpublicJwtAccessTokenConverterjwtAccessTokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();Resourceresource=newClassPathResource("public.cert");StringpublicKey;try{publicKey=newString(FileCopyUtils.copyToByteArray(resource.getInputStream()));}catch(IOExceptione){thrownewRuntimeException(e);}converter.setVerifierKey(publicKey);returnconverter;}
测试验证
发送POST请求http://localhost:8080/oauth/token?username=hellxz&password=xyz&scope=read_user_info&grant_type=password
返回结果
带token访问资源服务器
测试通过
另外使用JWT应设置尽量短的过期时间,因为JWT的token无法手动revoke,只能等待其到达过期时间失效
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
如何解决使用JWT作为Spring Security OAuth2的token存储问题的详细内容,希望对您有所帮助,信息来源于网络。