JSON Web Tokens,是一种开发的行业标准规范RFC 7519。广泛的用在系统的认证和数据交换方面。
JWT 由三个部分依次组成
2.1、Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,包含算法和token类型。
需要对json进行base64url加密
{
"alg": "HS256",
"typ": "JWT"
}
2.2、Payload
用来存放实际需要传递的数据。
需要json进行base64url加密
里面的前五个字段都是由JWT的标准所定义的,并且也支持自定义字段
iss: 该JWT的签发者
sub: 该JWT所面向的用户
aud: 接收该JWT的一方
exp(expires): 什么时候过期,这里是一个Unix时间戳
iat(issued at): 在什么时候签发的
#自定义字段
name:hello
2.3、Signature
把前两段的base密文通过·拼接起来,使用HS256加密
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
例如
eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2Puk_fv6cyfk0B1j7vbIqw_Q
4.1、基于session的认证
http协议是一种无状态的协议,本身是无法对访问的客户端进行识别。
采用session机制,客户端在服务端登陆成功之后,服务端会生成一个sessionID,返回给客户端,客户端将sessionID保存到cookie中,再次发起请求的时候,携带cookie中的sessionID到服务端,服务端会缓存该session(会话),当客户端请求到来的时候,服务端就知道是哪个用户的请求,并将处理的结果返回给客户端。
存在问题:
4.2、基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,服务端不需要保存认证信息或者会话信息
跨域访问:基于Token的访问策略可以克服cookies的跨域问题
无状态token:token无状态,session有状态的
使用前后端分离、移动端:Cookie手机端不支持
跨平台:语言无关性,标准规范
避免了CSRF 攻击
JWT撤销问题:无法在服务器端撤销,只能辅助逻辑判断处理
jwt受自身机制原因,payload具有过期时间,参与签名过程,过期时间改动,签名发生改变,因此jwt本身是不支持续签的。但是结合一些方案来辅助实现。
package com.spring.util;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 测试
*
* @author yilei
* @className DemoController
* @date 2021/3/1 20:33
**/
@Controller
public class DemoController {
/**
* 获取access_token
*
* @param clientId 客户端ID
* @param clientSecret 客户端secret
* @return java.lang.String
* @author yilei
* @date 2021-03-01 21:32
*/
@RequestMapping(value = "tokens")
@ResponseBody
public String tokens(String clientId, String clientSecret) {
Map map = new HashMap<>(16);
// 验证客户端ID,密钥是否匹配
if (StringUtils.equals("ddddd", clientId) && StringUtils.equals("sssss", clientSecret)) {
// 生成access_token
String token = JwtTokenUtil.generateToken(clientId);
map.put("code", 1);
map.put("access_token", token);
Date expiration = JwtTokenUtil.getClaimsFromToken(token).getExpiration();
map.put("expires_in", DateUtil.between(expiration, new Date(), DateUnit.MS));
} else {
map.put("code", -1);
map.put("msg", "授权信息不正确!");
}
return JSON.toJSONString(map);
}
/**
* 校验token
*
* @param token
* @param clientId 客户端ID
* @return java.lang.String
* @author yilei
* @date 2021-03-01 21:33
*/
@RequestMapping(value = "tokens/check")
@ResponseBody
public String tokensCheck(@RequestHeader String token, @RequestParam String clientId) {
Map map = new HashMap<>(16);
boolean flag = JwtTokenUtil.validateToken(token, clientId);
if (flag) {
map.put("code", 1);
map.put("client_id", clientId);
Date expiration = JwtTokenUtil.getClaimsFromToken(token).getExpiration();
map.put("expires_in", DateUtil.between(expiration, new Date(), DateUnit.MS));
} else {
map.put("code", -1);
map.put("msg", "非法token或token已过期!");
}
return JSON.toJSONString(map);
}
}
package com.spring.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JwtToken生成的工具类
*
* @author yilei
* @className JwtTokenUtil
* @date 2021/3/1 20:33
**/
@Component
@PropertySource("classpath:conf/jwt.properties")
public class JwtTokenUtil implements InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_CLIENT_ID = "client";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire}")
private Long expire;
private static String JWT_SECRET;
private static Long JWT_EXPIRE;
@Override
public void afterPropertiesSet() {
JWT_SECRET = secret;
JWT_EXPIRE = expire;
}
/**
* 生成token
*
* @param clientId
* @return java.lang.String
* @author yilei
* @date 2021-03-01 20:04
*/
public static String generateToken(String clientId) {
Map claims = new HashMap<>(3);
claims.put(CLAIM_KEY_CLIENT_ID, clientId);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 根据claims生成token
*
* @param claims
* @return java.lang.String
* @author yilei
* @date 2021-03-01 20:04
*/
public static String generateToken(Map claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRE * 1000))
.signWith(SignatureAlgorithm.HS512, JWT_SECRET)
.compact();
}
/**
* 根据token获取claims
*
* @param token
* @return io.jsonwebtoken.Claims
* @author yilei
* @date 2021-03-01 20:04
*/
public static Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(JWT_SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.info("JWT格式验证失败:{}", token);
}
return claims;
}
/**
* 验证token是否合法
* 1、client_id是否匹配
* 2、expire是否过期
*
* @param token
* @param clientId
* @return boolean
* @author yilei
* @date 2021-03-01 20:04
*/
public static boolean validateToken(String token, String clientId) {
Claims claims = getClaimsFromToken(token);
if (null == claims) {
return false;
}
Object cId = claims.get(CLAIM_KEY_CLIENT_ID);
if (null == cId) {
return false;
}
return StringUtils.equals(clientId, cId.toString()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*
* @param token
* @return boolean
* @author yilei
* @date 2021-03-01 20:04
*/
public static boolean isTokenExpired(String token) {
Claims claims = getClaimsFromToken(token);
Date expiredDate = claims.getExpiration();
return expiredDate.before(new Date());
}
public static void main(String[] args) {
String token = JwtTokenUtil.generateToken("123456");
System.out.println(token);
System.out.println("============");
String aa = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2MDg3NjgsImNsaWVudCI6IjEyMzQ1NiIsImNyZWF0ZWQiOjE2MTQ2MDg3MDg2MDV9.DyVpgKZ_2_8fP1gQdNYzdH5pI5JM7diw1ivXTHGtl1ayH6KQn3K3pRxGn1xrQRVzJyz6flLooY2_611XE8RNZA";
Claims claimsFromToken = JwtTokenUtil.getClaimsFromToken(aa);
System.out.println(claimsFromToken);
}
}
获取access_token
var settings = {
"url": "http://localhost:8080/spring_jwt/tokens.do?clientId=ddddd&clientSecret=sssss",
"method": "GET",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
console.log(response);
});
{“access_token”:“eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2NDk3OTUsImNsaWVudCI6ImRkZGRkIiwiY3JlYXRlZCI6MTYxNDY0OTczNTI5OH0.EsKmthI44vBXdrl3ZkOb2tQGdds2T90LJ1CKXsjj4dge2JCW4Rw1c5MbMwwzwaGoDbaGE09Fwav6KTnFGqI-qg”,“code”:1,“expires_in”:59193}
校验token
var settings = {
"url": "http://localhost:8080/spring_jwt/tokens/check.do?clientId=ddddd",
"method": "GET",
"timeout": 0,
"headers": {
"token": "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2NDk3OTUsImNsaWVudCI6ImRkZGRkIiwiY3JlYXRlZCI6MTYxNDY0OTczNTI5OH0.EsKmthI44vBXdrl3ZkOb2tQGdds2T90LJ1CKXsjj4dge2JCW4Rw1c5MbMwwzwaGoDbaGE09Fwav6KTnFGqI-qg"
},
};
$.ajax(settings).done(function (response) {
console.log(response);
});
{“code”:1,“expires_in”:5206,“client_id”:“ddddd”}
源码spring-jwt