vue---Jwt介绍与使用(vuex)
Jwt介绍什么是JWTJWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/token进行用户身份验证的流程:客户端使用用户名和密码请求登录服务端收到请求,验证用户名和密码验证成功后,服务端 会签发一个token,再把这个token返回给客户端客户端收到token后可以把它存储起来,比如放到cookie中客户端每次向服务端
Jwt介绍
什么是JWT
token
进行用户身份验证的流程:
客户端使用用户名和密码请求登录
- 服务端收到请求,验证用户名和密码
- 验证成功后,服务端 会签发一个token,再把这个token返回给客户端
- 客户端收到token后可以把它存储起来,比如放到cookie中
- 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
JWT
就是上述流程当中token
的一种具体实现方式,其全称是JSON Web Token
,官网地址:https://jwt.io/
token优点
- 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
- 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
- 更适用CDN:可以通过内容分发网络请求服务端的所有资料
- 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
- 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御
为什么要用JWT
session认证的缺点
- 传统的
session,
随着用户的增多,服务器开销会明显增大- 对于非浏览器的客户端、手机移动端等不适用,因为
session
依赖于cookie
,而移动端经常没有cookie
- 因为
session
认证本质基于cookie
,所以如果cookie
被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie
,这种方式也会失效- 由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用
JWT认证的优势
- JWT的精髓在于:“去中心化”,数据是保存在客户端的。
- 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
- 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端
JWT结构
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.
进行连接形成最终传输的字符串
Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{"typ":"JWT","alg":"HS256"}
Payload
payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为claims,它的一个“属性值对”其实就是一个claim(要求),每一个claim的都代表特定的含义和作用
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
{"sub":"123","name":"Tom","admin":true}
根据JWT的标准,这些claims可以分为以下三种类型:
1、Reserved claims(保留)
它的含义就像是编程语言的保留字一样,属于JWT标准里面规定的一些claim。JWT标准里面定义好的claim有:
iss(Issuser):代表这个JWT的签发主体;
sub(Subject):代表这个JWT的主体,即它的所有人;
aud(Audience):代表这个JWT的接收对象;
exp(Expiration time):是一个时间戳,代表这个JWT的过期时间;
nbf(Not Before):是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
iat(Issued at):是一个时间戳,代表这个JWT的签发时间;
jti(JWT ID):是JWT的唯一标识。
2、 Public claims,略(不重要)3、Private claims(私有)
这个指的就是自定义的claim,比如前面那个示例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,
JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证;而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行按照JWT标准的说明:保留的claims都是可选的,在生成payload不强制用上面的那些claim,你可以完全按照自己的想法来定义payload的结构,不过这样搞根本没必要:
第一是,如果把JWT用于认证, 那么JWT标准内规定的几个claim就足够用了,甚至只需要其中一两个就可以了,假如想往JWT里多存一些用户业务信息,
比如角色和用户名等,这倒是用自定义的claim来添加;第二是,JWT标准里面针对它自己规定的claim都提供了有详细的验证规则描述,
每个实现库都会参照这个描述来提供JWT的验证实现,所以如果是自定义的claim名称,那么你用到的实现库就不会主动去验证这些claim
signature
签名是把header和payload对应的json结构进行base64url编码之后得到的两个串用英文句点号拼接起来,然后根据header里面alg指定的签名算法生成出来的。
算法不同,签名结果不同。以alg: HS256为例来说明前面的签名如何来得到。
按照前面alg可用值的说明,HS256其实包含的是两种算法:HMAC算法和SHA256算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用HMACSHA256来统称
JWT的种类
JWS
:经过签名的JWTJWE
:payload
部分经过加密的JWT
jwt的签名算法有三种:
- HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
- RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
- ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)
JWT的验证
首先引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
配置tomcat允许跨域访问 CorsFilter 设置headers的Jwt属性
package com.zking.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 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");
// 允许客户端,发一个新的请求头jwt
resp.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With, Content-Type, Accept, jwt");
// 允许客户端,处理一个新的响应头jwt
resp.setHeader("Access-Control-Expose-Headers", "jwt");
if ("OPTIONS".equals(req.getMethod())) {// axios的ajax会发两次请求,第一次提交方式为:option,直接返回即可
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
JWT验证过滤器 生成Jwt
package com.zking.vue.jwt;
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 = "^/userAction\\.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();
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>j2eeVue</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- Vue之axios跨域调用过滤器 -->
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.zking.vue.util.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Jwt过滤器 -->
<filter>
<filter-name>JwtFilter</filter-name>
<filter-class>com.zking.vue.jwt.JwtFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JwtFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 中文乱码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.zking.vue.util.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Servlet核心控制器 -->
<servlet>
<servlet-name>ActionServlet</servlet-name>
<servlet-class>com.zking.mvc.framework.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ActionServlet</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>
登录成功生成令牌,并设置到headr中
public String userLogin(HttpServletRequest req,HttpServletResponse resp)
throws ServletException,IOException{
Map<String,Object> json=new HashMap<String,Object>();
User selectUser = userdao.SelectUser(user);
if(selectUser!=null) {
json.put("code","1");
json.put("msg", "用户登录成功");
//生成JWT,并设置到response响应头中
String jwt=JwtUtils.createJwt(json, JwtUtils.JWT_WEB_TTL);
resp.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
}else {
json.put("code", "-1");
json.put("msg", "用户名或密码错误");
}
mapper.writeValue(resp.getOutputStream(),json);
return null;
}
前端Vue
vuex 中state.js定义jwt变量拿来保存令牌
export default{
jwt:null
}
getters.js取值方法
export default{
getjwt:(state)=>{
return state.jwt;
}
}
mutations.js中设置值的方法
export default{
setJwt:(state,pyload)=>{
state.jwt=pyload.jwt;
}
}
main.js中定义全局Vue
window.vm=new Vue({
el: '#app',
router,
store, //在main.js中导入store实例
components: {
App
},
template: '<App/>'
})
前端Jwt验证请求和响应拦截
// 请求拦截器
axios.interceptors.request.use(function(config) {
//每一次向服务器发送请求时,先由请求拦截器在此处获取vuex中的令牌并存入到request请求头带入到后端(登录除外)
//从vuex中获取jwt令牌
let jwt=window.vm.$store.getters.getjwt;
//除登录
if(null!=jwt){
config.headers['jwt']=jwt;
}
console.log(config);
return config;
}, function(error) {
return Promise.reject(error);
});
// 响应拦截器
axios.interceptors.response.use(function(response) {
console.log(response.headers['jwt']);
window.vm.$store.commit('setJwt',{jwt:response.headers['jwt']});
return response;
}, function(error) {
return Promise.reject(error);
});
headers中带有jwt属性以及令牌进行验证,每次向后台请求都会带有令牌请求验证,除了登录之外
vuex中的Index.js
import Vue from 'vue'
import Vuex from 'vuex'
//页面数据持久化
//第一种方式 保存到内存中 永久保存 只能手动清楚 Application
//import persistedstate from 'vuex-persistedstate'
import vuexalong from 'vuex-along'
Vue.use(Vuex)
//分别导入其他相应模块(state/action/getters/mutatios)
import state from './state.js'
import actions from './actions.js'
import mutations from './mutations.js'
import getters from './getters.js'
//新建vuex的store实例
//每一个Vuex应用的核心就是store(仓库),store基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
const store = new Vuex.Store({
state, // 共同维护的一个状态,state里面可以是很多个全局状态
getters, // 获取数据并渲染
actions, // 数据的异步操作
mutations,
//plugins:[persistedstate()]
plugins:[
vuexalong({
//设置保存的集合名称,避免在同一站点下多项目的数据
name:'hellow-vue-along',
session: {
//保存模块 ma 中的a1 到sessionStorage
list:['hotalname','collapsed','jwt']
}
/* local:{
list:[""] //将数据保存到localStoreage
} */
})
]
}) // 处理数据的唯一途径,state的改变或赋值只能在这里
export default store
如果签名认证失败会抛出如下的异常:
io.jsonwebtoken.SignatureException
JWT过期异常:
io.jsonwebtoken.ExpiredJwtException
注意多次运行方法生成的token字符串内容是不一样的,尽管我们的payload信息没有变动。因为JWT中携带了超时时间,所以每次生成的token会不一样,我们利用base64解密工具可以发现payload确实携带了超时时间
更多推荐
所有评论(0)