二、開發流程
1.1、知識來源
OAuth2開發指導:https://projects.spring.io/spring-security-oauth/docs/oauth2.html
Spring Boot下的OAuth2使用:https://docs.spring.io/spring-security-oauth2-boot/docs/2.2.0.RELEASE/reference/html5/
Spring Cloud Security:
博客參考1:https://www.cnblogs.com/Irving/p/9430460.html
1.2、使用Spring Boot初始化項目工具,勾選Web
當前版本是:Spring Boot2.2.0.RELEASE,Spring Cloud Hoxton.M3
由於Spring Security OAuth2並不在Spring Boot中維護,所以不能在Spring Boot中自動引入依賴
但是Spring Cloud中好像維護了Spring Security OAuth2的版本
生成的工程自動引入了spring-cloud-starter-oauth2依賴,該依賴中包含了spring-security-oauth2-autoconfigure依賴
手動加入spring-security-oauth2-autoconfigure依賴
1.3、參照Spring Boot下OAuth2的指引
增加@EnableAuthorizationServer注解
@EnableAuthorizationServer
@SpringBootApplication
public class SimpleAuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleAuthorizationServerApplication, args);
}
}
- 定義一個客戶端和密碼
security:
oauth2:
client:
client-id: first-client
client-secret: noonewilleverguess
獲取token:
curl client:pwd@localhost:8080/oauth/token -d grant_type=client_credentials -d scope=any
1.4、根據【OAuth2開發指導】配置AuthorizationServerConfigurerAdapter中客戶端明細
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// 配置客戶端明細
@SneakyThrows
@Override
public void configure(ClientDetailsServiceConfigurer clients) {
// 1、基於內存的客戶端明細
clients.inMemory().withClient("client")// 允許訪問的客戶端
.secret("{noop}pwd")// 密碼
.authorizedGrantTypes("refresh_token", "password", "client_credentials")// 允許的授權類型
.scopes("webclient", "mobileclient");// 引用程序作用域
}
}
然后通過客戶端密碼模式獲取token
curl client:pwd@localhost:8080/oauth/token -d grant_type=client_credentials -d scope=webclient
注意,新版本的Spring Security5中,密碼增加了前綴{noop},可以指定密碼的加密內容,只有服務端需要加該前綴,前端傳的是加密后的密碼
修改自定義實現獲取客戶端明細
分別引入jdbc、mysql、druid依賴,這里借助了JdbcClientDetailsService這個實現,進行了擴展,稍微修改了SQL,以及加入了緩存機制
注意一點:這里的sql中,使用了CONCAT('{noop}',client_secret),代表這里的密碼是明文存儲的,以后有需要可以修改這里
再次通過客戶端密碼模式獲取token,客戶端賬號密碼、scope,按數據庫存儲的輸入,即可獲取得到
curl client:pwd@localhost:8080/oauth/token -d grant_type=client_credentials -d scope=webclient
1.5、根據【Spring Boot下的OAuth2使用】配置使用password模式下,用戶的賬號信息獲取
參考https://docs.spring.io/spring-security-oauth2-boot/docs/2.2.0.RELEASE/reference/html5/#oauth2-boot-authorization-server-password-grant-authentication-configuration
要使用OAuth2的Password模式,有多種方式,由於我們使用AuthorizationServerConfigurerAdapter,符合1.7.3所說,如下即可實現
- AuthorizationServerEndpointsConfigurer綁定Security的authenticationManager
- 然后創建一個UserDetailsService的Bean
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationConfiguration authenticationConfiguration;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationConfiguration.getAuthenticationManager())
}
}
@Service
public class HopeUserDetailsService implements UserDetailsService {
@Override
@SneakyThrows
public UserDetails loadUserByUsername(String username) {
Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("role1");
return new HopeUser(1, 1, 1, "hope", "{noop}hope", true, true, true, true,authorities );
}
}
curl client:pwd@localhost:8080/oauth/token -d grant_type=password -d scope=webclient -d username=hope -d password=hope
即可獲取到token
1.7、測試驗證token端點
默認驗證token端點是/oauth/check_token
先配置驗證端點完全開放
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
.checkTokenAccess("permitAll()");
}
}
curl client:pwd@localhost:8080/oauth/check_token -d token=8313a24b-c0d2-437b-afb0-0bb4fd440f47
返回令牌中攜帶信息
1.8、保護資源服務器
使用Spring Boot初始化項目工具,勾選Web
手動加入spring-security-oauth2-autoconfigure依賴
使用@EnableResourceServer注解
配置文件如下
security:
oauth2:
resource:
token-info-uri: http://localhost:8080/oauth/check_token
client:
# 配置后才能認證,生產環境建議設置獨立的客戶端信息
client-id: test
client-secret: test
scope: server
注意,需要開放授權服務器的check_token端點
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
.checkTokenAccess("isAuthenticated()");
}
}
此時,直接訪問資源提示沒有權限
訪問授權服務器,獲取token,使用bear token訪問這個受保護資源,訪問成功!
1.9、使用JWT Token
spring-security-oauth2-autoconfigure已包含jwt依賴,所以不需要處理依賴
授權服務器:
通過AuthorizationServerEndpointsConfigurer配置.accessTokenConverter(jwtAccessTokenConverter),配置一個JwtAccessTokenConverter即可,具體方法下面討論
資源服務器:
配置application配置文件即可。
2者成組配置:
方式1:對稱加密
授權服務器:
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("micosrv_signing_key");
return converter;
}
資源服務器:
security:
oauth2:
resource:
jwt:
key-value: micosrv_signing_key
方式2:非對稱加密(自行賦值)
授權服務器:
config:
oauth2:
# openssl genrsa -out jwt.pem 2048
# openssl rsa -in jwt.pem
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAm4irSNcR7CSSfXconxL4g4M4j34wTWdTv93ocMn4VmdB7rCB
U/BlxXtBUf/cgLIgQhQrAPszSZSmxiEXCOkGPr4aQBQuPgmNIR95Dhbzw/ZN0Bne
cAt3ZfkkDBHv8kH3kR/jYGTdwrxKeDgXGljNsTRhbjuASxPG/Z6gU1yRPCsgc2r8
NYnztWGcDWqaobqjG3/yzFmusoAboyV7asIpo4yk378LmonDNwxnOOTb2Peg5Pee
lwfOwJPbftK1VOOt18zA0cchw6dHUzq9NlB8clps/VdBap9BxU3/0YoFXRIc18ny
zrWo2BcY2KQqX//AJC3OAfrfDmo+BGK8E0mp8wIDAQABAoIBAENp64P45GXMPEpx
eYPpfxnRqJRZh6olHSHOl087243n16YTjxrI2fPMxrU6B2Mo0d6SS0lzl/lOmzLJ
aOiNyA0t7MbVeG2fSjKPJ7M5s5K+kV+fttAtyCTE5iDtLWl9ukaG4dEIJy6e2lBd
T3Y2A4HJSGm1FJh2DAwl0ywOtUy0X6ki9DgXVAaCGDuoU25Rhun64dh802DZbEEJ
LdorIyeJ0ovCZyNvhlZRYkAOPy3k88smYl2jE/AbZ7pCKz/XggDcjNsERm2llaa3
pNTAZQUlHu0BQrCn6J9BxtMPyduiyrE+JYqTwnYhWQ5QRe/2J8O3t0eIK9TfUQpJ
DrZf00ECgYEAy/sLX8UCmERwMuaQSwoM0BHTZIc0iAsgiXbVOLua9I3Tu/mXOVdH
TikjdoWLqM62bA9dN/oqzHDwvqCy6zwamjFVSmJUejf5v+52Qj64leOmDX/RC4ne
L08N1nP/Y4X24Y/5zq18qvVlhOMDdydzayJFrGhkQKhJg58pRUIdenECgYEAwzLC
Awr3LeUlHa+d2O6siJVmljTc8lT+qX4TvqTDH8rAC/EyKMNaTjaX6mWosZZ7qYXv
EMxvQzTEzUHRXrCGlhbX8xiBlWnvpghF2GJEvP9WaU/+OCr0gItRSLPDuZ6ctzKb
3QkBEiC8ODyPRKzlA67D23S3KJB067IUV81h9KMCgYBXUqmT3is2NFYz9DBhb3P8
vyTYLGl4tArBznWJTAcSGoVCO59ZlNuZwlLEMnePVK8To6AsjpQz4UWu1ezCd4CL
8gKpTV8M01m/qL5HrcInqMU1kjpTzjmn1xf9brsuR/NgrNoseGieZ1+GfAjHwcPP
YWSiYi5I38JY7pIkbCFigQKBgAnVtty8YrPXRcV3IbbaX6sKC/8pbrBvA926Unha
iNJDPuXbIzHWleg26/SNZrB76oMiEmeARWLXd8r3s/rXXhCV2g+PfofurHprFEnQ
ubHkE5B+zUo7L9KCMng9RnFFwpOgYyYB3CHzsEgNFRLauzcySP/3o3rRvHJbqJa7
7GGNAoGBAKSBn4zq0iNWI2BUBb90icMsHEneiydGtFcEl3/Sz8vmjFZn0sjRbGoY
gmP9LlQ+o7xRiJ/LTesi5BA6zCGrcdp0aeyJzCRbFc3WqjGeyLbfx1sJVVB6PnvS
iKvvCOJq6kl3/opO+ybqJ8dzkEyoj8K4+fcX1+U6eW2w+vSpOosG
-----END RSA PRIVATE KEY-----
# openssl rsa -in jwt.pem -pubout
publicKey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm4irSNcR7CSSfXconxL4
g4M4j34wTWdTv93ocMn4VmdB7rCBU/BlxXtBUf/cgLIgQhQrAPszSZSmxiEXCOkG
Pr4aQBQuPgmNIR95Dhbzw/ZN0BnecAt3ZfkkDBHv8kH3kR/jYGTdwrxKeDgXGljN
sTRhbjuASxPG/Z6gU1yRPCsgc2r8NYnztWGcDWqaobqjG3/yzFmusoAboyV7asIp
o4yk378LmonDNwxnOOTb2Peg5PeelwfOwJPbftK1VOOt18zA0cchw6dHUzq9NlB8
clps/VdBap9BxU3/0YoFXRIc18nyzrWo2BcY2KQqX//AJC3OAfrfDmo+BGK8E0mp
8wIDAQAB
-----END PUBLIC KEY-----
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
// 自行生成公私鑰
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
資源服務器:
security:
oauth2:
resource:
jwt:
key-value:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm4irSNcR7CSSfXconxL4
g4M4j34wTWdTv93ocMn4VmdB7rCBU/BlxXtBUf/cgLIgQhQrAPszSZSmxiEXCOkG
Pr4aQBQuPgmNIR95Dhbzw/ZN0BnecAt3ZfkkDBHv8kH3kR/jYGTdwrxKeDgXGljN
sTRhbjuASxPG/Z6gU1yRPCsgc2r8NYnztWGcDWqaobqjG3/yzFmusoAboyV7asIp
o4yk378LmonDNwxnOOTb2Peg5PeelwfOwJPbftK1VOOt18zA0cchw6dHUzq9NlB8
clps/VdBap9BxU3/0YoFXRIc18nyzrWo2BcY2KQqX//AJC3OAfrfDmo+BGK8E0mp
8wIDAQAB
-----END PUBLIC KEY-----
方式3:非對稱加密(通過token_key端點)
授權服務器:
在方式2的基礎上,需要配置開放token_key端口,在AuthorizationServerConfigurerAdapter中配置
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.tokenKeyAccess("permitAll()");
}
資源服務器:
security:
oauth2:
resource:
jwt:
key-uri: http://localhost:8080/oauth/token_key
1.10、token傳遞
客戶端添加依賴:alibaba discovery,alibaba config,hystricx斷路器,feign依賴,spring-cloud-starter-security
並且管理版本:spring-cloud-alibaba-dependencies 0.9.0.RELEASE
使用@SpringCloudApplication注解,開啟Cloud App,支持服務注冊於發現和斷路器
使用@EnableFeignClients注解,支持Feign客戶端
配置一個OAuth2FeignRequestInterceptor攔截器,通過AccessTokenContextRelay來傳遞token
AccessTokenContextRelay在@EnableOAuth2Client中有配置,但是我們這里不作為一個OAuth2客戶端來看,可以自己創建一個AccessTokenContextRelay
調用Feign,即可看到token是否能正常傳遞
1.11、JWT過於龐大,應該使用令牌內省這個概念
使用原來的令牌,在資源服務端訪問授權服務器的時候,進行緩存,下次訪問的時候取緩存
參考文章:
冷總:https://cloud.tencent.com/developer/article/1435727
冷總:https://my.oschina.net/giegie/blog/3023768
https://my.oschina.net/u/2518341/blog/3021642
思路
- 參考冷總第二篇文章,使用JWT,然后自己調用check_token,再解析
- 使用原始的訪問check_token方案,注入自己的RestTemplate,增加攔截器,進行緩存和處理
1.12、密碼的加密
pig中應該是網關進行了加密和解密,OAuth2服務器接收的應該是原生密碼,然后通過BCRYPT加密,與數據庫中已經過BCRYPT加密的密碼進行對比
1.13、check_token添加自定義信息
返回值是通過AccessTokenConverter來進行轉換的,我繼承DefaultAccessTokenConverter,並在convertAccessToken中,增加自定義的信息
授權服務器:在AuthorizationServerConfigurerAdapter中注入AccessTokenConverter
資源服務器:在ResourceServerTokenServices中注入AccessTokenConverter