以前已經經過本系列兩篇文章《單點登陸系列(1)- OAuth2實施方案 》、《單點登陸系列(2)- Spring OAuth2項目搭建》,跟你們介紹了OAuth2的單點登陸方案,以及如何用Spring搭建OAuth2項目。對於有些朋友來講,單點登陸的這個概念仍是比較抽象。vue
我想今天這篇文章,配合文中的一個配置OAuth2的Web平臺,爭取讓不會代碼的人也能輕鬆理解OAuth2的單點登陸。包括此次把上篇文章中代碼重構了一下,優化了不少功能,本文也會單獨作說明。java
網址是:http://kerrysmec.cn:81。作這個web平臺的目的,很大程度是想玩玩vue和移動適配。web
平臺本次作了三個配置的模塊:客戶端、登陸頁和用戶帳號配置。json
回顧一下《單點登陸系列(1)- OAuth2實施方案 》中,每一個第三方平臺若是想接入OAuth2的單點登陸,都須要申請client_id和client_secret。那麼咱們就將這個第三方平臺稱爲客戶端,下面這個界面就是配置新客戶端clientId等參數的。segmentfault
介紹一下需求配置客戶端的幾個參數:瀏覽器
5.Access Token有效期:由於我配置的是Jwt token,有時間限制,單位爲「秒」,如圖中access token在7200秒後過時。
6.Refresh Token有效期:同理,259200秒後失效。app
如圖中,咱們新增了四行數據,即配置了四個接入單點登陸的客戶端,第一個重定向地址爲空,後面三個分別是得帆、新浪、騰訊、百度。框架
那麼該若是訪問客戶端接入單點登陸的網址呢?客戶端表格中操做列的第一個按鈕,點擊打開客戶端詳情彈框,咱們打開baiduClient的彈框,點擊「訪問」按鈕,或者複製單點登陸的訪問地址到瀏覽器就能夠啦。ide
不過這時咱們就會發現,不管咱們訪問這四個客戶端裏面的那一個,都會跳轉到咱們的登陸頁。說明這些第三方的平臺已經受咱們單點登陸的控制了,咱們能夠試一下,在成功登陸任意一個客戶端後,再點擊任意平臺的「訪問」按鈕,就能夠直接進入對應的重定向網址,而不會再被攔截在登陸頁了。(登陸的帳號密碼在後文「帳號配置」中設置)svg
你們細心點會發現,在成功登陸進入重定向的頁面時,重定向url後面跟了一個參數code。在密碼模式裏面就能夠經過調接口,經過這個code的值,獲取包含當前用戶帳號信息的access_token和refresh_token,具體細節就參考本系列第一篇文章,不過咱們能夠用到上述彈框中的參數 Basic Authoriztion 。
若是客戶端中沒有配置重定向url,根據前文說的,咱們能夠在訪問地址中寫上任何網址,不受拘束。
我喜歡多姿多彩的登陸頁,甚至於像谷歌搜索的主頁同樣,一年遇到不一樣的節日,都有不一樣的頁面風格切換。
沒錯,這個登陸頁配置功能,就是讓你能夠實時的切換單點登陸系統的登陸頁。在你喜歡的登陸頁卡片上,點擊「選用」就生效了!我這裏偷懶,沒有設計不少登陸頁,只是弄了幾個不一樣色系的頁面。
既然是單點登陸,用統一的登陸頁,那麼帳號密碼也是惟一入口配置的。在企業裏面員工的帳號密碼有AD、OID等等來管理。就算存在表裏面,也是加密的方式,不會是像我開發的這樣明文顯示。
可是爲了這個平臺的功能完整,我仍是加了這個最簡單的帳號/密碼管理的頁面,方便你們自行操做。建立好「用戶名」和「密碼」,而後就能夠在登陸頁上成功登陸了。
《單點登陸系列(2)- Spring OAuth2項目搭建》 中對於客戶端的配置比較粗糙,是在項目啓動時一次性讀取全部客戶端client的配置信息,放在內存裏面實現的。若是有須要加接入新的客戶端就須要重啓後再生效,咱們能不能把配置信息放在表裏面呢?就像咱們上面Web平臺作的那樣。
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}![]() |
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 |
第一種方式和框架綁的太死,我推薦用這種方式-基於 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); }
和客戶端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")); }