目錄:
1.什么是Jwt
2.用Jwt實現登錄驗證
1.什么是Jwt
son web token (JWT), 是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標准((RFC 7519).該token被設計為緊湊且安全的,特別適用於分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
在用戶注冊或登錄后,我們想記錄用戶的登錄狀態,或者為用戶創建身份認證的憑證。我們不再使用Session認證機制,而使用Json Web Token認證機制。
JWT的構成
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).
3.Jwt實現登錄驗證
大概思路:利用Jwt在后端服務器生成jwt令牌,然后傳給前端服務器,然后在登錄時保存jwt令牌在request Header,每次請求其他路由時都會進行校驗,看是否保存,沒有保存就無法訪問該路由,由此實現身份驗證。
如圖所示:
1、首先定義驗證的過濾器CorsFilter和JwtFilter,還有我們所需要用到的工具類JwtUtils,注意的是在web中配置過濾器的順序一定要注意,JWT驗證過濾器,配置順序 :CorsFilte–>JwtFilter–>struts2中央控制器
CorsFilter
package com.hmc.vue.util;
import java.io.IOException;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 配置tomcat允許跨域訪問
*
* @author Administrator
*
*/
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
// @Override
// public void doFilter(ServletRequest servletRequest, ServletResponse
// servletResponse, FilterChain filterChain)
// throws IOException, ServletException {
// HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
//
// // Access-Control-Allow-Origin就是我們需要設置的域名
// // Access-Control-Allow-Headers跨域允許包含的頭。
// // Access-Control-Allow-Methods是允許的請求方式
// httpResponse.addHeader("Access-Control-Allow-Origin", "*");// *,任何域名
// httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT,
// DELETE");
// // httpResponse.setHeader("Access-Control-Allow-Headers", "Origin,
// // X-Requested-With, Content-Type, Accept");
//
// // 允許請求頭Token
// httpResponse.setHeader("Access-Control-Allow-Headers",
// "Origin,X-Requested-With, Content-Type, Accept, Token");
// HttpServletRequest req = (HttpServletRequest) servletRequest;
// System.out.println("Token=" + req.getHeader("Token"));
// if("OPTIONS".equals(req.getMethod())) {
// return;
// }
//
//
// filterChain.doFilter(servletRequest, servletResponse);
// }
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) servletResponse;
HttpServletRequest req = (HttpServletRequest) servletRequest;
// Access-Control-Allow-Origin就是我們需要設置的域名
// Access-Control-Allow-Headers跨域允許包含的頭。
// Access-Control-Allow-Methods是允許的請求方式
resp.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
// resp.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,
// Content-Type, Accept");
// 允許客戶端,發一個新的請求頭jwt
resp.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With, Content-Type, Accept, jwt");
// 允許客戶端,處理一個新的響應頭jwt
resp.setHeader("Access-Control-Expose-Headers", "jwt");
// String sss = resp.getHeader("Access-Control-Expose-Headers");
// System.out.println("sss=" + sss);
// 允許請求頭Token
// httpResponse.setHeader("Access-Control-Allow-Headers","Origin,X-Requested-With,
// Content-Type, Accept, Token");
// System.out.println("Token=" + req.getHeader("Token"));
if ("OPTIONS".equals(req.getMethod())) {// axios的ajax會發兩次請求,第一次提交方式為:option,直接返回即可
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
JwtFilter,要開啟jwt驗證就需要改變配置屬性 private boolean OFF = false;
JwtFilter
package com.hmc.vue.util; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; 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.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.jsonwebtoken.Claims; /** * * JWT驗證過濾器,配置順序 :CorsFilte-->JwtFilter-->struts2中央控制器 * * @author Administrator * */ public class JwtFilter implements Filter { // 排除的URL,一般為登陸的URL(請改成自己登陸的URL) private static String EXCLUDE = "^/vue/userAction_login\\.action?.*$"; private static Pattern PATTERN = Pattern.compile(EXCLUDE); private boolean OFF = false;// true關閉jwt令牌驗證功能 @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String path = req.getServletPath(); if (OFF || isExcludeUrl(path)) {// 登陸直接放行 chain.doFilter(request, response); return; } // 從客戶端請求頭中獲得令牌並驗證 String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY); Claims claims = this.validateJwtToken(jwt); if (null == claims) { // resp.setCharacterEncoding("UTF-8"); resp.sendError(403, "JWT令牌已過期或已失效"); return; } else { String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL); resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt); chain.doFilter(request, response); } } /** * 驗證jwt令牌,驗證通過返回聲明(包括公有和私有),返回null則表示驗證失敗 */ private Claims validateJwtToken(String jwt) { Claims claims = null; try { if (null != jwt) { claims = JwtUtils.parseJwt(jwt); } } catch (Exception e) { e.printStackTrace(); } return claims; } /** * 是否為排除的URL * * @param path * @return */ private boolean isExcludeUrl(String path) { Matcher matcher = PATTERN.matcher(path); return matcher.matches(); } // public static void main(String[] args) { // String path = "/sys/userAction_doLogin.action?username=zs&password=123"; // Matcher matcher = PATTERN.matcher(path); // boolean b = matcher.matches(); // System.out.println(b); // } }
JwtUitls
package com.hmc.vue.util; import java.util.Date; import java.util.Map; import java.util.UUID; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; /** * JWT驗證過濾器:配置順序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter * */ public class JwtUtils { /** * JWT_WEB_TTL:WEBAPP應用中token的有效時間,默認30分鍾 */ public static final long JWT_WEB_TTL = 30 * 60 * 1000; /** * 將jwt令牌保存到header中的key */ public static final String JWT_HEADER_KEY = "jwt"; // 指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部分內容封裝好了。 private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙 private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key static { byte[] encodedKey = Base64.decodeBase64(JWT_SECRET); JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); } private JwtUtils() { } /** * 解密jwt,獲得所有聲明(包括標准和私有聲明) * * @param jwt * @return * @throws Exception */ public static Claims parseJwt(String jwt) { Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(jwt).getBody(); return claims; } /** * 創建JWT令牌,簽發時間為當前時間 * * @param claims * 創建payload的私有聲明(根據特定的業務需要添加,如果要拿這個做驗證,一般是需要和jwt的接收方提前溝通好驗證方式的) * @param ttlMillis * JWT的有效時間(單位毫秒),當前時間+有效時間=過期時間 * @return jwt令牌 */ public static String createJwt(Map<String, Object> claims, long ttlMillis) { // 生成JWT的時間,即簽發時間 long nowMillis = System.currentTimeMillis(); // 下面就是在為payload添加各種標准聲明和私有聲明了 // 這里其實就是new一個JwtBuilder,設置jwt的body JwtBuilder builder = Jwts.builder() // 如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標准的聲明賦值之后,就是覆蓋了那些標准的聲明的 .setClaims(claims) // 設置jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置為一個不重復的值,主要用來作為一次性token,從而回避重放攻擊。 // 可以在未登陸前作為身份標識使用 .setId(UUID.randomUUID().toString().replace("-", "")) // iss(Issuser)簽發者,寫死 // .setIssuer("zking") // iat: jwt的簽發時間 .setIssuedAt(new Date(nowMillis)) // 代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可放數據{"uid":"zs"}。此處沒放 // .setSubject("{}") // 設置簽名使用的簽名算法和簽名使用的秘鑰 .signWith(SIGNATURE_ALGORITHM, JWT_KEY) // 設置JWT的過期時間 .setExpiration(new Date(nowMillis + ttlMillis)); return builder.compact(); } /** * 復制jwt,並重新設置簽發時間(為當前時間)和失效時間 * * @param jwt * 被復制的jwt令牌 * @param ttlMillis * jwt的有效時間(單位毫秒),當前時間+有效時間=過期時間 * @return */ public static String copyJwt(String jwt, Long ttlMillis) { Claims claims = parseJwt(jwt); // 生成JWT的時間,即簽發時間 long nowMillis = System.currentTimeMillis(); // 下面就是在為payload添加各種標准聲明和私有聲明了 // 這里其實就是new一個JwtBuilder,設置jwt的body JwtBuilder builder = Jwts.builder() // 如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標准的聲明賦值之后,就是覆蓋了那些標准的聲明的 .setClaims(claims) // 設置jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置為一個不重復的值,主要用來作為一次性token,從而回避重放攻擊。 // 可以在未登陸前作為身份標識使用 //.setId(UUID.randomUUID().toString().replace("-", "")) // iss(Issuser)簽發者,寫死 // .setIssuer("zking") // iat: jwt的簽發時間 .setIssuedAt(new Date(nowMillis)) // 代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可放數據{"uid":"zs"}。此處沒放 // .setSubject("{}") // 設置簽名使用的簽名算法和簽名使用的秘鑰 .signWith(SIGNATURE_ALGORITHM, JWT_KEY) // 設置JWT的過期時間 .setExpiration(new Date(nowMillis + ttlMillis)); return builder.compact(); } }
2、然后在后端的控制器生成jwt令牌,傳入前端
UserAction
package com.hmc.user.web; import java.util.List; import com.hmc.base.web.BaseAction; import com.hmc.node.entity.TreeNode; import com.hmc.user.biz.UserBiz; import com.hmc.user.entity.User; import com.opensymphony.xwork2.ModelDriven; public class UserAction extends BaseAction implements ModelDriven<User> { private static final long serialVersionUID = -1655051258255282376L; private User user = new User(); private UserBiz userBiz ; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String dologin() { List<User> list = userBiz.list(user); if(list.size()>0) { System.out.println("登錄成功"); } else { System.out.println("登錄失敗,用戶名或密碼錯誤"); } return null; } public String addUser() { int n = userBiz.add(user); if(n>0) { System.out.println("注冊成功"); } else { System.out.println("注冊失敗"); } return null; } public String list() { List<User> list = userBiz.list(user); for (User user : list) { System.out.println(user); } return null; } public String listNode() { List<TreeNode> listNode = userBiz.listNode(); for (TreeNode treeNode : listNode) { System.out.println(treeNode); } return null; } public UserBiz getUserBiz() { return userBiz; } public void setUserBiz(UserBiz userBiz) { this.userBiz = userBiz; } @Override public User getModel() { // TODO Auto-generated method stub return user; } }
3、然后我們的Vuex中定義變量jwt,通過mutations和gette操作返回jwt
State.js
export default {
jwt:''
}
Mutations.js
export default {
setResturantName: (state, payload) => {
state.resturantName = payload.resturantName;
},
setJwt: (state, payload) => {
state.jwt= payload.jwt;
}
}
Getters.js
export default {
getResturantName: (state) => {
return state.resturantName;
},
getJwt: (state) => {
return state.jwt;
}
}
4.然后再前端NodeJs服務器配置攔截器,配置在http目錄下
http.js
// 請求攔截器
axios.interceptors.request.use(function(config) {
var jwt = window.vm.$store.getters.getJwt;
config.headers['jwt'] = jwt;
return config;
}, function(error) {
return Promise.reject(error);
});
// 響應攔截器
axios.interceptors.response.use(function(response) {
debugger;
var jwt = response.headers['jwt'];
if (jwt) {
window.vm.$store.commit('setJwt', {
jwt: jwt
});
}
return response;
}, function(error) {
return Promise.reject(error);
});
這樣就完成登錄驗證,我們不登錄直接進入主頁面是看不到任何效果的
我們登錄后就可以看到