作为一个新手,我认为第一步就是去看官方文档 jjwt 别管英语有多差,有道翻译或者网页翻译结果语义不尽相同,也要先看文档,尽管我也看不懂,但是能大概知道这是做什么的,拿来怎么使用,然后再根据博客和一些资料进行整理,我觉得这样能一个比较深刻的印象!
作用
官方文档上说,JWT(JSON Web Token) 是一种可以验证双方之间传递信息的手段,并且以JSON格式呈现,JWT是可签名的,变成JWS,或者加密,成为JWE。
这让使用JWT的用户增加了一层加验证性和安全性,例如,接收器通过验证签名对JWT没有被篡改具有高度的信心。
JWT字符串格式呈现:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJudWxsaiJ9.W8hfFfcMVgmlAhTRUl4GHNAq4tq_MWJGB1bv-r9wMCE
分为三部分,(header,payload)用Base64编码,都是以‘ . ’进行分隔开的:
第一部分:Header:头部,记录了一些元数据,例如采用何种算法,令牌类型等。
第二部分:Payload:负载,存储我们的关键信息。
第三部分:签名,前两部分的签名,防止数据篡改。
我们主要关注 Payload,JWT 官方规定了 7 个供选择的字段:
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
说白一点,到底是用来干嘛的(个人理解):
一般在用户登陆成功,服务端便会返回一串 字符串(同上),可以用来作为唯一token使用,在某个时间段内失效,验证合法性,也可以使用 jwt 来做 restful api 的身份认证,其实它就是一个标识,是每个用户的身份证,你要做什么,例如:开房,就必须要使用身份证才能进行,而身份证得保证合法,正确,安全。身份证到期就得去再办一次身份证,不让就失效了,就不可以开房了 :)
java实现
第一步,我查看文档,jjwt框架,导入pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
第二步创建 jwt Token创建,写个工具类,方便以后调用。
生成key在官网上也是有讲的,也给出例子,可以直接使用
/**
* 由字符串生成加密key
*
* @return
*/
private static final String KEY1 = "somethinghereshouldbelong";
public static Key generalKey() {
try {
// 一般服务端自定义一个KEY值,转换成byte[] 再转成SecretKey
String stringKey = "UnWhVr6cKjw5gzwnR6j5FjYpox6kRoyHbvaTwcfexb11QrKrvVeoGGP3YD3cxlKvyJL6lrK0XX0oMGcA5nPIq7ucGeUFFZ7sIuR";
byte[] encodedKey = Base64.decodeBase64(stringKey);
SecretKey key = Keys.hmacShaKeyFor(encodedKey);
//如果嫌麻烦,可以直接使用jjwt 提供key算法
// SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //or HS384 or HS512
return key;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 创建新token
* @param userId
* @return
*/
public static String createNewToken(String userId){
//Header:头部,记录了一些元数据,例如采用何种算法,令牌类型等。
//Payload:负载,存储我们的关键信息。
//Signature:签名,前两部分的签名,防止数据篡改。
//Payload 官方提供7个可选字段
//iss:Issuer jwt签发者
//sub:Subject 面向的用户(jwt所面向的用户)
//aud:Audience 接收jwt的一方
//exp:Expiration 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
//nbf:Not Before 定义在什么时间之前,该jwt都是不可用的.
//iat:Issued At jwt的签发时间
//jti:JWT ID jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
//setClaims:自定义字段信息
Key secretKey = generalKey();
//获取当前时间
Date now = new Date(System.currentTimeMillis());
Map<String,Object> map=new HashMap<>();
map.put("name","河神");
//过期时间
Date expiration = new Date(now.getTime() + 72000);
return Jwts
.builder()
.setClaims(map)
.setSubject("User")
.setIssuedAt(now)
.setAudience(userId)
.setIssuer("Login")
.setExpiration(expiration)
.signWith(secretKey,SignatureAlgorithm.HS512)
.compact();
}
第三步,解密,当用户使用API时,我们需要验证此Token是否签名正确,才能进行服务端下一步
/**
* 解密jwt
* @param jwt 密钥
* @return 主题
* @throws Exception 如果发生 JwtException,说明该密钥无效
*/
public static JwtParser parseJWT (String jwt) {
Key key = generalKey();
try {
JwtParser parser = Jwts.parser();
parser
.setSigningKey(key)
.parseClaimsJws(jwt)
.getBody()
.getSubject();
return parser;
}catch (SignatureException signatureException){
throw new BusiHandlerException("签名错误",10001);
}catch (ExpiredJwtException ex){
throw new BusiHandlerException("签名过期",10001);
}catch (JwtException ex){
throw new BusiHandlerException(ex.getMessage(),ex.getCause());
}
}
异常可以自己去定义,这样简单的使用方式就OK了,接下来就是拦截它。
我们或许要达到此类效果:
拦截是否传入token,附带查看是否有注解,是否需要去验证
根据网上的资料和博客,基本清一色的使用拦截器,这里我想试试AOP来达到此效果。
代码如下,不喜勿喷:
@Aspect
@Order(-1)
@Component
public class BusinessAspect {
/**
* 切入business
*/
@Pointcut("execution(public * com.hj.project.controller.*.*(..))")
public void pointCut() {
}
/**
* around
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//获得request response
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
//获得自定义注解
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Token anno = method.getAnnotation(Token.class);
//如果有@Token注解
if (anno == null)
{
return joinPoint.proceed();
// throw new RuntimeException("method->" + method.getName() + " PathAuth annotation required");
}else{
String token = request.getHeader("token");
if(StringUtils.isBlank(token)){
throw new BusiHandlerException("签名不能为空",10001);
}
TokenUtils.parseJWT(token);
return joinPoint.proceed();
}
}
}
很简单,是给新手的一个入门,有更好的建议,希望不吝赐教