Jwt

目录

1. JWT是什么? 

2. 为什么使用JWT

3. JWT的工作原理 

4. JWT组成

4.1 JWT实际结构

4.2 Header

4.3 Payload(负荷)

4.4 signature 

 5. 小案例

5.1 pom文件添加依赖

5.2 JwtFilter(这是一个过滤器类)

5.3 JwtUtils (这是一个Jwt工具类)

5.4 在Web.xml里面配置JwtFilter过滤器

5.5 可以在用户登录后生成JWT,并设置到response响应头中

5.6 配置Vuex

5.7 配置请求,响应拦截器


1.  JWT是什么? 

  •  JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案 

2.  为什么使用JWT

  •   JWT的精髓在于:“去中心化”,数据是保存在客户端的。(安全)

3. JWT的工作原理 

  //1. 是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,示例如下:
      {"UserName": "Chongchong","Role": "Admin","Expire": "2018-08-08 20:15:56"}

  //2. 之后,当用户与服务器通信时,客户在请求中发回JSON对象
    
  //3. 为了防止用户篡改数据,服务器将在生成对象时添加签名,并对发回的数据进行验证

 4.  JWT组成

 一个JWT实际上就是一个字符串,它由三部分组成:头部(Header)载荷(Payload)签名(signature)

 

4.1   JWT实际结构

eyJhbGciOiJIUzI1NiJ9.
  eyJzdWIiOiJ7fSIsImlzcyI6InpraW5nIiwiZXhwIjoxNTYyODUwMjM3LCJpYXQiOjE1NjI4NDg0MzcsImp0aSI6ImM5OWEyMzRmMDc4NzQyZWE4YjlmYThlYmYzY2VhNjBlIiwidXNlcm5hbWUiOiJ6c3MifQ.
WUfqhFTeGzUZCpCfz5eeEpBXBZ8-lYg1htp-t7wD3I4 

 4.2 Header

 {"typ":"JWT","alg":"HS256"}
      这个json中的typ属性,用来标识整个token字符串是一个JWT字符串;它的alg属性,用来说明这个JWT签发的时候所使用的签名和摘要算法
      typ跟alg属性的全称其实是type跟algorithm,分别是类型跟算法的意思。之所以都用三个字母来表示,也是基于JWT最终字串大小的考虑,
      同时也是跟JWT这个名称保持一致,这样就都是三个字符了…typ跟alg是JWT中标准中规定的属性名称 

 4.3 Payload(负荷)

  {"sub":"123","name":"Tom","admin":true}
      payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为claims,
      它的一个“属性值对”其实就是一个claim(要求),
      每一个claim的都代表特定的含义和作用。
      
      注1:英文“claim”就是要求的意思
      注2:如上面结构中的sub代表这个token的所有人,存储的是所有人的ID;name表示这个所有人的名字;admin表示所有人是否管理员的角色。
           当后面对JWT进行验证的时候,这些claim都能发挥特定的作用
      注3:根据JWT的标准,这些claims可以分为以下三种类型:
           A. 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的唯一标识。   
           
           B. Public claims,略(不重要)

           C. 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  

 

4.4 signature 

签名是把header和payload对应的json结构进行base64url编码之后得到的两个串用英文句点号拼接起来,然后根据header里面alg指定的签名算法生成出来的。
算法不同,签名结果不同。以alg: HS256为例来说明前面的签名如何来得到。


       按照前面alg可用值的说明,HS256其实包含的是两种算法:HMAC算法和SHA256算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用HMACSHA256来统称     

 5. 小案例

前言:这篇小案例采用的是模仿网银一类...如果用户在程序规定的时间不请求后端就会失效 

 5.1 pom文件添加依赖

 <!-- jwt -->
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>${jwt.version}</version>
</dependency>
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>${java.jwt.version}</version>
</dependency>

 5.2 JwtFilter(这是一个过滤器类)

package com.zking.ssm.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验证过滤器,配置顺序 :CorsFilter-->JwtFilter-->struts2中央控制器
 * 
 * @author Administrator
 *
 */

public class JwtFilter implements Filter {

	// 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
	private static String EXCLUDE = "^/user/userLogin?.*$";

	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;
		//获取当前请求路径。只有登录的请求路径不进行校验之外,其他的URL请求路径必须进行JWT令牌校验
		//http://localhost:8080/ssh2/bookAction_queryBookPager.action
		//req.getServletPath()==/bookAction_queryBookPager.action
		String path = req.getServletPath();
		if (OFF || isExcludeUrl(path)) {// 登陆直接放行
			chain.doFilter(request, response);
			System.out.println("第一次登录会放行");
			return;
		}

		// 从客户端请求头中获得令牌并验证
		//token=头.载荷.签名
		String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
		Claims claims = this.validateJwtToken(jwt);
		//在这里请各位大哥大姐从JWT令牌中提取payload中的声明部分
		//从声明部分中获取私有声明
		//获取私有声明中的User对象 -> Modules
		Boolean flag=false;
		if (null == claims) {
			// resp.setCharacterEncoding("UTF-8");
			resp.sendError(403, "JWT令牌已过期或已失效");
			/*System.out.println("已经过期");*/
			return;
		} else {
			/*System.out.println("没有过期");*/
			//1.获取已经解析后的payload(私有声明)
			//2.从私有声明中当前用户所对应的权限集合List<String>或者List<Module>
			//3.循环权限(Module[id,url])
			// OK,放行请求 chain.doFilter(request, response);
			// NO,发送错误信息的JSON
			// ObjectMapper mapper=new ObjectMapper()
			// mapper.writeValue(response.getOutputStream(),json)
			
			String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL);
			resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt);
			/*System.out.println("重新的jwt令牌" + newJwt);*/
			chain.doFilter(request, response);
		}
	}

	/**
	 * 验证jwt令牌,验证通过返回声明(包括公有和私有),返回null则表示验证失败
	 */
	private Claims validateJwtToken(String jwt) {
		Claims claims = null;
		try {
			if (null != jwt) {
				//该解析方法会验证:1)是否过期 2)签名是否成功
				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);
	// }

}

5.3 JwtUtils (这是一个Jwt工具类)

package com.zking.ssm.jwt;

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那部分,jwt已经将这部分内容封装好了。
	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的时间,即签发时间 2021-10-30 10:02:00 -> 30 10:32:00
	
		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) {
		//解密JWT,获取所有的声明(私有和标准)
		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();
	}
}




 5.4 在Web.xml里面配置JwtFilter过滤器

<!--JwtFilter-->
  <filter>
    <filter-name>jwtFilter</filter-name>
    <filter-class>com.zking.ssm.jwt.JwtFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>jwtFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

5.5 可以在用户登录后生成JWT,并设置到response响应头中

//私有要求claim
Map<String,Object> json=new HashMap<String,Object>();
json.put("username", userVo.getUsername());
//生成JWT,并设置到response响应头中
String jwt=JwtUtils.createJwt(json, JwtUtils.JWT_WEB_TTL);
/* System.out.println(jwt);*/
response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);

 5.6 配置Vuex

  • state.js 

/* 值的状态 */
export default {
      jwt:null
}
  •  getters.js
/* 获取值 */
export default {
  getJwt: (state) => {
    return state.jwt
  }

}
  • mutations.js
/* 同步修改值 */
export default {
  setJwt: (state, payload) => {
    return state.jwt = payload.jwt;
  }
}

 5.7 配置请求,响应拦截器

  • 需要在main.js里面Vue实例取一个名称即可 
window.jwt=new Vue({
  el: '#app',
  router,
  store,
  components: {
    App
  },
  template: '<App/>',
  data: {//Bus总线
    //自定义的事件总线对象,用于父子组件的通信
    Bus: new Vue()
  }
})
// 请求拦截器
axios.interceptors.request.use(function(config) {
	//从vuex中取出jwt
  let jwt = window.jwt.$store.getters.getJwt;
  //设置到请求头中
  if (jwt) {
  	config.headers['jwt'] = jwt;
  }
  return config;
}, function(error) {
	return Promise.reject(error);
});

// 响应拦截器
axios.interceptors.response.use(function(response) {
  console.log(response.headers['jwt']);
  let jwt = response.headers['jwt'];
  //将响应头中的jwt保存到vuex中
  //this.$store.commit();
  if (jwt) {
    window.jwt.$store.commit('setJwt',{jwt:jwt});
  }
  return response;
}, function(error) {
	return Promise.reject(error);
});
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值