spring-boot-jwt/
│
├── src/main/java/
│ └── murraco
│ ├── configuration
│ │ └── SwaggerConfig.java
│ │
│ ├── controller
│ │ └── UserController.java
│ │
│ ├── dto
│ │ ├── UserDataDTO.java
│ │ └── UserResponseDTO.java
│ │
│ ├── exception
│ │ ├── CustomException.java
│ │ └── GlobalExceptionController.java
│ │
│ ├── model
│ │ ├── AppUserRole.java
│ │ └── AppUser.java
│ │
│ ├── repository
│ │ └── UserRepository.java
│ │
│ ├── security
│ │ ├── JwtTokenFilter.java
│ │ ├── JwtTokenFilterConfigurer.java
│ │ ├── JwtTokenProvider.java
│ │ ├── MyUserDetails.java
│ │ └── WebSecurityConfig.java
│ │
│ ├── service
│ │ └── UserService.java
│ │
│ └── JwtAuthServiceApp.java
│
├── src/main/resources/
│ └── application.yml
│
├── .gitignore
├── LICENSE
├── mvnw/mvnw.cmd
├── README.md
└── pom.xml
為了提供一些背景知識,我們有一個精彩的介紹,由jwt.io提供!我們來看一下:
JSON Web Token (JWT) 是一種開放式標準 (RFC 7519),它定義了一種緊湊且獨立的方式,用於在各方之間以 JSON 物件的形式安全地傳輸資訊。這些資訊可以被驗證和信任,因為它是經過數位簽署的。 JWT 可以使用秘密(使用 HMAC 演算法)或使用 RSA 的公鑰/私鑰對進行簽署。
讓我們進一步解釋這個定義的一些概念。
緊湊:由於 JWT 尺寸較小,因此可以透過 URL、POST 參數或在 HTTP 標頭內發送。此外,較小的尺寸意味著傳輸速度更快。
自包含:有效負載包含有關使用者的所有必需信息,避免多次查詢資料庫。
以下是 JSON Web 令牌有用的一些場景:
身份驗證:這是使用 JWT 最常見的場景。用戶登入後,每個後續請求都將包含 JWT,從而允許用戶存取該令牌允許的路由、服務和資源。單一登入是當今廣泛使用 JWT 的一項功能,因為它的開銷很小並且能夠輕鬆地跨不同網域使用。
資訊交換:JSON Web 令牌是在各方之間安全地傳輸資訊的好方法。因為 JWT 可以進行簽署(例如,使用公鑰/私鑰對),所以您可以確定發送者就是他們所說的人。此外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否未被竄改。
JSON Web Token 由點(.)分隔的三個部分組成,它們是:
因此,JWT 通常如下所示。
xxxxx
。 yyyyy
。 zzzzz
讓我們分解不同的部分。
標頭
標頭通常由兩部分組成:令牌的類型(JWT)和所使用的雜湊演算法(例如 HMAC SHA256 或 RSA)。
例如:
{
"alg" : " HS256 " ,
"typ" : " JWT "
}
然後,對該 JSON 進行 Base64Url 編碼以形成 JWT 的第一部分。
有效載荷
令牌的第二部分是有效負載,其中包含聲明。聲明是關於實體(通常是使用者)和附加元資料的聲明。索賠分為三種類型:保留索賠、公開索賠和私人索賠。
請注意,聲明名稱只有三個字元長,因為 JWT 旨在緊湊。
公共聲明:這些可以由使用 JWT 的人隨意定義。但為了避免衝突,它們應該在 IANA JSON Web 令牌註冊表中定義,或定義為包含防衝突命名空間的 URI。
私人聲明:這些是為在同意使用它們的各方之間共享資訊而創建的自訂聲明。
有效負載的範例可以是:
{
"sub" : " 1234567890 " ,
"name" : " John Doe " ,
"admin" : true
}
然後對有效負載進行 Base64Url 編碼以形成 JSON Web 令牌的第二部分。
簽名
若要建立簽章部分,您必須取得編碼的標頭、編碼的有效負載、秘密、標頭中指定的演算法,然後對其進行簽章。
例如,如果要使用HMAC SHA256演算法,則將透過以下方式建立簽章:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
簽名用於驗證 JWT 的發送者是否是其所說的發送者,並確保訊息在此過程中沒有被更改。將所有內容放在一起
輸出是三個由點分隔的 Base64 字串,可以在 HTML 和 HTTP 環境中輕鬆傳遞,同時與基於 XML 的標準(例如 SAML)相比更加緊湊。
下面顯示了一個 JWT,它具有先前的標頭和有效負載編碼,並且使用密鑰進行簽署。編碼 JWT
在驗證中,當使用者使用其憑證成功登入時,將傳回一個JSON Web Token,並且必須保存在本地(通常在本地儲存中,但也可以使用cookie),而不是傳統的在Web 中建立會話的方法。
每當使用者想要存取受保護的路由或資源時,使用者代理程式應該發送 JWT,通常使用承載模式在授權標頭中發送。標頭的內容應如下所示:
Authorization: Bearer <token>
這是一種無狀態身份驗證機制,因為使用者狀態永遠不會保存在伺服器記憶體中。伺服器的受保護路由將檢查授權標頭中是否存在有效的 JWT,如果存在,則將允許使用者存取受保護的資源。由於 JWT 是獨立的,所有必要的資訊都在那裡,減少了多次查詢資料庫的需要。
這使得您可以完全依賴無狀態的資料API,甚至可以向下游服務發出請求。哪些網域為您的 API 提供服務並不重要,因此跨來源資源共用 (CORS) 不會成為問題,因為它不使用 cookie。
下圖展示了這個過程:
基於令牌的身份驗證模式最近變得非常流行,因為與會話/cookie 相比,它們提供了重要的好處:
使用這種方法必須做出一些權衡:
JWT 身份驗證流程非常簡單
請務必注意,授權聲明將包含在存取權杖中。為什麼這很重要?好吧,假設授權聲明(例如資料庫中的使用者權限)在存取權杖的生命週期內發生了變更。在頒發新的存取權杖之前,這些變更不會生效。在大多數情況下,這不是大問題,因為訪問令牌是短暫的。否則就採用不透明的令牌模式。
讓我們看看如何使用 Java 和 Spring 實作基於 JWT 令牌的身份驗證,同時嘗試盡可能重複使用 Spring 安全預設行為。 Spring Security 框架附帶了已經處理授權機制的插件類,例如:會話 cookie、HTTP Basic 和 HTTP Digest。然而,它缺乏對 JWT 的原生支持,我們需要親自動手才能使其工作。
該演示目前使用名為test_db的 H2 資料庫,因此您可以快速且開箱即用地運行它,而無需進行太多配置。如果要連接到不同的資料庫,則必須在資源目錄內的application.yml
檔案中指定連線。請注意, hibernate.hbm2ddl.auto=create-drop
將在每次部署時刪除並建立一個乾淨的資料庫(如果您在實際專案中使用它,您可能需要更改它)。以下是專案中的範例,看看如何輕鬆交換url
和dialect
屬性上的註解以使用您自己的 MySQL 資料庫:
spring :
datasource :
url : jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
# url: jdbc:mysql://localhost:3306/user_db
username : root
password : root
tomcat :
max-wait : 20000
max-active : 50
max-idle : 20
min-idle : 15
jpa :
hibernate :
ddl-auto : create-drop
properties :
hibernate :
dialect : org.hibernate.dialect.H2Dialect
# dialect: org.hibernate.dialect.MySQL8Dialect
format_sql : true
id :
new_generator_mappings : false
JwtTokenFilter
JwtTokenFilterConfigurer
JwtTokenProvider
MyUserDetails
WebSecurityConfig
JwtToken過濾器
JwtTokenFilter
過濾器會套用於每個 API ( /**
),但登入令牌端點 ( /users/signin
) 和 singup 端點 ( /users/signup
) 除外。
此過濾器具有以下職責:
JwtTokenProvider
否則拋出身份驗證異常請確保在驗證成功後呼叫chain.doFilter(request, response)
。您希望處理請求前進到下一個過濾器,因為最後一個過濾器FilterSecurityInterceptor#doFilter負責實際呼叫控制器中處理請求的 API 資源的方法。
String token = jwtTokenProvider . resolveToken (( HttpServletRequest ) req );
if ( token != null && jwtTokenProvider . validateToken ( token )) {
Authentication auth = jwtTokenProvider . getAuthentication ( token );
SecurityContextHolder . getContext (). setAuthentication ( auth );
}
filterChain . doFilter ( req , res );
JwtTokenFilter配置器
將JwtTokenFilter
加入到 spring boot security 的DefaultSecurityFilterChain
。
JwtTokenFilter customFilter = new JwtTokenFilter ( jwtTokenProvider );
http . addFilterBefore ( customFilter , UsernamePasswordAuthenticationFilter . class );
JwtTokenProvider
JwtTokenProvider
具有以下職責:
我的用戶詳細信息
實作UserDetailsService
以定義我們自己的自訂loadUserbyUsername函數。 UserDetailsService
介面用於檢索與使用者相關的資料。它有一個名為loadUserByUsername的方法,該方法根據使用者名稱尋找使用者實體,並且可以重寫以自訂查找使用者的過程。
DaoAuthenticationProvider
使用它在身份驗證期間加載有關用戶的詳細資訊。
網路安全配置
WebSecurityConfig
類別擴充WebSecurityConfigurerAdapter
以提供自訂安全性設定。
在此類中配置並實例化了以下 bean:
JwtTokenFilter
PasswordEncoder
此外,在WebSecurityConfig#configure(HttpSecurity http)
方法中,我們將設定模式來定義受保護/不受保護的 API 端點。請注意,我們已停用 CSRF 保護,因為我們不使用 Cookie。
// Disable CSRF (cross site request forgery)
http . csrf (). disable ();
// No session will be created or used by spring security
http . sessionManagement (). sessionCreationPolicy ( SessionCreationPolicy . STATELESS );
// Entry points
http . authorizeRequests () //
. antMatchers ( "/users/signin" ). permitAll () //
. antMatchers ( "/users/signup" ). permitAll () //
// Disallow everything else..
. anyRequest (). authenticated ();
// If a user try to access a resource without having enough permissions
http . exceptionHandling (). accessDeniedPage ( "/login" );
// Apply JWT
http . apply ( new JwtTokenFilterConfigurer ( jwtTokenProvider ));
// Optional, if you want to test the API from a browser
// http.httpBasic();
確保您已安裝 Java 8 和 Maven
分叉此存儲庫並克隆它
$ git clone https://github.com/<your-user>/spring-boot-jwt
$ cd spring-boot-jwt
$ mvn install
$ mvn spring-boot:run
http://localhost:8080/swagger-ui.html
以檢查一切是否正常運作。您可以更改application.yml
檔案中的預設端口 server :
port : 8080
/users/me
發出 GET 請求以檢查您是否未通過身份驗證。由於您尚未設定有效的 JWT 令牌,您應該會收到403
回應,其中包含Access Denied
訊息 $ curl -X GET http://localhost:8080/users/me
/users/signin
發出 POST 請求,以取得有效的 JWT 令牌 $ curl -X POST 'http://localhost:8080/users/signin?username=admin&password=admin'
/users/me
發出初始 GET 請求 $ curl -X GET http://localhost:8080/users/me -H 'Authorization: Bearer <JWT_TOKEN>'
{
"id" : 1 ,
"username" : "admin" ,
"email" : "[email protected]" ,
"roles" : [
"ROLE_ADMIN"
]
}