JWT的單點(diǎn)登陸SSO開發(fā)及原理是什么

本篇內(nèi)容介紹了“JWT的單點(diǎn)登陸SSO開發(fā)及原理是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)是一家專注網(wǎng)站建設(shè)、網(wǎng)絡(luò)營銷策劃、微信小程序、電子商務(wù)建設(shè)、網(wǎng)絡(luò)推廣、移動互聯(lián)開發(fā)、研究、服務(wù)為一體的技術(shù)型公司。公司成立十載以來,已經(jīng)為數(shù)千家建筑動畫各業(yè)的企業(yè)公司提供互聯(lián)網(wǎng)服務(wù)?,F(xiàn)在,服務(wù)的數(shù)千家客戶與我們一路同行,見證我們的成長;未來,我們一起分享成功的喜悅。

> ??在學(xué)習(xí)Spring Cloud 時,遇到了授權(quán)服務(wù)oauth 相關(guān)內(nèi)容時,總是一知半解,因此決定先把Spring Security 、Spring Security Oauth3 等權(quán)限、認(rèn)證相關(guān)的內(nèi)容、原理及設(shè)計學(xué)習(xí)并整理一遍。本系列文章就是在學(xué)習(xí)的過程中加強(qiáng)印象和理解所撰寫的,如有侵權(quán)請告知。

> 項目環(huán)境: > - JDK1.8 > - Spring boot 2.x > - Spring Security 5.x

??單點(diǎn)登錄(Single Sign On),簡稱為SSO,是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。 SSO的定義是在多個應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)。 單點(diǎn)登陸本質(zhì)上也是OAuth3的使用,所以其開發(fā)依賴于授權(quán)認(rèn)證服務(wù),如果不清楚的可以看我的上一篇文章。

一、 單點(diǎn)登陸 Demo開發(fā)

??從單點(diǎn)登陸的定義上來看就知道我們需要新建個應(yīng)用程序,我把它命名為 security-sso-client。接下的開發(fā)就在這個應(yīng)用程序上了。

一、Maven 依賴

??主要依賴 spring-boot-starter-security、spring-security-oauth3-autoconfigure、spring-security-oauth3 這3個。其中 spring-security-oauth3-autoconfigure 是Spring Boot 2.X 才有的。

<dependency>
          <groupid>org.springframework.boot</groupid>
          <artifactid>spring-boot-starter-security</artifactid>
      </dependency>
      <dependency>
          <groupid>org.springframework.boot</groupid>
          <artifactid>spring-boot-starter-data-redis</artifactid>
      </dependency>
      <!--@EnableOAuth3Sso 引入,Spring Boot 2.x 將這個注解移到該依賴包-->
      <dependency>
          <groupid>org.springframework.security.oauth.boot</groupid>
          <artifactid>spring-security-oauth3-autoconfigure</artifactid>
          <exclusions>
              <exclusion>
                  <groupid>org.springframework.security.oauth</groupid>
                  <artifactid>spring-security-oauth3</artifactid>
              </exclusion>
          </exclusions>
          <version>2.1.7.RELEASE</version>
      </dependency>
      <!-- 不是starter,手動配置 -->
      <dependency>
          <groupid>org.springframework.security.oauth</groupid>
          <artifactid>spring-security-oauth3</artifactid>
          <!--請注意下 spring-authorization-oauth3 的版本 務(wù)必高于 2.3.2.RELEASE,這是官方的一個bug:
          java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V
          要求必須大于2.3.5 版本,官方解釋:https://github.com/BUG9/spring-security/network/alert/pom.xml/org.springframework.security.oauth:spring-security-oauth3/open
          -->
          <version>2.3.5.RELEASE</version>
      </dependency>
二、單點(diǎn)配置 @EnableOAuth3Sso

??單點(diǎn)的基礎(chǔ)配置引入是依賴 @EnableOAuth3Sso 實(shí)現(xiàn)的,在Spring Boot 2.x 及以上版本 的 @EnableOAuth3Sso 是在 spring-security-oauth3-autoconfigure 依賴?yán)锏?。我這里簡單配置了一下:

@Configuration
@EnableOAuth3Sso
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  public void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
              .antMatchers("/","/error","/login").permitAll()
              .anyRequest().authenticated()
              .and()
              .csrf().disable();
  }
}

?? 因為單點(diǎn)期間可能存在某些問題,會重定向到 /error ,所以我們把 /error 設(shè)置成無權(quán)限訪問。

三、測試接口及頁面
測試接口
@RestController
@Slf4j
public class TestController {

    @GetMapping("/client/{clientId}")
    public String getClient(@PathVariable String clientId) {
        return clientId;
    }

}
測試頁面
  
  
  
      <meta charset="UTF-8">
      <title>OSS-client</title>
  
  
  <h2>OSS-client</h2>
  <a href="http://localhost:8091/client/1">跳轉(zhuǎn)到OSS-client-1</a>
  <a href="http://localhost:8092/client/2">跳轉(zhuǎn)到OSS-client-2</a>
四、單點(diǎn)配置文件配置授權(quán)信息

?? 由于我們要測試多應(yīng)用間的單點(diǎn),所以我們至少需要2個單點(diǎn)客戶端,我這邊通過Spring Boot 的多環(huán)境配置實(shí)現(xiàn)。

application.yml 配置

?? 我們都知道單點(diǎn)實(shí)現(xiàn)本質(zhì)就是Oauth3的授權(quán)碼模式,所以我們需要配置訪問授權(quán)服務(wù)器的地址信息,包括 :

  • security.oauth3.client.user-authorization-uri = /oauth/authorize 請求認(rèn)證的地址,即獲取code 碼

  • security.oauth3.client.access-token-uri = /oauth/token 請求令牌的地址

  • security.oauth3.resource.jwt.key-uri = /oauth/token_key 解析jwt令牌所需要密鑰的地址,服務(wù)啟動時會調(diào)用 授權(quán)服務(wù)該接口獲取jwt key,所以務(wù)必保證授權(quán)服務(wù)正常

  • security.oauth3.client.client-id = client1 clientId 信息

  • security.oauth3.client.client-secret = 123456 clientSecret 信息

其中有幾個配置需要簡單解釋下:

  • security.oauth3.sso.login-path=/login OAuth3授權(quán)服務(wù)器觸發(fā)重定向到客戶端的路徑 ,默認(rèn)為 /login,這個路徑要與授權(quán)服務(wù)器的回調(diào)地址(域名)后的路徑一致

  • server.servlet.session.cookie.name = OAUTH2CLIENTSESSION 解決單機(jī)開發(fā)存在的問題,如果是非單機(jī)開發(fā)可忽略其配置


auth-server: http://localhost:9090 # authorization服務(wù)地址

security: oauth3: client: user-authorization-uri: ${auth-server}/oauth/authorize #請求認(rèn)證的地址 access-token-uri: ${auth-server}/oauth/token #請求令牌的地址 resource: jwt: key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所需要密鑰的地址,服務(wù)啟動時會調(diào)用 授權(quán)服務(wù)該接口獲取jwt key,所以務(wù)必保證授權(quán)服務(wù)正常 sso: login-path: /login #指向登錄頁面的路徑,即OAuth3授權(quán)服務(wù)器觸發(fā)重定向到客戶端的路徑 ,默認(rèn)為 /login

server: servlet: session: cookie: name: OAUTH2CLIENTSESSION # 解決 Possible CSRF detected - state parameter was required but no state could be found 問題 spring: profiles: active: client1

#### application-client1.yml 配置  

?? application-client2 和 application-client1是一樣的,只是端口號和client信息不一樣而已,這里就不再重復(fù)貼出了。

server: port: 8091

security: oauth3: client: client-id: client1 client-secret: 123456

#### 五、單點(diǎn)測試

?? 效果如下:

![/upload/otherpic69/787917.jpg](/upload/otherpic69/787917.jpg)

??從效果圖中我們可以發(fā)現(xiàn),當(dāng)我們第一次訪問client2 的接口時,跳轉(zhuǎn)到了授權(quán)服務(wù)的登陸界面,完成登陸后成功跳轉(zhuǎn)回到了client2 的測試接口,并且展示了接口返回值。此時我們訪問client1 的 測試接口時直接返回(表面現(xiàn)象)了接口返回值。這就是單點(diǎn)登陸的效果,好奇心強(qiáng)的同學(xué)一定會在心里問道:它是如何實(shí)現(xiàn)的? 那么接下來我們就來揭開其面紗。


### 二、 單點(diǎn)登陸原理解析


#### 一、@EnableOAuth3Sso

?? 我們都知道 @EnableOAuth3Sso 是實(shí)現(xiàn)單點(diǎn)登陸的最核心配置注解,那么我們來看下 @EnableOAuth3Sso 的源碼:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @EnableOAuth3Client @EnableConfigurationProperties(OAuth3SsoProperties.class) @Import({ OAuth3SsoDefaultConfiguration.class, OAuth3SsoCustomConfiguration.class, ResourceServerTokenServicesConfiguration.class }) public @interface EnableOAuth3Sso {

}

?? 其中我們關(guān)注4個配置文件的引用: ResourceServerTokenServicesConfiguration 、OAuth3SsoDefaultConfiguration 、 OAuth3SsoProperties 和 @EnableOAuth3Client:
- OAuth3SsoDefaultConfiguration 單點(diǎn)登陸的核心配置,內(nèi)部創(chuàng)建了 SsoSecurityConfigurer 對象, SsoSecurityConfigurer 內(nèi)部 主要是配置 **OAuth3ClientAuthenticationProcessingFilter** 這個單點(diǎn)登陸核心過濾器之一。

- ResourceServerTokenServicesConfiguration  內(nèi)部讀取了我們在 yml 中配置的信息

- OAuth3SsoProperties 配置了回調(diào)地址url ,這個就是 security.oauth3.sso.login-path=/login  匹配的

- @EnableOAuth3Client   標(biāo)明單點(diǎn)客戶端,其內(nèi)部 主要 配置了  **OAuth3ClientContextFilter** 這個單點(diǎn)登陸核心過濾器之一

#### 二、 OAuth3ClientContextFilter

??  OAuth3ClientContextFilter 過濾器類似于  ExceptionTranslationFilter , 它本身沒有做任何過濾處理,只要當(dāng) chain.doFilter() 出現(xiàn)異常后 做出一個重定向處理。 但別小看這個重定向處理,它可是實(shí)現(xiàn)單點(diǎn)登陸的第一步,還記得第一次單點(diǎn)時會跳轉(zhuǎn)到授權(quán)服務(wù)器的登陸頁面么?而這個功能就是 OAuth3ClientContextFilter 實(shí)現(xiàn)的。我們來看下其源碼:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; request.setAttribute(CURRENT_URI, calculateCurrentUri(request)); // 1、記錄當(dāng)前地址(currentUri)到HttpServletRequest

	try {
		chain.doFilter(servletRequest, servletResponse);
	} catch (IOException ex) {
		throw ex;
	} catch (Exception ex) {
		// Try to extract a SpringSecurityException from the stacktrace
		Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
		UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
				.getFirstThrowableOfType(
						UserRedirectRequiredException.class, causeChain);  
		if (redirect != null) {  // 2、判斷當(dāng)前異常 UserRedirectRequiredException 對象 是否為空
			redirectUser(redirect, request, response); // 3、重定向訪問 授權(quán)服務(wù) /oauth/authorize 
		} else {
			if (ex instanceof ServletException) {
				throw (ServletException) ex;
			}
			if (ex instanceof RuntimeException) {
				throw (RuntimeException) ex;
			}
			throw new NestedServletException("Unhandled exception", ex);
		}
	}
}
??  Debug看下:
![微信圖片_20190916173425.png](/upload/otherpic69/787918.jpg)

??整個 filter 分三步:

- 1、記錄當(dāng)前地址(currentUri)到HttpServletRequest 
- 2、判斷當(dāng)前異常 UserRedirectRequiredException 對象 是否為空
- 3、重定向訪問 授權(quán)服務(wù) /oauth/authorize 


#### 三、 OAuth3ClientAuthenticationProcessingFilter 
?? OAuth3ClientContextFilter 過濾器 其要完成的工作就是 通過獲取到的code碼調(diào)用 授權(quán)服務(wù) /oauth/token 接口獲取 token 信息,并將獲取到的token 信息解析成 OAuth3Authentication 認(rèn)證對象。起源如下:

@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

	OAuth3AccessToken accessToken;
	try {
		accessToken = restTemplate.getAccessToken(); //1、  調(diào)用授權(quán)服務(wù)獲取token 
	} catch (OAuth3Exception e) {
		BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
		publish(new OAuth3AuthenticationFailureEvent(bad));
		throw bad;			
	}
	try {
		OAuth3Authentication result = tokenServices.loadAuthentication(accessToken.getValue()); // 2、  解析token信息為 OAuth3Authentication 認(rèn)證對象并返回
		if (authenticationDetailsSource!=null) {
			request.setAttribute(OAuth3AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
			request.setAttribute(OAuth3AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
			result.setDetails(authenticationDetailsSource.buildDetails(request));
		}
		publish(new AuthenticationSuccessEvent(result));
		return result;
	}
	catch (InvalidTokenException e) {
		BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
		publish(new OAuth3AuthenticationFailureEvent(bad));
		throw bad;			
	}

}
?? 整個 filter 2點(diǎn)功能:

- restTemplate.getAccessToken(); //1、  調(diào)用授權(quán)服務(wù)獲取token 
-  tokenServices.loadAuthentication(accessToken.getValue());  // 2、  解析token信息為 OAuth3Authentication 認(rèn)證對象并返回

 ??完成上面步驟后就是一個正常的security授權(quán)認(rèn)證過程,這里就不再講述,有不清楚的同學(xué)可以看下我寫的相關(guān)文章。
 
 

#### 四、 AuthorizationCodeAccessTokenProvider
?? 在講述 OAuth3ClientContextFilter 時有一點(diǎn)沒講,那就是  UserRedirectRequiredException 是 誰拋出來的。 在講述 OAuth3ClientAuthenticationProcessingFilter 也有一點(diǎn)沒講到,那就是它是如何判斷出 當(dāng)前 /login 是屬于 需要獲取code碼的步驟還是去獲取 token 的步驟( 當(dāng)然是判斷/login 是否帶有code 參數(shù),這里主要講明是誰來判斷的)。 這2個點(diǎn)都設(shè)計到了 AuthorizationCodeAccessTokenProvider 這個類。這個類是何時被調(diào)用的?
其實(shí) OAuth3ClientAuthenticationProcessingFilter 隱藏在  restTemplate.getAccessToken();  這個方法內(nèi)部 調(diào)用的 accessTokenProvider.obtainAccessToken() 這里。 我們來看下OAuth3ClientAuthenticationProcessingFilter 的   obtainAccessToken() 方法內(nèi)部源碼:

public OAuth3AccessToken obtainAccessToken(OAuth3ProtectedResourceDetails details, AccessTokenRequest request) throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException, OAuth3AccessDeniedException {

	AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) details;

	if (request.getAuthorizationCode() == null) {  //1、 判斷當(dāng)前參數(shù)是否包含code碼 
		if (request.getStateKey() == null) {
			throw getRedirectForAuthorization(resource, request); //2、 不包含則拋出 UserRedirectRequiredException 異常
		}
		obtainAuthorizationCode(resource, request);
	}
	return retrieveToken(request, resource, getParametersForTokenRequest(resource, request),
			getHeadersForTokenRequest(request)); // 3 、 包含則調(diào)用獲取token 

}
整個方法內(nèi)部分3步:

- 1、 判斷當(dāng)前參數(shù)是否包含code碼 
- 2、 不包含則拋出 UserRedirectRequiredException 異常 
- 3、 包含繼續(xù)獲取token


?? 最后可能有同學(xué)會問,為什么第一個客戶端單點(diǎn)要跳轉(zhuǎn)到授權(quán)服務(wù)登陸頁面去登陸, 而當(dāng)問第二個客戶端卻沒有,其實(shí) 2次 客戶端單點(diǎn)的流程都是一樣的,都是授權(quán)碼模式,但為什么客戶端2 卻不需要登陸呢? 其實(shí)是因為Cookies/Session的原因,因為我們訪問同2個客戶端基本上都是在同一個瀏覽器中進(jìn)行的。 不信的同學(xué)可以試試2個瀏覽器分別訪問2個單點(diǎn)客戶端。



### 三、 個人總結(jié)
??單點(diǎn)登陸本質(zhì)上就是授權(quán)碼模式,所以理解起來還是很容易的,如果非要給個流程圖,還是那張授權(quán)碼流程圖:

![/upload/otherpic69/787919.jpg](/upload/otherpic69/787919.jpg)

?? 本文介紹 基于JWT的單點(diǎn)登陸(SSO)開發(fā)及原理解析 開發(fā)的代碼可以訪問代碼倉庫 ,項目的github 地址 : https://github.com/BUG9/spring-security 

?? ?? ?? **如果您對這些感興趣,歡迎star、follow、收藏、轉(zhuǎn)發(fā)給予支持!**

“JWT的單點(diǎn)登陸SSO開發(fā)及原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

標(biāo)題名稱:JWT的單點(diǎn)登陸SSO開發(fā)及原理是什么
標(biāo)題網(wǎng)址:http://muchs.cn/article46/pdpseg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、定制網(wǎng)站網(wǎng)站改版、電子商務(wù)手機(jī)網(wǎng)站建設(shè)、品牌網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)