目录:
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);
});
这样就完成登录验证,我们不登录直接进入主页面是看不到任何效果的
我们登录后就可以看到