認證鑑權與API許可權控制在微服務架構中的設計與實現:升級

aoho發表於2019-03-01

概述

在之前的系列文章認證鑑權與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命令。

認證鑑權與API許可權控制在微服務架構中的設計與實現:升級

即可啟動我們所需要的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

新增了來自AuthenticationManagerConfigAuthenticationManager配置。

由於Spring Security5預設PasswordEncoder不是NoOpPasswordEncoder,需要手動指定。原來的auth專案中沒有對密碼進行加密,NoOpPasswordEncoder已經被廢棄,只適合在測試環境中使用,本次我們使用SCryptPasswordEncoder密碼加密器對密碼進行加解密,更貼近產線的使用。其他的演算法還有Pbkdf2PasswordEncoderBCryptPasswordEncoder

關於Scrpyt演算法,可以肯定的是其很難被攻擊。

Scrpyt演算法是由著名的FreeBSD黑客 Colin Percival為他的備份服務 Tarsnap開發的,當初的設計是為了降低CPU負荷,儘量少的依賴cpu計算,利用CPU閒置時間進行計算,因此scrypt不僅計算所需時間長,而且佔用的記憶體也多,使得平行計算多個摘要異常困難,因此利用rainbow table進行暴力攻擊更加困難。Scrpyt沒有在生產環境中大規模應用,並且缺乏仔細的審察和廣泛的函式庫支援。所以Scrpyt一直沒有推廣開,但是由於其記憶體依賴的設計特別符合當時對抗專業礦機的設計,成為數字貨幣演算法發展的一個主要應用方向。

而BCrypt相對出現的時間更久,也很安全。Spring Security中的BCryptPasswordEncoder方法採用SHA-256 + 隨機鹽 + 金鑰對密碼進行加密。SHA系列是Hash演算法,不是加密演算法,使用加密演算法意味著可以解密(這個與編碼/解碼一樣),但是採用Hash處理,其過程是不可逆的。

  1. 加密(encode):註冊使用者時,使用SHA-256+隨機鹽+金鑰把使用者輸入的密碼進行hash處理,得到密碼的hash值,然後將其存入資料庫中。
  2. 密碼匹配(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-…

訂閱最新文章,歡迎關注我的公眾號

微信公眾號

相關閱讀

認證鑑權與API許可權控制在微服務架構中的設計與實現

參考

scrypt演算法的前世今生(從零開始學區塊鏈 192)

相關文章