參考
JSON Web Tokens官網 Libraries里有各種語言的推薦包
jjwt的Github網址 JWT官網里面star最多的,所以用了
jjwt官方 生成和解析的例子
前后端分離之JWT用戶認證 對JWT有詳細的介紹
Java安全驗證之JWT實踐
依賴
流程
登錄成功后,在Java中生成Jwt,存入數據庫,然后返回給前端;前端接收到Jwt,儲存起來(cookie或localStorage)。
前端調用api時放在Header的Authorization里面,后端通過過濾器Filter判斷是否已登錄。
沒有使用框架,單純的Html、servlet、數據庫
生成和解析Jwt
其實就是官方的 然后用谷歌翻譯了一波
生成jwt
//構建JWT的示例方法 private String createJWT(String id, String issuer, String subject, long ttlMillis) { //我們將用於簽署令牌的JWT簽名算法 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //創建時間 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //我們將使用我們的Api Key秘密簽署您的JWT byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret()); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //讓我們設置JWT Claims JwtBuilder builder = Jwts.builder().setId(id) .setIssuedAt(now) .setSubject(subject) .setIssuer(issuer) .signWith(signatureAlgorithm, signingKey); //builder.claim("name", "value"); //設置自定義的信息 //如果已經指定,讓我們添加到期日 //過期時間 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } //構建JWT並將其序列化為緊湊的URL安全字符串 return builder.compact(); }
解析Jwt
//驗證和讀取JWT的示例方法 private void parseJWT(String jwt) { //如果它不是簽名的JWS(如預期的那樣),則該行將拋出異常 Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret())) .parseClaimsJws(jwt).getBody(); System.out.println("ID: " + claims.getId()); System.out.println("Subject: " + claims.getSubject()); System.out.println("Issuer: " + claims.getIssuer()); System.out.println("Expiration: " + claims.getExpiration()); //claims.get("name") //獲取自定義的信息 }
實例
后端
JwtUtil 工具類
package com.util; import java.security.Key; import java.util.Date; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; public class JwtUtil { // token秘鑰 太短會報錯 public static String SECRET = "qwerasdfdxzvdfajjlkjeiojznvxndjkfaowijeiodl"; /** * 生成Jwt的方法 * * @param id * 用戶ID * @param subject * 用戶昵稱 * @param ttlMillis * 過期時間 * @return Token String 憑證 */ public static String createJWT(String id, String subject, long ttlMillis) { // 簽名方法 HS256 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成Jwt的時間 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); // 生成秘鑰 byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); // 設置JWT所存儲的信息 JwtBuilder builder = Jwts.builder().setId(id).setIssuedAt(now).setSubject(subject).signWith(signingKey, signatureAlgorithm); //builder.claim("name", "value"); //存儲自定義信息 // 設置過期時間 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } // 構建JWT並將其序列化為緊湊的URL安全字符串 return builder.compact(); } /** * 解析Jwt字符串 * * @param jwt * Jwt字符串 * @return Claims 解析后的對象 */ public static Claims parseJWT(String jwt) { return Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(SECRET)).parseClaimsJws(jwt).getBody(); } /** * 驗證JWT * * @param jwtStr jwt字符串 * @return JOSNObject 解析結果<br/> *   Success 成功標識<br/> *     true:成功<br/> *     false:失敗<br/> *   Claim 聲明對象<br/> *   ErrCode 錯誤碼<br/> *     1005:過期<br/> *     1004:未登錄 */ public static JSONObject validateJWT(String jwtStr) { JSONObject pojo = new JSONObject(); Claims claims = null; try { claims = parseJWT(jwtStr); pojo.put("Success", true); pojo.put("Claims", claims); } catch (ExpiredJwtException e) { pojo.put("Success", false); pojo.put("ErrCode", 1005); e.printStackTrace(); } catch (Exception e) { pojo.put("Success", false); pojo.put("ErrCode", 1004); e.printStackTrace(); } return pojo; } }
LoginServlet 登錄
package com.test; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.util.JwtUtil; @WebServlet("/user/login") public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); request.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); // 賬號 String Account = request.getParameter("Account"); // 密碼 String password = request.getParameter("password"); // 登錄操作 // 登錄成功: // 有效時間:一天 long ttlMillis = 24 * 60 * 60 * 1000; // 昵稱 String nickname = "star"; // 生成jws String jws = JwtUtil.createJWT(Account, nickname, ttlMillis); // jws存到數據庫 // 返回給前端 out.print(jws); out.flush(); out.close(); } }
JwtFilter 過濾器
package com.filter; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.entity.Result; import com.util.JwtUtil; import io.jsonwebtoken.Claims; // 登錄、注冊等操作不過濾 攔截servlet下的所有接口 @WebFilter(filterName = "JWTFilter", urlPatterns = { "/servlet/*" }) public class JWTFilter implements Filter { @Override public void destroy() { /* 銷毀時調用 */ } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { /* 過濾方法 主要是對request和response進行一些處理,然后交給下一個過濾器或Servlet處理 */ HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); // jws設置在Header的Authorization里面 String tokenStr = request.getHeader("Authorization"); // 錯誤說明 String msg = "系統異常"; // 成功標識 boolean flag = false; // 錯誤碼 int RtnValue = 9099; // 存放結果的實體類 有屬性:success、errNum、message Result lr = null; // 用戶未登錄 if (tokenStr == null || tokenStr.equals("")) { RtnValue = 1004; msg = "未登錄"; lr = new Result(RtnValue, flag, msg); out.print(JSON.toJSONString(lr)); out.flush(); out.close(); return; } // 解析jws JSONObject pojo = JwtUtil.validateJWT(tokenStr); if (pojo.getBooleanValue("Success")) { // 解析成功 Claims claims = (Claims) pojo.get("Claims"); String Account = claims.getId(); // 與數據庫里的比較 // true chain.doFilter(request, response); // false /* * RtnValue = 1004; * msg = "登錄失效"; * lr = new Result(RtnValue, flag, msg); * out.print(JSON.toJSONString(lr)); * out.flush(); * out.close(); */ } else { // 解析失敗 RtnValue = pojo.getIntValue("ErrCode"); switch (RtnValue) { case 1005: msg = "簽名過期"; break; case 1004: msg = "未登錄"; break; default: break; } lr = new Result(RtnValue, flag, msg); out.print(JSON.toJSONString(lr)); out.flush(); out.close(); } } @Override public void init(FilterConfig config) throws ServletException { } }
前端
登錄成功后,把獲取到的Token存起來
function login() { var account = $('#account').val(); var password = $('password').val(); $.ajax({ url: 'user/login', data: { 'account': account, 'password': password }, dataType: 'json', success: function (result) { if (result.success) { //登錄成功 把Token令牌存起來 window.localStorage.setItem('token', result.message); } else { alert(result.message); } } }); };
調用api
$(function () { $.ajax({ headers: { // 不設置的話,返回信息會出現中文亂碼 contentType: 'application/x-www-form-urlencoded; charset=utf-8', // 傳給后端判斷是否登錄 Authorization: localStorage.getItem('token') }, url: 'servlet/getUserinfo', data: { 'id': '1' }, dataType: 'json', success: function(result) { if (result.hasOwnProperty('success')) { // 調用接口被攔截了 登錄出錯 alert(result.message); return; } // 相對應的操作 } }); });
原文鏈接:https://blog.csdn.net/CSDN906214391/article/details/98633667