目錄:前端
1.什麼是Jwt
2.用Jwt實現登陸驗證vue
1.什麼是Jwt
son web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。java
第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature).node
大概思路:利用Jwt在後端服務器生成jwt令牌,而後傳給前端服務器,而後在登陸時保存jwt令牌在request Header,每次請求其餘路由時都會進行校驗,看是否保存,沒有保存就沒法訪問該路由,由此實現身份驗證。
如圖所示:ios
一、首先定義驗證的過濾器CorsFilter和JwtFilter,還有咱們所須要用到的工具類JwtUtils,注意的是在web中配置過濾器的順序必定要注意,JWT驗證過濾器,配置順序 :CorsFilte–>JwtFilter–>struts2中央控制器web
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() {
}
}
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); // } }
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(); } }
二、而後在後端的控制器生成jwt令牌,傳入前端ajax
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; } }
三、而後咱們的Vuex中定義變量jwt,經過mutations和gette操做返回jwt算法
export default {
jwt:''
}
export default {
setResturantName: (state, payload) => {
state.resturantName = payload.resturantName;
},
setJwt: (state, payload) => {
state.jwt= payload.jwt;
}
}
export default {
getResturantName: (state) => {
return state.resturantName;
},
getJwt: (state) => {
return state.jwt;
}
}
4.而後再前端NodeJs服務器配置攔截器,配置在http目錄下apache
// 請求攔截器
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);
});
這樣就完成登陸驗證,咱們不登陸直接進入主頁面是看不到任何效果的json
咱們登陸後就能夠看到