作者:LTLAYX https:// dwz.cn/yv1Do6e3推薦閱讀 1.
Java 性能優化:教你提高代碼運作的效率
2. 基于token的多平台身份認證架構設計 3.select count(*)底層究竟做了什麼?
4. Springboot啟動原了解析初探JWT
什麼是JWT
JWT(Json Web Token),是一種工具,格式為
XXXX.XXXX.XXXX
的字元串,JWT以一種安全的方式在使用者和伺服器之間傳遞存放在JWT中的不敏感資訊。
為什麼要用JWT
設想這樣一個場景,在我們登入一個網站之後,再把網頁或者浏覽器關閉,下一次打開網頁的時候可能顯示的還是登入的狀态,不需要再次進行登入操作,通過JWT就可以實作這樣一個使用者認證的功能。當然使用Session可以實作這個功能,但是使用Session的同時也會增加伺服器的存儲壓力,而JWT是将存儲的壓力分布到各個用戶端機器上,進而減輕伺服器的壓力。
JWT長什麼樣
JWT由3個子字元串組成,分别為
Header
,
Payload
以及
Signature
,結合JWT的格式即:
Header.Payload.Signature
。(Claim是描述Json的資訊的一個Json,将Claim轉碼之後生成Payload)。
HeaderHeader是由以下這個格式的Json通過Base64編碼(編碼不是加密,是可以通過反編碼的方式擷取到這個原來的Json,是以JWT中存放的一般是不敏感的資訊)生成的字元串,Header中存放的内容是說明編碼對象是一個JWT以及使用“SHA-256”的算法進行加密(加密用于生成Signature)
{
"typ":"JWT",
"alg":"HS256"
}
Claim Claim是一個Json,Claim中存放的内容是JWT自身的标準屬性,所有的标準屬性都是可選的,可以自行添加,比如:JWT的簽發者、JWT的接收者、JWT的持續時間等;同時Claim中也可以存放一些自定義的屬性,這個自定義的屬性就是在使用者認證中用于标明使用者身份的一個屬性,比如使用者存放在資料庫中的id,為了安全起見,一般不會将使用者名及密碼這類敏感的資訊存放在Claim中。将Claim通過Base64轉碼之後生成的一串字元串稱作Payload。
{
"iss":"Issuer —— 用于說明該JWT是由誰簽發的",
"sub":"Subject —— 用于說明該JWT面向的對象",
"aud":"Audience —— 用于說明該JWT發送給的使用者",
"exp":"Expiration Time —— 數字類型,說明該JWT過期的時間",
"nbf":"Not Before —— 數字類型,說明在該時間之前JWT不能被接受與處理",
"iat":"Issued At —— 數字類型,說明該JWT何時被簽發",
"jti":"JWT ID —— 說明标明JWT的唯一ID",
"user-definde1":"自定義屬性舉例",
"user-definde2":"自定義屬性舉例"
}
Signature Signature是由Header和Payload組合而成,将Header和Claim這兩個Json分别使用Base64方式進行編碼,生成字元串Header和Payload,然後将Header和Payload以Header.Payload的格式組合在一起形成一個字元串,然後使用上面定義好的加密算法和一個密匙(這個密匙存放在伺服器上,用于進行驗證)對這個字元串進行加密,形成一個新的字元串,這個字元串就是Signature。
總結JWT實作認證的原理
伺服器在生成一個JWT之後會将這個JWT會以Authorization : Bearer JWT 鍵值對的形式存放在cookies裡面發送到用戶端機器,在用戶端再次通路收到JWT保護的資源URL連結的時候,伺服器會擷取到cookies中存放的JWT資訊,首先将Header進行反編碼擷取到加密的算法,在通過存放在伺服器上的密匙對Header.Payload 這個字元串進行加密,比對JWT中的Signature和實際加密出來的結果是否一緻,如果一緻那麼說明該JWT是合法有效的,認證成功,否則認證失敗。
JWT實作使用者認證的流程圖
JWT的代碼實作
這裡的代碼實作使用的是Spring Boot(版本号:1.5.10)架構,以及Apache Ignite(版本号:2.3.0)資料庫。有關Ignite和Spring Boot的整合可以檢視這裡。
http:// blog.csdn.net/ltl112358 /article/details/79399026
代碼說明:
代碼中與JWT有關的内容如下:
- config包中JwtCfg類配置生成一個JWT并配置了JWT攔截的URL
- controller包中PersonController 用于處理使用者的登入注冊時生成JWT,SecureController 用于測試JWT
- model包中JwtFilter 用于處理與驗證JWT的正确性
- 其餘屬于Ignite資料庫通路的相關内容
這個類中聲明了一個@Bean ,用于生成一個過濾器類,對/secure 連結下的所有資源通路進行JWT的驗證
/**
* This is Jwt configuration which set the url "/secure/*" for filtering
* @program: users
* @create: 2018-03-03 21:18
**/
@Configuration
public class JwtCfg {
@Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/secure/*");
return registrationBean;
}
}
JwtFilter 類 這個類聲明了一個JWT過濾器類,從Http請求中提取JWT的資訊,并使用了”secretkey”這個密匙對JWT進行驗證
/**
* Check the jwt token from front end if is invalid
* @program: users
* @create: 2018-03-01 11:03
**/
public class JwtFilter extends GenericFilterBean {
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
throws IOException, ServletException {
// Change the req and res to HttpServletRequest and HttpServletResponse
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
// Get authorization from Http request
final String authHeader = request.getHeader("authorization");
// If the Http request is OPTIONS then just return the status code 200
// which is HttpServletResponse.SC_OK in this code
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(req, res);
}
// Except OPTIONS, other request should be checked by JWT
else {
// Check the authorization, check if the token is started by "Bearer "
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header");
}
// Then get the JWT token from authorization
final String token = authHeader.substring(7);
try {
// Use JWT parser to check if the signature is valid with the Key "secretkey"
final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
// Add the claim to request header
request.setAttribute("claims", claims);
} catch (final SignatureException e) {
throw new ServletException("Invalid token");
}
chain.doFilter(req, res);
}
}
}
PersonController 類 這個類中在使用者進行登入操作成功之後,将生成一個JWT作為傳回
/**
* @program: users
* @create: 2018-02-27 19:28
**/
@RestController
public class PersonController {
@Autowired
private PersonService personService;
/**
* User register with whose username and password
* @param reqPerson
* @return Success message
* @throws ServletException
*/
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(@RequestBody() ReqPerson reqPerson) throws ServletException {
// Check if username and password is null
if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null
|| reqPerson.getPassword() == "" || reqPerson.getPassword() == null)
throw new ServletException("Username or Password invalid!");
// Check if the username is used
if(personService.findPersonByUsername(reqPerson.getUsername()) != null)
throw new ServletException("Username is used!");
// Give a default role : MEMBER
List<Role> roles = new ArrayList<Role>();
roles.add(Role.MEMBER);
// Create a person in ignite
personService.save(new Person(reqPerson.getUsername(), reqPerson.getPassword(), roles));
return "Register Success!";
}
/**
* Check user`s login info, then create a jwt token returned to front end
* @param reqPerson
* @return jwt token
* @throws ServletException
*/
@PostMapping
public String login(@RequestBody() ReqPerson reqPerson) throws ServletException {
// Check if username and password is null
if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null
|| reqPerson.getPassword() == "" || reqPerson.getPassword() == null)
throw new ServletException("Please fill in username and password");
// Check if the username is used
if(personService.findPersonByUsername(reqPerson.getUsername()) == null
|| !reqPerson.getPassword().equals(personService.findPersonByUsername(reqPerson.getUsername()).getPassword())){
throw new ServletException("Please fill in username and password");
}
// Create Twt token
String jwtToken = Jwts.builder().setSubject(reqPerson.getUsername()).claim("roles", "member").setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secretkey").compact();
return jwtToken;
}
}
SecureController 類 這個類中隻是用于測試JWT功能,當使用者認證成功之後,/secure 下的資源才可以被通路
/**
* Test the jwt, if the token is valid then return "Login Successful"
* If is not valid, the request will be intercepted by JwtFilter
* @program: users
* @create: 2018-03-01 11:05
**/
@RestController
@RequestMapping("/secure")
public class SecureController {
@RequestMapping("/users/user")
public String loginSuccess() {
return "Login Successful!";
}
}
代碼功能測試
本例使用Postman對代碼進行測試,這裡并沒有考慮到安全性傳遞的明文密碼,實際上應該用SSL進行加密
1.首先進行一個新的測試使用者的注冊,可以看到注冊成功的提示傳回
2.再讓該使用者進行登入,可以看到登入成功之後傳回的JWT字元串
3.直接申請通路/secure/users/user ,這時候肯定是無法通路的,伺服器傳回500錯誤
4.将擷取到的JWT作為Authorization屬性送出,申請通路/secure/users/user ,可以通路成功
示例代碼
https:// github.com/ltlayx/Sprin gBoot-Ignite
參考
http:// blog.leapoahead.com/201 5/09/06/understanding-jwt/ ↩ https:// aboullaite.me/spring-bo ot-token-authentication-using-jwt/