JWT


JWT

常見的認證機制

HTTP Basic Auth

 HTTP Basic Auth簡單點說明就是每次請求API時都提供用戶的username和password,簡言之,Basic Auth是配合RESTful API 使用的最簡單的認證方式,只需提供用戶名密碼即可,但由於有把用戶名密碼暴露給第三方客戶端的風險,在生產環境下被使用的越來越少。因此,在開發對外開放的RESTful API時,盡量避免采用HTTP Basic Auth。

 Cookie認證機制就是為一次請求認證在服務端創建一個Session對象,同時在客戶端的瀏覽器端創建了一個Cookie對象;通過客戶端帶上來Cookie對象來與服務器端的session對象匹配來實現狀態管理的。默認的,當我們關閉瀏覽器的時候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時間內有效。

OAuth

 OAuth(開放授權,Open Authorization)是一個開放的授權標准,允許用戶讓第三方應用訪問該用戶在某一web服務上存儲的私密的資源(如照片,視頻,聯系人列表),而無需將用戶名和密碼提供給第三方應用。如網站通過微信、微博登錄等,主要用於第三方登錄。

 OAuth允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每一個令牌授權一個特定的第三方系統(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用戶可以授權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非所 有內容。

下面是OAuth2.0的流程:

 這種基於OAuth的認證機制適用於個人消費者類的互聯網產品,如社交類APP等應用,但是不太適合擁有自有認證權限管理的企業應用。

缺點:過重。

Token Auth

使用基於 Token 的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。大概的流程是這樣的:

  1. 客戶端使用用戶名跟密碼請求登錄

  2. 服務端收到請求,去驗證用戶名與密碼

  3. 驗證成功后,服務端會簽發一個 Token,再把這個 Token 發送給客戶端

  4. 客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里

  5. 客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 Token

  6. 服務端收到請求,然后去驗證客戶端請求里面帶着的 Token,如果驗證成功,就向客戶端返回請求的數據

比第一種方式更安全,比第二種方式更節約服務器資源,比第三種方式更加輕量。

具體,Token Auth的優點(Token機制相對於Cookie機制又有什么好處呢?):

  1. 支持跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通過HTTP頭傳輸.

  2. 無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因為Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.

  3. 更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可.

  4. 去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可.

  5. 更適用於移動應用: 當你的客戶端是一個原生平台(iOS, Android,Windows 10等)時,Cookie是不被支持的(你需要通過Cookie容器進行處理),這時采用Token認證機制就會簡單得多。

  6. CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防范。

  7. 性能: 一次網絡往返時間(通過數據庫查詢session信息)總比做一次HMACSHA256計算的Token驗證和解析要費時得多.

  8. 不需要為登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登錄頁面做特殊處理.

  9. 基於標准化:你的API可以采用標准化的 JSON Web Token (JWT). 這個標准已經存在多個后端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

JWT簡介

什么是JWT

 JSON Web Token(JWT)是一個開放的行業標准(RFC 7519),它定義了一種簡介的、自包含的協議格式,用於在通信雙方傳遞json對象,傳遞的信息經過數字簽名可以被驗證和信任。JWT可以使用HMAC算法或使用RSA的公鑰/私鑰對來簽名,防止被篡改。

官網: https://jwt.io/

標准: https://tools.ietf.org/html/rfc7519

JWT令牌的優點:

  1. jwt基於json,非常方便解析。

  2. 可以在令牌中自定義豐富的內容,易擴展。

  3. 通過非對稱加密算法及數字簽名技術,JWT防止篡改,安全性高。

  4. 資源服務使用JWT可不依賴認證服務即可完成授權。

缺點:

  1. JWT令牌較長,占存儲空間比較大。

JWT組成

一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。

頭部(Header)

頭部用於描述關於該JWT的最基本的信息,例如其類型(即JWT)以及簽名所用的算法(如HMAC SHA256或RSA)等。這也可以被表示成一個JSON對象。

{
  "alg": "HS256",
  "typ": "JWT"
}
  • typ:是類型。

  • alg:簽名的算法,這里使用的算法是HS256算法

我們對頭部的json字符串進行BASE64編碼(網上有很多在線編碼的網站),編碼后的字符串如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9  

 Base64是一種基於64個可打印字符來表示二進制數據的表示方法。由於2的6次方等於64,所以每6個比特為一個單元,對應某個可打印字符。三個字節有24個比特,對應於4個Base64單元,即3個字節需要用4個可打印字符來表示。JDK 中提供了非常方便的 BASE64Encoder BASE64Decoder,用它們可以非常方便的完成基於 BASE64 的編碼和解碼。

負載(Payload)

第二部分是負載,就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分:

  • 標准中注冊的聲明(建議但不強制使用)
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什么時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
  • 公共的聲明

 公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.

  • 私有的聲明

 私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味着該部分信息可以歸類為明文信息。

 這個指的就是自定義的claim。比如下面那個舉例中的name都屬於自定的claim。這些claim跟JWT標准規定的claim區別在於:JWT規定的claim,JWT的接收方在拿到JWT之后,都知道怎么對這些標准的claim進行驗證(還不知道是否能夠驗證);而private claims不會驗證,除非明確告訴接收方要對這些claim進行驗證以及規則才行。

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

其中sub是標准的聲明,name是自定義的聲明(公共的或私有的)

然后將其進行base64編碼,得到Jwt的第二部分:

 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbWVzIiwiYWRtaW4iOnRydWV9  

提示:聲明中不要放一些敏感信息。

簽證、簽名(signature)

jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:

  1. header (base64后的)

  2. payload (base64后的)

  3. secret(鹽,一定要保密)

 這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成了jwt的第三部分:

8HI-Lod0ncfVDnbKIPJJqLH998duF9DSDGkx3gRPNVI

將這三部分用.連接成一個完整的字符串,構成了最終的jwt:

eyJhbGciOiJIUzI1NiIsInR9cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.8HI-Lod0ncfVDnbKIPJJqLH998duF9DSDGkx3gRPNVI  

注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是可以自我簽發jwt了。

JJWT簡介

什么是JJWT

 JJWT是一個提供端到端的JWT創建和驗證的Java庫。永遠免費和開源(Apache License,版本2.0),JJW很容易使用和理解。它被設計成一個以建築為中心的流暢界面,隱藏了它的大部分復雜性。

規范官網:https://jwt.io/

快速入門

token的創建

創建SpringBoot工程,引入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.2.2.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.yjxxt</groupId>
   <artifactId>jwtdemo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>jwtdemo</name>
   <description>Demo project for Spring Boot</description>

   <properties>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!--JWT依賴-->
      <dependency>
         <groupId>io.jsonwebtoken</groupId>
         <artifactId>jjwt</artifactId>
         <version>0.9.0</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
         <exclusions>
            <exclusion>
               <groupId>org.junit.vintage</groupId>
               <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
         </exclusions>
      </dependency>
   </dependencies>
</project>

創建測試類JwtTest,用於生成token:

package com.yjxxt.jwtdemo;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.Base64Codec;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;

@SpringBootTest
public class JwtdemoApplicationTests {

   /**
    * 創建token
    */
   @Test
   public void testCreatToken() {
      //創建一個JwtBuilder對象
      JwtBuilder jwtBuilder = Jwts.builder()
            //聲明的標識{"jti":"888"}
            .setId("888")
            //主體,用戶{"sub":"Rose"}
            .setSubject("Rose")
            //創建日期{"ita":"yjxxtxx"}
            .setIssuedAt(new Date())
            //簽名手段,參數1:算法,參數2:鹽
            .signWith(SignatureAlgorithm.HS256,"yjxxt");
      //獲取jwt的token
      String token = jwtBuilder.compact();
      System.out.println(token);

      //三部分的base64解密
      System.out.println("--------------------");
      String[] split = token.split("\\.");
      System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
      System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
      //無法解密
      System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
   }
}

運行結果如下

再次運行,會發現每次運行的結果是不一樣的,因為我們的載荷中包含了時間

token的驗證解析

 我們剛才已經創建了token ,在web應用中這個操作是由服務端進行然后發給客戶端,客戶端在下次向服務端發送請求時需要攜帶這個token(這就好像是拿着一張門票一樣),那服務端接到這個token 應該解析出token中的信息(例如用戶id),根據這些信息查詢數據庫返回相應的結果。

@Test
public void testParseToken(){
   //token
   String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNTc4ODE0MjUyfQ" +
         ".-FYFMHyfTcGzq900f_Drfdsges0ge2UjaWvPW9gCDto";
   //解析token獲取負載中的聲明對象
   Claims claims = Jwts.parser()
         .setSigningKey("yjxxt")
         .parseClaimsJws(token)
         .getBody();
   //打印聲明的屬性
   System.out.println("id:"+claims.getId());
   System.out.println("subject:"+claims.getSubject());
   System.out.println("issuedAt:"+claims.getIssuedAt());
}

試着將token或簽名秘鑰篡改一下,會發現運行時就會報錯,所以解析token也就是驗證token

token過期校驗

 有很多時候,我們並不希望簽發的token是永久生效的(上節的token是永久的),所以我們可以為token添加一個過期時間。原因:從服務器發出的token,服務器自己並不做記錄,就存在一個弊端就是,服務端無法主動控制某token的立刻失效。

測試用例:

@Test
public void testCreatTokenHasExp() {
   //當前系統時間的長整型
   long now = System.currentTimeMillis();
   //過期時間,這里是1分鍾后的時間長整型
   long exp = now + 60 * 1000;
   //創建一個JwtBuilder對象
   JwtBuilder jwtBuilder = Jwts.builder()
         //聲明的標識{"jti":"888"}
         .setId("888")
         //主體,用戶{"sub":"Rose"}
         .setSubject("Rose")
         //創建日期{"ita":"yjxxtxx"}
         .setIssuedAt(new Date())
         //簽名手段,參數1:算法,參數2:鹽
         .signWith(SignatureAlgorithm.HS256, "yjxxt")
         //設置過期時間
         .setExpiration(new Date(exp));
   //獲取jwt的token
   String token = jwtBuilder.compact();
   System.out.println(token);
}


@Test
public void testParseTokenHasExp() {
   //token
   String token = "eyJhbGciOiJIUzI1NiJ9" +
         ".eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNTc4ODE1MDYyLCJleHAiOjE1Nzg4MTUxMjIsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoic2hzeHQuanBnIn0.hKog0RsZ9_6II_R8kUCp0HLAouUAYXAJVbz3xtLTUh4";
   //解析token獲取負載中的聲明對象
   Claims claims = Jwts.parser()
         .setSigningKey("yjxxt")
         .parseClaimsJws(token)
         .getBody();
   //打印聲明的屬性
   System.out.println("id:" + claims.getId());
   System.out.println("subject:" + claims.getSubject());
   System.out.println("issuedAt:" + claims.getIssuedAt());
   DateFormat sf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   System.out.println("簽發時間:"+sf.format(claims.getIssuedAt()));
   System.out.println("過期時間:"+sf.format(claims.getExpiration()));
   System.out.println("當前時間:"+sf.format(new Date()));
}

測試:當未過期時可以正常讀取,當過期時會引發io.jsonwebtoken.ExpiredJwtException異常。

自定義claims

我們剛才的例子只是存儲了id和subject兩個信息,如果你想存儲更多的信息(例如角色)可以定義自定義claims

測試用例:

@Test
public void testCreatTokenByClaims() {
   //當前系統時間的長整型
   long now = System.currentTimeMillis();
   //過期時間,這里是1分鍾后的時間長整型
   long exp = now + 60 * 1000;
   //創建一個JwtBuilder對象
   JwtBuilder jwtBuilder = Jwts.builder()
         //聲明的標識{"jti":"888"}
         .setId("888")
         //主體,用戶{"sub":"Rose"}
         .setSubject("Rose")
         //創建日期{"ita":"yjxxtxx"}
         .setIssuedAt(new Date())
         //簽名手段,參數1:算法,參數2:鹽
         .signWith(SignatureAlgorithm.HS256, "yjxxt")
         //設置過期時間
         .setExpiration(new Date(exp))
         //直接傳入map
         // .addClaims(map)
         .claim("roles","admin")
         .claim("logo","yjxxt.jpg");
   //獲取jwt的token
   String token = jwtBuilder.compact();
   System.out.println(token);
}


@Test
public void testParseTokenByClaims() {
   //token
   String token = "eyJhbGciOiJIUzI1NiJ9" +
         ".eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNTc4ODE1MDYyLCJleHAiOjE1Nzg4MTUxMjIsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoic2hzeHQuanBnIn0.hKog0RsZ9_6II_R8kUCp0HLAouUAYXAJVbz3xtLTUh4";
   //解析token獲取負載中的聲明對象
   Claims claims = Jwts.parser()
         .setSigningKey("yjxxt")
         .parseClaimsJws(token)
         .getBody();
   //打印聲明的屬性
   System.out.println("id:" + claims.getId());
   System.out.println("subject:" + claims.getSubject());
   System.out.println("issuedAt:" + claims.getIssuedAt());
   DateFormat sf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   System.out.println("簽發時間:"+sf.format(claims.getIssuedAt()));
   System.out.println("過期時間:"+sf.format(claims.getExpiration()));
   System.out.println("當前時間:"+sf.format(new Date()));

   System.out.println("roles:"+claims.get("roles"));
   System.out.println("logo:"+claims.get("logo"));
}

Spring Security Oauth2 整合JWT

整合JWT

我們拿之前Spring Security Oauth2的完整代碼進行修改

添加配置文件JwtTokenStoreConfig.java

package com.yjxxt.springsecurityoauth2demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * 使用Jwt存儲token的配置
 * @author ylc
 * @since 1.0.0
 */
@Configuration
public class JwtTokenStoreConfig {

   @Bean
   public TokenStore jwtTokenStore(){
      return new JwtTokenStore(jwtAccessTokenConverter());
   }

   @Bean
   public JwtAccessTokenConverter jwtAccessTokenConverter(){
      JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
      //配置JWT使用的秘鑰
      accessTokenConverter.setSigningKey("test_key");
      return accessTokenConverter;
   }
}

在認證服務器配置中指定令牌的存儲策略為JWT

package com.yjxxt.springsecurityoauth2demo.config;

import com.yjxxt.springsecurityoauth2demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

/**
 * 授權服務器配置
 * @author ylc
 * @since 1.0.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;
    
    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;


    /**
     * 使用密碼模式需要配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                //配置存儲令牌策略
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置client_id
                .withClient("admin")
                //配置client-secret
                .secret(passwordEncoder.encode("112233"))
                //配置訪問token的有效期
                .accessTokenValiditySeconds(3600)
                //配置刷新token的有效期
                .refreshTokenValiditySeconds(864000)
                //配置redirect_uri,用於授權成功后跳轉
                .redirectUris("http://www.baidu.com")
                //配置申請的權限范圍
                .scopes("all")
                //配置grant_type,表示授權類型
                .authorizedGrantTypes("authorization_code","password");
    }
}

用密碼模式測試:

發現獲取到的令牌已經變成了JWT令牌,將access_token拿到https://jwt.io/ 網站上去解析下可以獲得其中內容。

擴展JWT中存儲的內容

 有時候我們需要擴展JWT中存儲的內容,這里我們在JWT中擴展一個key為enhance,value為enhance info的數據。

繼承TokenEnhancer實現一個JWT內容增強器

package com.yjxxt.springsecurityoauth2demo.config;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import java.util.HashMap;
import java.util.Map;

/**
 * JWT內容增強器
 * @author ylc
 * @since 1.0.0
 */
public class JwtTokenEnhancer implements TokenEnhancer {

   @Override
   public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
      Map<String,Object> info = new HashMap<>();
      info.put("enhance","enhance info");
      ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
      return accessToken;
   }
}

創建一個JwtTokenEnhancer實例

package com.yjxxt.springsecurityoauth2demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * 使用Jwt存儲token的配置
 * @author ylc
 * @since 1.0.0
 */
@Configuration
public class JwtTokenStoreConfig {

   @Bean
   public TokenStore jwtTokenStore(){
      return new JwtTokenStore(jwtAccessTokenConverter());
   }

   @Bean
   public JwtAccessTokenConverter jwtAccessTokenConverter(){
      JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
      //配置JWT使用的秘鑰
      accessTokenConverter.setSigningKey("test_key");
      return accessTokenConverter;
   }

   @Bean
   public JwtTokenEnhancer jwtTokenEnhancer() {
      return new JwtTokenEnhancer();
   }
}

在認證服務器配置中配置JWT的內容增強器

package com.yjxxt.springsecurityoauth2demo.config;

import com.yjxxt.springsecurityoauth2demo.component.JwtTokenEnhancer;
import com.yjxxt.springsecurityoauth2demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * 授權服務器配置
 * @author ylc
 * @since 1.0.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    /**
     * 使用密碼模式需要配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        //配置JWT的內容增強器
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                //配置存儲令牌策略
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置client_id
                .withClient("admin")
                //配置client-secret
                .secret(passwordEncoder.encode("112233"))
                //配置訪問token的有效期
                .accessTokenValiditySeconds(3600)
                //配置刷新token的有效期
                .refreshTokenValiditySeconds(864000)
                //配置redirect_uri,用於授權成功后跳轉
                .redirectUris("http://www.baidu.com")
                //配置申請的權限范圍
                .scopes("all")
                //配置grant_type,表示授權類型
                .authorizedGrantTypes("authorization_code","password");
    }
}

運行項目后使用密碼模式來獲取令牌,之后對令牌進行解析,發現已經包含擴展的內容。

Java中解析JWT中的內容

添加依賴

<!--jwt 依賴-->
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.0</version>
</dependency>

修改UserController類,使用jjwt工具類來解析Authorization頭中存儲的JWT內容

package com.yjxxt.springsecurityoauth2demo.controller;

import io.jsonwebtoken.Jwts;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;

/**
 * @author ylc
 * @since 1.0.0
 */
@RestController
@RequestMapping("/user")
public class UserController {

   @GetMapping("/getCurrentUser")
   public Object getCurrentUser(Authentication authentication, HttpServletRequest request) {
      String header = request.getHeader("Authorization");
      String token = header.substring(header.indexOf("bearer") + 7);
      return Jwts.parser()
            .setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))
            .parseClaimsJws(token)
            .getBody();
   }
}

將令牌放入Authorization頭中,訪問如下地址獲取信息:

http://localhost:8080/user/getCurrentUser

刷新令牌

 在Spring Cloud Security 中使用oauth2時,如果令牌失效了,可以使用刷新令牌通過refresh_token的授權模式再次獲取access_token。

只需修改認證服務器的配置,添加refresh_token的授權模式即可。

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            //配置client_id
            .withClient("admin")
            //配置client-secret
            .secret(passwordEncoder.encode("112233"))
            //配置訪問token的有效期
            .accessTokenValiditySeconds(3600)
            //配置刷新token的有效期
            .refreshTokenValiditySeconds(86400)
            //配置redirect_uri,用於授權成功后跳轉
            .redirectUris("http://www.baidu.com")
            //配置申請的權限范圍
            .scopes("all")
            //配置grant_type,表示授權類型
            .authorizedGrantTypes("authorization_code","password","refresh_token");
}

使用刷新令牌模式來獲取新的令牌,訪問如下地址:

http://localhost:8080/oauth/token


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM