技術文章第一時間送達!php
JWT(Json Web Token),是一種工具,格式爲XXXX.XXXX.XXXX
的字符串,JWT以一種安全的方式在用戶和服務器之間傳遞存放在JWT中的不敏感信息。git
設想這樣一個場景,在咱們登陸一個網站以後,再把網頁或者瀏覽器關閉,下一次打開網頁的時候可能顯示的仍是登陸的狀態,不須要再次進行登陸操做,經過JWT就能夠實現這樣一個用戶認證的功能。固然使用Session能夠實現這個功能,可是使用Session的同時也會增長服務器的存儲壓力,而JWT是將存儲的壓力分佈到各個客戶端機器上,從而減輕服務器的壓力。github
JWT由3個子字符串組成,分別爲Header
,Payload
以及Signature
,結合JWT的格式即:Header.Payload.Signature
。(Claim是描述Json的信息的一個Json,將Claim轉碼以後生成Payload)。算法
Headerspring
Header是由如下這個格式的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。cookie
總結
服務器在生成一個JWT以後會將這個JWT會以Authorization : Bearer JWT 鍵值對的形式存放在cookies裏面發送到客戶端機器,在客戶端再次訪問收到JWT保護的資源URL連接的時候,服務器會獲取到cookies中存放的JWT信息,首先將Header進行反編碼獲取到加密的算法,在經過存放在服務器上的密匙對Header.Payload 這個字符串進行加密,比對JWT中的Signature和實際加密出來的結果是否一致,若是一致那麼說明該JWT是合法有效的,認證成功,不然認證失敗。
這裏的代碼實現使用的是Spring Boot(版本號:1.5.10)框架,以及Apache Ignite(版本號:2.3.0)數據庫。
代碼中與JWT有關的內容以下:
config包中JwtCfg類配置生成一個JWT並配置了JWT攔截的URL
controller包中PersonController 用於處理用戶的登陸註冊時生成JWT,SecureController 用於測試JWT
model包中JwtFilter 用於處理與驗證JWT的正確性
其他屬於Ignite數據庫訪問的相關內容
JwtCfg 類
這個類中聲明瞭一個@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/SpringBoot-Ignite
http://blog.leapoahead.com/2015/09/06/understanding-jwt/ ↩
https://aboullaite.me/spring-boot-token-authentication-using-jwt/
看完本文有收穫?請轉發分享給更多人