使用JWT作為Spring?Security?OAuth2的token存儲(chǔ)問(wèn)題
序
Spring Security OAuth2的demo在前幾篇文章中已經(jīng)講過(guò)了,在那些模式中使用的都是RemoteTokenService調(diào)用授權(quán)服務(wù)器來(lái)校驗(yàn)token,返回校驗(yàn)通過(guò)的用戶(hù)信息供上下文中獲取
這種方式會(huì)加重授權(quán)服務(wù)器的負(fù)載,你想啊,當(dāng)用戶(hù)沒(méi)授權(quán)時(shí)候獲取token得找授權(quán)服務(wù)器,有token了訪問(wèn)資源服務(wù)器還要訪問(wèn)授權(quán)服務(wù)器,相當(dāng)于說(shuō)每次請(qǐng)求都要訪問(wèn)授權(quán)服務(wù)器,這樣對(duì)授權(quán)服務(wù)器的負(fù)載會(huì)很大
常規(guī)的方式有兩種來(lái)解決這個(gè)問(wèn)題:
- 使用JWT作為T(mén)oken傳遞
- 使用Redis存儲(chǔ)Token,資源服務(wù)器本地訪問(wèn)Redis校驗(yàn)Token
使用JWT與Redis都可以在資源服務(wù)器中進(jìn)行校驗(yàn)Token,從而減少授權(quán)服務(wù)器的工作量
JWT默認(rèn)使用HMACSHA256對(duì)稱(chēng)加密算法,以下記錄下默認(rèn)算法實(shí)現(xiàn)與非對(duì)稱(chēng)RSA算法的集成,使用不同算法加解密測(cè)試方法是一致的,所以放在文章最后
授權(quán)服務(wù)器整合JWT——對(duì)稱(chēng)加解密算法
授權(quán)服務(wù)器整體代碼結(jié)構(gòu)
pom.xml中引入依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.2.1.RELEASE</version> </dependency> <!-- Spring Security OAuth2 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</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這兩個(gè)bean
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public UserDetailsService userDetailsService(){ //主要是配置這個(gè)Bean,用于授權(quán)服務(wù)器配置中注入 return super.userDetailsService(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // @formatter: off auth.inMemoryAuthentication() .withUser("hellxz") .password(passwordEncoder().encode("xyz")) .authorities(Collections.emptyList()); // @formatter: on } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() //所有請(qǐng)求都需要通過(guò)認(rèn)證 .and() .httpBasic() //Basic提交 .and() .csrf().disable(); //關(guān)跨域保護(hù) } }
授權(quán)服務(wù)器配置AuthorizationConfig
@Configuration @EnableAuthorizationServer //開(kāi)啟授權(quán)服務(wù) public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired public UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //允許表單提交 security.allowFormAuthenticationForClients() .checkTokenAccess("permitAll()") .tokenKeyAccess("permitAll()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // @formatter: off clients.inMemory() .withClient("client-a") //client端唯一標(biāo)識(shí) .secret(passwordEncoder.encode("client-a-secret")) //client-a的密碼,這里的密碼應(yīng)該是加密后的 .authorizedGrantTypes("authorization_code", "password", "refresh_token") //授權(quán)模式標(biāo)識(shí),這里主要測(cè)試用password模式,另外refresh_token不是一種模式,但是可以使用它來(lái)刷新access_token(在它的有效期內(nèi)) .scopes("read_user_info") //作用域 .resourceIds("resource1") //資源id,如不需限制資源id,注釋此處即可 .redirectUris("http://localhost:9001/callback"); //回調(diào)地址 // @formatter: on } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenStore(jwtTokenStore()) //設(shè)置jwtToken為tokenStore .accessTokenConverter(jwtAccessTokenConverter());//設(shè)置access_token轉(zhuǎn)換器 } /** * jwt訪問(wèn)token轉(zhuǎn)換器 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("my-sign-key"); //資源服務(wù)器需要配置此選項(xiàng)方能解密jwt的token return converter; } /** * jwt的token存儲(chǔ)對(duì)象 */ @Bean public JwtTokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } }
這里主要是在configure(AuthorizationServerEndpointsConfigurer endpoints)
授權(quán)服務(wù)的端點(diǎn)配置中加入JWT的tokenStore和access_token的轉(zhuǎn)換器,以及這二者的聲明Bean方法
這里使用的是默認(rèn)對(duì)稱(chēng)MAC算法,即加密解密使用相同的密鑰
啟動(dòng)類(lèi)就不說(shuō)了,開(kāi)啟@SpringBootApplicatin的main方法
資源服務(wù)器整合JWT——對(duì)稱(chēng)加解密算法
資源服務(wù)器主要就一個(gè)資源配置類(lèi)
@Configuration @EnableResourceServer public class ResourceConfig extends ResourceServerConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public void configure(HttpSecurity http) throws Exception { //設(shè)置創(chuàng)建session策略 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); //@formatter:off //所有請(qǐng)求必須授權(quán) http.authorizeRequests() .anyRequest().authenticated(); //@formatter:on } @Override public void configure(ResourceServerSecurityConfigurer resources) { //@formatter:off //如不需要限制資源id,請(qǐng)?jiān)谑跈?quán)配置處去除resourceIds的配置 resources.resourceId("resource1") .tokenStore(jwtTokenStore()); //@formatter:on } /** * jwt訪問(wèn)token轉(zhuǎn)換器 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("my-sign-key"); //與授權(quán)服務(wù)器相同的signingKey return converter; } /** * jwt的token存儲(chǔ)對(duì)象 */ @Bean public JwtTokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } }
配置JWT的TokenStore和AccessTokenConverter與授權(quán)服器相同,添加啟動(dòng)類(lèi)完成配置
OAuth整合JWT——非對(duì)稱(chēng)加解密RSA
本部分基于對(duì)稱(chēng)加密部分,僅展示需要修改的部分
首先使用keytool生成jks (Java Key Store) 密鑰,按提示輸入姓氏等信息
keytool -genkeypair -alias hellxz-jwt -validity 3650 -keyalg RSA -keypass hellxzTest -keystore hellxz-jwt.jks -storepass hellxzTest
生成的私鑰文件會(huì)在當(dāng)前目錄,把hellxz-jwt.jks復(fù)制到授權(quán)服務(wù)器的resources目錄下
授權(quán)服務(wù)器需修改jwtAccessTokenConverter()
@Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyStoreKeyFactory storeKeyFactory = new KeyStoreKeyFactory( new ClassPathResource("hellxz-jwt.jks"), "hellxzTest".toCharArray()); converter.setKeyPair(storeKeyFactory.getKeyPair("hellxz-jwt")); return converter; }
在hellxz-jwt.jks同目錄下,執(zhí)行命令生成公鑰
? keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey
輸入密鑰庫(kù)口令:? 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+UCsrhMC7vpCxDCf0wteXOdV9zNyVk5lJ8M2O77Um4HOq3p8Q9apqUFRVh1X
TMJQMbyKQ3WX0PSbW1JJoqmJOtTbIRQZSOL7v1SvLtjwEKURfp3gJOX1NACEvmDf
YagkRzRLbL7Rup6pSy/WdS30qIlP2SI68D5DujZPw4e6pBP2V7uS+YiLiBJfm9I2
+9lqATZSCWHNAgMBAAGjITAfMB0GA1UdDgQWBBQF96rK7n0XufnvtJuH9tD9Ixza
6zANBgkqhkiG9w0BAQsFAAOCAQEAuMzWZJhej6+4TGgodQKQ5L5RBtOUbesxA1Ue
s9iA4m/jNZnVCXJE0nY47YVzBCIkIsYALswGooMj1PIJxEMpggXVmIuiJpaPgg+4
sthzISxKzX0ru8IrJTapaglMi74ai6S73LTBSke9GEPgWWnbtdUZoUSiSNt1oJ0J
EhFHdPuzxc36neDFRBOBxW4w3qhsTlKTN2wJm1nLV96nFKmqJhQJhhKt6ihe7hMg
qWxzNsWAqv9gJNdKZt5teqwNKT6H7r1NX5oJkJ0Kn1dZy0O3rDDd5E0KDKkMtwOh
3deJH6Uvtt/dw/drzJlByNDEPp6hYGQu2dW5JG5uiHuzFHnJeA==
-----END CERTIFICATE-----
復(fù)制公鑰部分到public.cert放到資源服務(wù)器的resources目錄
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4
ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI
7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4T
Au76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1
l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+
0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2Uglh
zQIDAQAB
-----END PUBLIC KEY-----
修改資源服務(wù)器jwtAccessTokenConverter()方法
@Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); Resource resource = new ClassPathResource("public.cert"); String publicKey; try { publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); } catch (IOException e) { throw new RuntimeException(e); } converter.setVerifierKey(publicKey); return converter; }
測(cè)試驗(yàn)證
發(fā)送POST請(qǐng)求http://localhost:8080/oauth/token?username=hellxz&password=xyz&scope=read_user_info&grant_type=password
返回結(jié)果
帶token訪問(wèn)資源服務(wù)器
測(cè)試通過(guò)
另外使用JWT應(yīng)設(shè)置盡量短的過(guò)期時(shí)間,因?yàn)镴WT的token無(wú)法手動(dòng)revoke,只能等待其到達(dá)過(guò)期時(shí)間失效
到此這篇關(guān)于使用JWT作為Spring Security OAuth2的token存儲(chǔ)的文章就介紹到這了,更多相關(guān)Spring Security OAuth2 token存儲(chǔ)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問(wèn)題
這篇文章主要介紹了如惡化解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11MybatisPlus使用@TableId主鍵id自增長(zhǎng)無(wú)效的解決
本文主要介紹了MybatisPlus使用@TableId主鍵id自增長(zhǎng)無(wú)效的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04idea中啟動(dòng)項(xiàng)目彈出 IDEA out of memory窗口的解決方案
這篇文章主要介紹了idea中啟動(dòng)項(xiàng)目彈出 IDEA out of memory窗口的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Java實(shí)現(xiàn)解出世界最難九宮格問(wèn)題
這篇文章主要介紹了Java實(shí)現(xiàn)解出世界最難九宮格問(wèn)題,芬蘭數(shù)學(xué)家因卡拉花費(fèi)3個(gè)月設(shè)計(jì)出了世界上迄今難度最大的數(shù)獨(dú)游戲,而且它只有一個(gè)答案,本文使用Java實(shí)現(xiàn)解出,需要的朋友可以參考下2015-01-01