從零開始SpringCloud Alibaba電商系統(九)——基於Spring Security OAuth2實現SSO-認證服務器(非JWT)

零、系列

歡迎來嫖從零開始SpringCloud Alibaba電商系列:

  1. 從零開始SpringCloud Alibaba電商系統(一)——Alibaba與Nacos服務註冊與發現
  2. 從零開始SpringCloud Alibaba電商系統(二)——Nacos配置中心
  3. 從零開始SpringCloud Alibaba電商系統(三)——Sentinel流量防衛兵介紹、流量控制demo
  4. 從零開始SpringCloud Alibaba電商系統(四)——Sentinel的fallback和blockHandler
  5. 從零開始SpringCloud Alibaba電商系統(五)——Feign Demo,Sentinel+Feign實現多節點間熔斷/服務降級
  6. 從零開始SpringCloud Alibaba電商系統(六)——Sentinel規則持久化到Nacos配置中心
  7. 從零開始SpringCloud Alibaba電商系統(七)——Spring Security實現登錄認證、權限控制
  8. 從零開始SpringCloud Alibaba電商系統(八)——用一個好看的Swagger接口文檔

一、概念

基於Cookie和Session的會話機制

介紹OAuth2之前,讓我們先來回顧一下早期的登錄認證流程。
遠古時期,互聯網上還只是一個共享的地方,沒有誰不能看什麼這樣的概念;隨着時代發展,互聯網引用豐富,基於商業和隱私,人們對登錄認證有了需求,但是大家都知道HTTP本身是無狀態的協議,是沒辦法讓HTTP來識別用戶身份的。

於是在上古時期,HTTP引入了Authorization請求頭,用戶可以將自己的用戶名密碼放入到這個請求頭中,服務端收到一條請求時先解析Authorization請求頭,確認用戶身份。

中古時期,人們覺得HTTP請求頭在自己內網用用還行,放在互聯網上太危險了,畢竟用戶名密碼就在HTTP的請求頭裏面放着,隨便誰攔截了都能看的清清楚楚,都能copy你的信息冒充你來請求資源。

於是大家都開始自己搞認證,而自己搞最常用的方式便是基於Form表單的自定義認證方式,畢竟Form表單中的數據屬於自定義請求數據,而Aithorization屬於HTTP的標準請求頭。Form表單認證,基本上就等同於基於Cookie和Session的會話機制

典型的會話機制如下圖所示,服務器端維護一個會session,客戶端持有這個session的id,這樣只要你登錄一次,服務器端就知道你是誰了。大致流程是這樣的:
在這裏插入圖片描述
越過千年的羈絆,再看現在,由於當下分佈式、微服務的常規化,大部分企業都已經不再是單體應用服務器,那麼Session的使用便成爲一個問題。

SSO就是解決這一類問題的方案的統稱,俗稱單點登錄,即對於用戶來說,登錄系統就是登錄了,我不需要在乎你裏面有幾個子系統,內部複雜度自己搞定。

JWT

衆多解決方案之中,有一個亮眼的存在——JWT(JSON Web Token)

JWT不是基於Session工作的,而是將用戶信息使用一種加密方式全都存放在cookies中,這樣的方式顯然能處理分佈式系統的登錄問題,因爲只要持有JWT令牌,你願意訪問哪個子系統就訪問哪個子系統,你願意何時訪問就何時訪問。

但是問題在於這個隨意,令牌就像央行發出去的鈔票,太難控制了,服務器系統不知道是誰拿着令牌,務器端不進行保存/管理,即jwt令牌一旦發放,其銷燬、續期、登出等功能將很難控制。

話無絕對,人總是聰明的,解決方法總是有的,有興趣的朋友可以多瞭解一下jwt,現在它還是很流行的:https://jwt.io/introduction/

OAuth2

我們今天的主角是OAuth2:OAuth2是一種可用於實現SSO單點登錄的一種協議。

OAuth 2.0關注客戶端開發者的簡易性。要麼通過組織在資源擁有者和HTTP服務商之間的被批准的交互動作代表用戶,要麼允許第三方應用代表用戶獲得訪問的權限。同時爲Web應用,桌面應用和手機,和起居室設備提供專門的認證流程。 ————— 百度百科

OAuth2協議的官方文檔:https://tools.ietf.org/html/rfc6749

綜(說)上(句)所(人)述(話),OAuth2(授權碼模式,我們只關注這個)的核心就是:服務器就負責提供服務,用戶端就負責消費服務,認證和授權的事情都交給認證服務器,資源服務器就只提供資源,簡要思想見下圖。

這個OAuth2怎麼好用了呢?讓我們來用Session作詳細描述(首先讓我們承認直接使用Session比使用JWT這樣方法出去的令牌好簡單方便,對用戶更友好)。

  1. 用戶需要訪問工單系統,需要登錄,於是發送用戶密碼到工單系統。
  2. 工單系統不直接鑑權,而是將用戶請求、自己的回調地址都轉發給認證中心。
  3. 認證中心知道用戶要登錄,於是給用戶一個登錄頁面,讓用戶輸用戶密碼。
  4. 認證中心拿到用戶密碼進行鑑權,認證通過,ok,我認證中心建立一個全局Session,代表這個用戶在我這個系統已經登陸了,然後,由於此時認證中心還持有工單系統的回調地址,所有可以直接發送給工單系統一個授權碼
  5. 工單系統拿到授權碼,再次通過授權碼向工單系統請求授權令牌
  6. 認證中心校驗授權碼,ok沒問題,給你工單系統一個令牌。
  7. 工單系統拿到令牌,就有權限訪問資源服務器上的相關資源了。同時工單系統與用戶建立了局部Session,兩者可以順暢交互。

在這裏插入圖片描述
大家會發現,感覺好像和JWT這種直接將令牌發給用戶的方式有些類似,確實類似,類似在令牌由認證中心授權給工單系統(俗稱client系統),而非直接給用戶。 client系統擁有資源訪問權限,且與用戶建立局部Session後,就可以實現一個用戶端的無Token訪問,即用戶不需要再持有Token令牌了,對於我用戶來說,還和單機時代一樣,持有個局部Session id就行。

工單系統解決了認證問題,那運維繫統呢?按理來說對於我用戶來說已經登錄了,點開運維繫統應該不用登陸了,這才符合SSO單點登錄原則嘛!

對於新的要訪問的子系統,OAuth2是這樣做的:

  1. 用戶對運維繫統發起請求。
  2. 運維繫統同樣將請求、自身回調地址轉發給認證中心。
  3. 認證中心發現該用戶的全局Session存在,不再鑑權,直接給運維繫統一個授權碼
  4. 運維繫統拿到授權碼,向認證中心請求授權令牌
  5. 認證中心校驗授權碼,校驗沒問題,返回授權令牌
  6. 運維繫統拿到授權令牌,可以訪問資源服務器了,同時與用戶建立局部Session

細心的同學可能發現了,運維繫統與工單系統不同的地方僅僅在於,運維繫統的鑑權直接由認證中心完成,沒有再讓用戶來一次登錄操作。除此之外,其他操作都一模一樣。

二、OAuth2認證服務器搭建

  1. 首先來看一下我們的目錄結構,本次不再與之前系列代碼的基礎上做文章,重新建了一個maven父子項目,目前主要兩個子項目:認證中心,公共服務(本次示例中可以當做一個子系統來理解)

    同時對alibaba、nacos、sentinel、feign、oauth2、flywaydb、mybatis-plus等pom進行一次集合。
    Nacos遠端的配置文件內容copy在了resource文件夾下,一見便知,可直接更改地址使用。
    內心OS:依賴地獄真的可怕……
    

    在這裏插入圖片描述

  2. 其次,我們採用認證數據持久化的方式搭建認證服務器。
    樂樂在authorization模塊中加入mysql驅動和flywaydb(mysql版本管理工具),有需要的同學可以直接下載本節源碼,配置好mysql連接,啓動項目即可得到一個完整的數據庫環境。
    在這裏插入圖片描述

  3. 庫表說明。
    目前我們主要需要關注oauth_client_details表,這是配置子系統信息的地方,如client_id-子系統識別id,client_secret-相當於子系統請求認證中心所需要的密碼,authorized_grant_types-授權類型,我們只關注授權碼模式,web_server_redirect_uri-子系統回調地址
    完整詳細的認證端庫表說明:http://www.andaily.com/spring-oauth-server/db_table_description.html

  4. 子系統信息填入。
    我們將common作爲一個client,填入oauth_client_details,之後我們就可以在common子系統中通過這個client_id進行用戶信息請求了,下面我們先只是演示認證服務器如何使用,下節在介紹認證服務器和資源服務器的配合。
    在這裏插入圖片描述

  5. 認證服務器配置,主要是爲了讓認證服務器如何知道來訪的客戶端是誰。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    // 使用druid連接池。
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource dataSourceConfig()  {
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }

    @Bean
    public TokenStore tokenStore() {
        // 保存令牌
        return new JdbcTokenStore(dataSourceConfig());
    }

    @Bean
    public ClientDetailsService jdbcClientDetails() {
        // 獲取client子系統數據
        return new JdbcClientDetailsService(dataSourceConfig());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 設置令牌
        endpoints.tokenStore(tokenStore());
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetails());
    }
}
  1. 配置可認證的用戶,本次先配置一個內存中的用戶。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 設置默認的加密方式
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("USER");

    }
}
  1. 啓動項目。模擬common子系統發起認證請求:http://localhost:8081/oauth/authorize?client_id=common&response_type=code

登錄之後會看到:
在這裏插入圖片描述

  1. 這是在問用戶,是否同意認證中心將數據訪問授權給common子系統。
    選擇Approve,認證,可以看到頁面自動跳轉了一個連接,就是我們在oauth_client_details裏填的web_server_redirect_uri,同時在地址後面附加了一個code,這個code就是之前講的授權碼。
    即,common客戶端獲取到這個code,再去請求一下token令牌,就擁有權限訪問認證中心那邊的資源了,然後與用戶建立局部會話。
    在這裏插入圖片描述

三、Demo

https://github.com/flyChineseBoy/lel-mall/tree/master/mall09

本節內容主要講解了如何使用spring security搭建一個OAuth2.0認證中心,這種認證中心的方式最適合微信、支付寶這一類擁有大量用戶信息的第三方,我們開發小系統可以直接去借用它們的權限系統而不再需要自己設計。
下一次我們使用一個更適合分佈式系統內部鑑權的方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章