單點登陸系列(3) - 一個配置OAuth2的平臺

概述

以前已經經過本系列兩篇文章《單點登陸系列(1)- OAuth2實施方案 》《單點登陸系列(2)- Spring OAuth2項目搭建》,跟你們介紹了OAuth2的單點登陸方案,以及如何用Spring搭建OAuth2項目。對於有些朋友來講,單點登陸的這個概念仍是比較抽象。vue

我想今天這篇文章,配合文中的一個配置OAuth2的Web平臺,爭取讓不會代碼的人也能輕鬆理解OAuth2的單點登陸。包括此次把上篇文章中代碼重構了一下,優化了不少功能,本文也會單獨作說明。java

OAuth2 Web配置平臺

網址是:http://kerrysmec.cn:81。作這個web平臺的目的,很大程度是想玩玩vue和移動適配。web

平臺本次作了三個配置的模塊:客戶端、登陸頁和用戶帳號配置。json

  1. 客戶端配置:第三方平臺地址接入OAuth2的配置。
  2. 登陸頁配置:切換自定義登陸頁。
  3. 帳號配置:登陸帳號密碼管理。

1.客戶端配置

回顧一下《單點登陸系列(1)- OAuth2實施方案 》中,每一個第三方平臺若是想接入OAuth2的單點登陸,都須要申請client_id和client_secret。那麼咱們就將這個第三方平臺稱爲客戶端,下面這個界面就是配置新客戶端clientId等參數的。segmentfault

75dd91fed1124bc4b4ffa27d45060f4f.png

介紹一下需求配置客戶端的幾個參數:瀏覽器

  1. 客戶端編碼:client_id,業務主鍵
  2. 客戶端密鑰:client_secret,密鑰
  3. 受權範圍:只選取了經常使用的「受權碼模式」和「密碼模式」,authorization_code能夠經過「受權碼模式」調整登陸頁;password能夠經過「密碼模式」調接口獲取用戶token信息;refresh_token則能夠經過refresh_token獲取最新的access_token。
  4. 重定向url:是配置用戶在登陸頁登陸成功後,重定向跳轉到該客戶端的頁面地址。這裏值得注意的是:(1)若是配置了「重定向url」,單點登陸就只能重定向到該地址;(2)若是沒有配置「重定向url」,該字段爲空,則能夠重定向到自定義拼接的任意地址。

5.Access Token有效期:由於我配置的是Jwt token,有時間限制,單位爲「秒」,如圖中access token在7200秒後過時。
6.Refresh Token有效期:同理,259200秒後失效。app

如圖中,咱們新增了四行數據,即配置了四個接入單點登陸的客戶端,第一個重定向地址爲空,後面三個分別是得帆、新浪、騰訊、百度。框架

1.png

那麼該若是訪問客戶端接入單點登陸的網址呢?客戶端表格中操做列的第一個按鈕,點擊打開客戶端詳情彈框,咱們打開baiduClient的彈框,點擊「訪問」按鈕,或者複製單點登陸的訪問地址到瀏覽器就能夠啦。ide

不過這時咱們就會發現,不管咱們訪問這四個客戶端裏面的那一個,都會跳轉到咱們的登陸頁。說明這些第三方的平臺已經受咱們單點登陸的控制了,咱們能夠試一下,在成功登陸任意一個客戶端後,再點擊任意平臺的「訪問」按鈕,就能夠直接進入對應的重定向網址,而不會再被攔截在登陸頁了。(登陸的帳號密碼在後文「帳號配置」中設置)svg

你們細心點會發現,在成功登陸進入重定向的頁面時,重定向url後面跟了一個參數code。在密碼模式裏面就能夠經過調接口,經過這個code的值,獲取包含當前用戶帳號信息的access_token和refresh_token,具體細節就參考本系列第一篇文章,不過咱們能夠用到上述彈框中的參數 Basic Authoriztion 。

若是客戶端中沒有配置重定向url,根據前文說的,咱們能夠在訪問地址中寫上任何網址,不受拘束。

2.png

3.png

2.登陸頁選擇

我喜歡多姿多彩的登陸頁,甚至於像谷歌搜索的主頁同樣,一年遇到不一樣的節日,都有不一樣的頁面風格切換。

沒錯,這個登陸頁配置功能,就是讓你能夠實時的切換單點登陸系統的登陸頁。在你喜歡的登陸頁卡片上,點擊「選用」就生效了!我這裏偷懶,沒有設計不少登陸頁,只是弄了幾個不一樣色系的頁面。

4.png

3.帳號管理

既然是單點登陸,用統一的登陸頁,那麼帳號密碼也是惟一入口配置的。在企業裏面員工的帳號密碼有AD、OID等等來管理。就算存在表裏面,也是加密的方式,不會是像我開發的這樣明文顯示。

可是爲了這個平臺的功能完整,我仍是加了這個最簡單的帳號/密碼管理的頁面,方便你們自行操做。建立好「用戶名」和「密碼」,而後就能夠在登陸頁上成功登陸了。

5.png

Spring OAuth2 的補充

《單點登陸系列(2)- Spring OAuth2項目搭建》 中對於客戶端的配置比較粗糙,是在項目啓動時一次性讀取全部客戶端client的配置信息,放在內存裏面實現的。若是有須要加接入新的客戶端就須要重啓後再生效,咱們能不能把配置信息放在表裏面呢?就像咱們上面Web平臺作的那樣。

1.client 基於表

Spring OAuth2的官方框架裏面就封裝了表oauth_client_details,要求只要你按照他的要求建一個一樣的表,將讀取client的方式換成這種方式,就能夠實現動態的從表裏面讀取client的配置信息。

AuthorizationServerConfigurerAdapter.java

@Resourceprivate DataSource dataSource; 
@Bean public ClientDetailsService clientDetailsService() {    
return new JdbcClientDetailsService(dataSource); 
} 

@Override public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {  
clients.withClientDetails(clientDetailsService());
 }

oauth_client_details 表結構

字段名 字段描述 詳細描述 範例
client_id 主鍵,必須惟一,不能爲空 用於惟一標識每個客戶端(client);註冊時必須填寫(也能夠服務端自動生成),這個字段是必須的,實際應用也有叫app_key OaH1heR2E4eGnBr87Br8FHaUFrA2Q0kE8HqZgpdg8Sw
resource_ids 不能爲空,用逗號分隔 客戶端能訪問的資源id集合,註冊客戶端時,根據實際須要可選擇資源id,也能夠根據不一樣的額註冊流程,賦予對應的額資源id order-resource,pay-resource
client_secret 必須填寫 註冊填寫或者服務端自動生成,實際應用也有叫app_secret, 必需要有前綴表明加密方式 {bcrypt}07240e742f4facca14c99fe78c2cff2b.svg+xmlgY/Hauph1tqvVWiH4atxteSH8sRX03IDXRIQi03DVTFGzKfz8ZtGi
scope 不能爲空,用逗號分隔 指定client的權限範圍,好比讀寫權限,好比移動端仍是web端權限 read,write / web,mobile
authorized_grant_types 不能爲空 可選值 受權碼模式:authorization_code,密碼模式:password,刷新token: refresh_token, 隱式模式: implicit: 客戶端模式: client_credentials。支持多個用逗號分隔 password,refresh_token
web_server_redirect_uri 可爲空 客戶端重定向uri,authorization_code和implicit須要該值進行校驗,註冊時填寫, http://baidu.com
authorities 可爲空 指定用戶的權限範圍,若是受權的過程須要用戶登錄,該字段不生效,implicit和client_credentials須要 ROLE_ADMIN,ROLE_USER
access_token_validity 可空 設置access_token的有效時間(秒),默認(606012,12小時) 3600
refresh_token_validity 可空 設置refresh_token有效期(秒),默認(606024*30, 30填) 7200
additional_information 可空 值必須是json格式 {"key", "value"}
autoapprove false/true/read/write 默認false,適用於authorization_code模式,設置用戶是否自動approval操做,設置true跳過用戶確認受權操做頁面,直接跳到redirect_uri false

2.client基於接口(ClientDetailsService)

第一種方式和框架綁的太死,我推薦用這種方式-基於 ClientDetailsService 接口來實現。我在mongo裏面本身建立了一個表,不用要求徹底和第一種方式的表結構同樣,只要實現了該接口就行。

OauthClientServiceImpl.java

@Service
public class OauthClientServiceImpl implements ClientDetailsService {
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private PaasDao paasDao;


    @Override
    public BaseClientDetails loadClientByClientId(String clientId)  {
        OauthClientEO client = paasDao.getClientByClientId(clientId);
        if (client == null) {
            throw new ClientRegistrationException(clientId+"無效") ;
        }
        BaseClientDetails baseClientDetails=new BaseClientDetails();
        baseClientDetails.setClientId(client.getClientId());
        baseClientDetails.setClientSecret(EncoderUtil.encryptByBCrypt(client.getClientSecret()));
        baseClientDetails.setAccessTokenValiditySeconds(client.getAccessTokenValidity());
        baseClientDetails.setRefreshTokenValiditySeconds(client.getRefreshTokenValidity());
        baseClientDetails.setAuthorizedGrantTypes(Arrays.asList(client.getAuthorizedGrantTypes().split(",")));
        Collection<SimpleGrantedAuthority> collection = new HashSet<>();
        collection.add(new SimpleGrantedAuthority("ROLE_USER"));
        baseClientDetails.setAuthorities(collection);
        Set<String> scope = new TreeSet<String>();
        scope.add("user_info");
        baseClientDetails.setScope(scope);
        baseClientDetails.setAutoApproveScopes(scope);
        return baseClientDetails;
    }
}

AuthorizationServerConfigurerAdapter.java

/**
 * 客戶端驗證,自定義實現oauthClientService接口
 */
 @Autowired
    private OauthClientServiceImpl oauthClientServiceImpl;

 @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(oauthClientServiceImpl).clients(oauthClientServiceImpl);
    }

3.user基於接口(UserDetailsService)

和客戶端client同樣,帳號/密碼的校驗也是能夠經過實現接口來完成的。代碼中 PasswordEncoderImpl( ) 是我自定義的加密類,由於企業中密碼通常都是加密的,好比base64+md5,這時候就能夠自定義去實現這個加密類裏面的方法。

UserDetailsServiceImpl.java

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private PaasDao paasDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws BadCredentialsException {
        //enable :用戶已失效
        //accountNonExpired:用戶賬號已過時
        //credentialsNonExpired:壞的憑證
        //accountNonLocked:用戶帳號已鎖定
        //String password= userFeign.loadUserByUsername(username);
        UserEO userEO=paasDao.getUserByUsername(username);
        return new User(username, new PasswordEncoderImpl().encode(userEO.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));

    }
相關文章
相關標籤/搜索