概述
在之前的系列文章認證鑑權與API許可權控制在微服務架構中的設計與實現中,我們有四篇文章講解了微服務下的認證鑑權與API許可權控制的實現。當時基於的Spring Cloud版本為Dalston.SR4
,當前最新的Spring Cloud版本為Finchley.SR1
,對應的Spring Boot也升級到了2.0.x
。Spring Cloud版本為Finchley
和Spring Boot2.0相對之前的版本有較大的變化,至於具體的changes,請參見官網。本次會將專案升級到最新版本,下面具體介紹其中的變化。與使用之前的版本,請切換到1.0-RELEASE
。
升級依賴
將Spring Boot的依賴升級為2.0.4.RELEASE
。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
複製程式碼
升級dependencyManagement中的spring-cloud依賴為Finchley.RELEASE
。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
複製程式碼
刪除spring-cloud-starter-oauth2
依賴,只留下spring-cloud-starter-security
依賴。
工具升級
flyway
我們在專案中,引入了flyway的依賴,用以初始化資料庫的增量指令碼,具體可以參見資料庫版本管理工具Flyway應用。
docker容器
為了更加簡便的體驗本專案,筆者在專案中提供了docker compose指令碼。在本地安裝好docker compose的情況下,進入專案根目錄執行docker-compose up
命令。
即可啟動我們所需要的mysql和redis。
Mybatis和HikariCP
在Spring Boot 2.0.X版本中,選擇了HikariCP作為預設資料庫連線池。所以我們並不需要額外配置DataSource。
Mybatis的mapper和config-location配置也通過配置檔案的形式,因此DatasourceConfig
大大簡化。
application.yml
spring:
flyway:
baseline-on-migrate: true
locations: classpath:db
datasource:
hikari:
connection-test-query: SELECT 1
minimum-idle: 1
maximum-pool-size: 5
pool-name: dbcp1
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/auth?autoReconnect=true&useSSL=false
username: ${AUTH_DB_PWD:root}
password: ${AUTH_DB_USER:_123456_}
# schema[0]: classpath:/auth.sql
# initialization-mode: ALWAYS
type: com.zaxxer.hikari.HikariDataSource
redis:
database: 0
host: localhost
port: 6379
mybatis:
mapper-locations: classpath:/mybatis/mapper/*Mapper.xml
config-location: classpath:/mybatis/config.xml
複製程式碼
配置類升級
AuthenticationManagerConfig
棄用,由於迴圈依賴的問題,將AuthenticationManager
的配置放置到WebSecurityConfig
中。
WebSecurityConfig
新增了來自AuthenticationManagerConfig
的AuthenticationManager
配置。
由於Spring Security5預設PasswordEncoder
不是NoOpPasswordEncoder
,需要手動指定。原來的auth專案中沒有對密碼進行加密,NoOpPasswordEncoder
已經被廢棄,只適合在測試環境中使用,本次我們使用SCryptPasswordEncoder
密碼加密器對密碼進行加解密,更貼近產線的使用。其他的演算法還有Pbkdf2PasswordEncoder
和BCryptPasswordEncoder
。
關於Scrpyt演算法,可以肯定的是其很難被攻擊。
Scrpyt演算法是由著名的FreeBSD黑客 Colin Percival為他的備份服務 Tarsnap開發的,當初的設計是為了降低CPU負荷,儘量少的依賴cpu計算,利用CPU閒置時間進行計算,因此scrypt不僅計算所需時間長,而且佔用的記憶體也多,使得平行計算多個摘要異常困難,因此利用rainbow table進行暴力攻擊更加困難。Scrpyt沒有在生產環境中大規模應用,並且缺乏仔細的審察和廣泛的函式庫支援。所以Scrpyt一直沒有推廣開,但是由於其記憶體依賴的設計特別符合當時對抗專業礦機的設計,成為數字貨幣演算法發展的一個主要應用方向。
而BCrypt相對出現的時間更久,也很安全。Spring Security中的BCryptPasswordEncoder
方法採用SHA-256 + 隨機鹽 + 金鑰對密碼進行加密。SHA系列是Hash演算法,不是加密演算法,使用加密演算法意味著可以解密(這個與編碼/解碼一樣),但是採用Hash處理,其過程是不可逆的。
- 加密(encode):註冊使用者時,使用SHA-256+隨機鹽+金鑰把使用者輸入的密碼進行hash處理,得到密碼的hash值,然後將其存入資料庫中。
- 密碼匹配(matches):使用者登入時,密碼匹配階段並沒有進行密碼解密(因為密碼經過Hash處理,是不可逆的),而是使用相同的演算法把使用者輸入的密碼進行hash處理,得到密碼的hash值,然後將其與從資料庫中查詢到的密碼hash值進行比較。如果兩者相同,說明使用者輸入的密碼正確。
關於怎麼初始化密碼呢,和註冊使用者的時候怎麼給密碼加密,我們可以在初始化密碼時呼叫如下的方法:
SCryptPasswordEncoder sCryptPasswordEncoder = new SCryptPasswordEncoder();
sCryptPasswordEncoder.encode("frontend");
複製程式碼
此時需要對資料庫中的client_secret
進行修改,如把frontend
修改為:
$e0801$65x9sjjnRPuKmqaFn3mICtPYnSWrjE7OB/pKzKTAI4ryhmVoa04cus+9sJcSAFKXZaJ8lcPO1I9H22TZk6EN4A==$o+ZWccaWXSA2t7TxE5VBRvz2W8psujU3RPPvejvNs4U=
複製程式碼
並修改配置如下:
@Autowired
CustomAuthenticationProvider customAuthenticationProvider;
@Autowired
CodeAuthenticationProvider codeAuthenticationProvider;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
auth.authenticationProvider(codeAuthenticationProvider);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new SCryptPasswordEncoder();
}
複製程式碼
ResourceServerConfig
棄用,auth專案不啟用資源伺服器的功能。
OAuth2Config
由於當前版本的spring-boot-redis
中的RedisConnection
缺少#set
方法,直接使用RedisTokenStore
會出現以下異常:
java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V
複製程式碼
因此自定義CustomRedisTokenStore
類,與RedisTokenStore程式碼一致,只是將RedisConnection#set
方法的呼叫替換為RedisConnection#stringCommands#set
,如下所示:
conn.stringCommands().set(accessKey, serializedAccessToken);
conn.stringCommands().set(authKey, serializedAuth);
conn.stringCommands().set(authToAccessKey, serializedAccessToken);
複製程式碼
完整程式碼見文末的GitHub地址。
結果驗證
經過如上的升級改造,我們將驗證如下的API端點:
- password模式獲取token:/oauth/token?grant_type=password
- 重新整理token:/oauth/token?grant_type=refresh_token&refresh_token=…
- 檢驗token:/oauth/check_token
- 登出:/logout
- 授權:/oauth/authorize
- 授權碼模式獲取token:/oauth/token?grant_type=authorization_code
結果就不展示了,都可以正常使用。
小結
OAuth鑑權服務是微服務架構中的一個基礎服務,專案公開之後得到了好多同學的關注,好多同學在加入QQ群之後也提出了自己關於這方面的疑惑或者建議,一起討論和解決疑惑的地方。隨著Spring Boot和Spring Cloud的版本升級,筆者也及時更新了本專案,希望能夠幫到一些童鞋。筆者籌劃的一本關於Spring Cloud應用的書籍,本月即將出版面世,其中關於Spring Cloud Security部分,有著詳細的解析,各位同學可以支援一下正版。
本文的原始碼地址:
GitHub:github.com/keets2012/A…
碼雲: gitee.com/keets/Auth-…