文章目錄
- JWT
- 1、什么是JWT
- 2、JWT能做什么
- 3、為什么是JWT
- 傳統的session認證
- 基于JWT認證
- 4、JWT 的結構
- 4.1、令牌組成
- 4.2、Header
- 4.3、Payload
- 4.4、Signature
- 4.5、Base64URL
- 5、JWT 的幾個特點
- 6、JWT 的使用方式
JWT
1、什么是JWT
官網:https://jwt.io/introduction
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
JSON Web Token (JWT) 是一個開放標準 ( RFC 7519 ),它定義了一種緊湊且自包含的方式,用于在各方之間作為 JSON 物件安全地傳輸資訊,該資訊可以被驗證和信任,因為它是經過數字簽名的,JWT 可以使用秘密(使用HMAC演算法)或使用RSA或ECDSA的公鑰/私鑰對進行簽名,
一句話:JWT用戶分布式系統的單點登錄SSO場景,主要用來做用戶身份鑒別或者資源(介面)安全性的一種技識訓者一種機制,
- 身份鑒別
- 資源介面安全性校驗,保護服務器資源不被泄漏
Token
1、什么是Token?
Token是服務器端生成的一串字串,以作客戶端進行請求的一個令牌,當第一次登錄后,服務器生成一個Token便將此Token回傳給客戶端,以后客戶端只需帶上這個Token前來請求資料即可,無需再次帶上用戶名和密碼,
服務器的介面安全性問題
1、token生成和獲取的階段:一般來說都是在登錄的時候,就生成token
2、然后在未來的每一次請求需要進行安全校驗的情況下都需要攜帶token到服務端進行對比和比較
3、傳統的做法一般可以使用Map或者session來完成, 但是這個會消耗大量服務資源,
4、 現在比較主流的最佳使用解決方案是:JWT,因為他是無狀態并且不會去消耗太多的服務器資源的一種解決方案,
2、JWT能做什么
1、授權
- 這是使用JWT的最常見方案,一旦用戶登錄,每個后續請求將包括JWT,從而允許用戶訪問令牌允許的路由,服務和資源,單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小并且可以在不同的域中輕松使用,,
2、資訊交換
- JSON Web Token是在各方之間安全地傳輸資訊的好方法,因為可以對JWT進行簽名(例如,使用公鑰/私鑰),所以您可以確保發件人是他們所說的人,此外,由于簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否遭到篡改,
3、為什么是JWT
傳統的session認證
? HTTP是一種沒有狀態的協議,也就是它并不知道是誰訪問應用,這里我們把用戶看成客戶端,客戶端使用用戶名還有密碼通過了身份驗證,不過下回這個客戶端再發送請求時候,還得再驗證一下,
? 解決的方法就是,當用戶請求登錄的時候,如果沒有問題,我們在服務端生成一條記錄,這個記錄可以說明一下登錄的用戶是誰,然后把這條記錄的ID好發送給客戶端,客戶端收到以后把這個 ID 號存盤在 Cookie 里,下次這個用戶再向服務端發送請求的時候,可以帶著這個 Cookie ,這樣服務端會驗證一個這個 Cookie 里的資訊,看啊看能不能在服務端里面找到對應的記錄,如果可以,說明用戶已近通過了身份驗證,就把用戶請求的資料回傳給客戶端,

? 上面說的就是 Session,我們需要在服務端為登錄的用戶生成 Session,這些 Session 可能會存盤在記憶體,磁盤,或者資料庫里,我們可能需要在服務端定期的去清理過期的 Session,
顯露的問題
**Session:**每個用戶經過我們的應用認證之后,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑒別,通常而言session都是保存在記憶體中,而隨著認證用戶的增多,服務端的開銷會明顯增大,
**擴展性:**用戶認證之后,服務端做認證記錄,如果認證的記錄被保存在記憶體中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分布式的應用上,相應的限制了負載均衡器的能力,這也意味著限制了應用的擴展能力,
**CSRF:**因為是基于cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊,
基于JWT認證

基于Token的身份驗證法,在服務端不需要存盤用戶的登錄記錄,流程是這樣的:
- 客戶端使用用戶名跟密碼請求登錄
- 服務端收到請求,去驗證用戶名與密碼
- 驗證成功后,服務端會簽發一個Token,返給客戶端,存放在Cookies里或者Local Storage里
- 客戶端每次想服務端請求資源的時候需要帶著Token(將token放入HTTP Header中的Authorization )
- 服務端驗證token是否正確、是否過期,如果驗證成功,就向客戶端回傳請求的資料;失敗回傳錯誤資訊,讓他重新登錄,
jwt優勢
- 簡潔(Compact):可以通過URL,POST引數或者在HTTP header發送,因為數量小,傳輸速度也很快
- 自包含(Self-contained):負載中包含了所有用戶所需要的資訊,避免了多次查詢資料庫
- 因為Token是以JSON加密的形式保存在客戶端的,所以JWT是跨語言的,原則上任何web形式都支持
- 不需要在服務端保存回話資訊,特別適用于分布式微服務
4、JWT 的結構
4.1、令牌組成
實際的 JWT 大概就像下面這樣,

它是一個很長的字串,中間用點(.)分隔成三個部分,注意,JWT 內部是沒有換行的,這里只是為了便于展示,將它寫成了幾行,
JWT 的三個部分依次如下,
- Header(頭部)
- Payload(負載)
- Signature(簽名)
寫成一行,就是下面的樣子,
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-o9fBlDce-1639747081324)(https://www.wangbase.com/blogimg/asset/201807/bg2018072303.jpg)]
下面依次介紹這三個部分,
4.2、Header
Header 部分是一個 JSON 物件,描述 JWT 的元資料,通常是下面的樣子,
{
"alg": "HS256",
"typ": "JWT"
}
上面代碼中,alg屬性表示簽名的演算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ屬性表示這個令牌(token)的型別(type),JWT 令牌統一寫為JWT,
最后,將上面的 JSON 物件使用 Base64URL 演算法(詳見后文)轉成字串,
4.3、Payload
Payload 部分也是一個 JSON 物件,用來存放實際需要傳遞的資料,JWT 規定了7個官方欄位,供選用,
- iss (issuer):簽發人
- exp (expiration time):過期時間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時間
- iat (Issued At):簽發時間
- jti (JWT ID):編號
除了官方欄位,你還可以在這個部分定義私有欄位,下面就是一個例子,
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默認是不加密的,任何人都可以讀到,所以不要把秘密資訊放在這個部分,
這個 JSON 物件也要使用 Base64URL 演算法轉成字串,
4.4、Signature
Signature 部分是對前兩部分的簽名,防止資料篡改,
首先,需要指定一個密鑰(secret),這個密鑰只有服務器才知道,不能泄露給用戶,然后,使用 Header 里面指定的簽名演算法(默認是 HMAC SHA256),按照下面的公式產生簽名,
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字串,每個部分之間用"點"(.)分隔,就可以回傳給用戶,
4.5、Base64URL
前面提到,Header 和 Payload 串型化的演算法是 Base64URL,這個演算法跟 Base64 演算法基本類似,但有一些小的不同,
JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx),Base64 有三個字符+、/和=,在 URL 里面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ ,這就是 Base64URL 演算法,
5、JWT 的幾個特點
(1)JWT 默認是不加密,但也是可以加密的,生成原始 Token 以后,可以用密鑰再加密一次,
(2)JWT 不加密的情況下,不能將秘密資料寫入 JWT,
(3)JWT 不僅可以用于認證,也可以用于交換資訊,有效使用 JWT,可以降低服務器查詢資料庫的次數,
(4)JWT 的最大缺點是,由于服務器不保存 session 狀態,因此無法在使用程序中廢止某個 token,或者更改 token 的權限,也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯,
(5)JWT 本身包含了認證資訊,一旦泄露,任何人都可以獲得該令牌的所有權限,為了減少盜用,JWT 的有效期應該設定得比較短,對于一些比較重要的權限,使用時應該再次對用戶進行認證,
(6)為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸,
6、JWT 的使用方式
1、引入依賴
dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2、生成token
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 60);
// 生成令牌
String token = JWT.create()
.withClaim("userId", 21)
.withClaim("username", "張三") // 設定自定義用戶名
.withExpiresAt(instance.getTime()) // 設定過期時間
.sing(Algorithm.HMAC256("token!15ef65efwe")); // 設定簽名 保密 復雜
System.out.println(token);
3、根據令牌和簽名決議資料
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!15ef65efwe")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("id:" + decodedJWT.getClaim("userId").asInt());
System.out.println("用戶名:" + decodedJWT.getClaim("username").asString());
System.out.println("過期時間:" + decodedJWT.getExpiresAt());
常見例外資訊
- SignatureVerificationException: 簽名不一致例外
- TokentExpiredException: 令牌過期例外
- AlgorithmMismatchException: 演算法不匹配例外
- InvalidClaimException: 失效的payload例外
封裝工具類
public class JWTUtils {
private static final String SIGN = "token!15ef65efwe";
/**
* 生成token
*
* @param map
* @return
*/
public static String getToken(Map<String, Object> map){
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); // 默認7天過期
// 生成令牌
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime()) // 設定過期時間
.sing(Algorithm.HMAC256(SIGN)); // 設定簽名
return token;
}
/**
* 驗證token
*
* @param token
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
/**
* 獲取token資訊
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token){
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
return decodedJWT;
}
}
springboot整合jwt
1、引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
<!--引入mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--引入druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
2、創建資料庫
3、添加配置
server.port=8989
spring.application.name=jwt
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.chilly.entity
mybatis.mapper-locations=classpath:com/chilly/mapper/*.xml
logging.level.com.chilly.dao=debug
4、撰寫代碼
物體類
@Data
@Accessors(chain = true)
public class User {
private String id;
private String name;
private String password;
}
dao
@Mapper
public interface UserDAO {
User login(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chilly.dao.UserDAO">
<select id="login" parameterType="User" resultType="User">
select id,name,password from user where name =#{name} and password=#{password}
</select>
</mapper>
service
public interface UserService {
/**
* 登錄介面
*
* @param user 表單中的user
* @return 資料庫中查詢到的User
*/
User login(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDAO userDAO;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User userDB = userDAO.login(user);
if (userDB != null) {
return userDB;
}
throw new RuntimeException("認證失敗");
}
}
controller
@RestController
@Slf4j
public class UserController {
@Resource
private UserService userService;
@GetMapping("/user/login")
public Map<String, Object> login(User user) {
log.info("用戶名:{}", user.getName());
log.info("password: {}", user.getPassword());
Map<String, Object> map = new HashMap<>();
try {
User userDB = userService.login(user);
Map<String, String> payload = new HashMap<>();
payload.put("id", userDB.getId());
payload.put("name", userDB.getName());
String token = JWTUtils.getToken(payload);
map.put("state", true);
map.put("msg", "登錄成功");
map.put("token", token);
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", e.getMessage());
map.put("token", "");
}
return map;
}
@PostMapping("/user/test")
public Map<String, Object> test(HttpServletRequest request) {
String token = request.getHeader("token");
DecodedJWT verify = JWTUtils.verify(token);
String id = verify.getClaim("id").asString();
String name = verify.getClaim("name").asString();
log.info("用戶id:{}", id);
log.info("用戶名: {}", name);
//TODO 業務邏輯
Map<String, Object> map = new HashMap<>();
map.put("state", true);
map.put("msg", "請求成功");
return map;
}
}
5、添加攔截器
@Slf4j
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//獲取請求頭中的令牌
String token = request.getHeader("token");
log.info("當前token為:{}", token);
Map<String, Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
return true;
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg", "簽名不一致");
} catch (TokenExpiredException e) {
e.printStackTrace();
map.put("msg", "令牌過期");
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg", "演算法不匹配");
} catch (InvalidClaimException e) {
e.printStackTrace();
map.put("msg", "失效的payload");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "token無效");
}
map.put("state", false);
//回應到前臺: 將map轉為json
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
注入到springboot
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/test")
.excludePathPatterns("/user/login")
;
}
}
測驗
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/388260.html
標籤:其他
下一篇:源火星球——青龍羊毛
