首先要了解一下jwt的三个基本术语,
Header
Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)
{
“typ”:“JWT”,
“alg”:“HS256”
}
Claim
Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。
{
“iss”:“Issuer —— 用于说明该JWT是由谁签发的”,
“sub”:“Subject —— 用于说明该JWT面向的对象”,
“aud”:“Audience —— 用于说明该JWT发送给的用户”,
“exp”:“Expiration Time —— 数字类型,说明该JWT过期的时间”,
“nbf”:“Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理”,
“iat”:“Issued At —— 数字类型,说明该JWT何时被签发”,
“jti”:“JWT ID —— 说明标明JWT的唯一ID”,
“user-definde1”:“自定义属性举例”,
“user-definde2”:“自定义属性举例”
}
Signature
Signature是由Header和Payload组合而成,将Header和Claim这两个Json分别使用Base64方式进行编码,生成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在一起形成一个字符串,然后使用上面定义好的加密算法和一个密匙(这个密匙存放在服务器上,用于进行验证)对这个字符串进行加密,形成一个新的字符串,这个字符串就是Signature。
服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。
下面通过两个工具类来说明一下JWT的具体使用,首先需要导入jwt的jar包,
io.jsonwebtoken
jjwt
0.9.1
/**
* jwt工具类封装
* @author asus
*
*/
public class JwtUtils {
public static final String SUBJECT = "congge";
/**
* 设置token的过期时间
*/
public static final long EXPIRITION = 1000 * 24 * 60 * 60 * 7;
/**
* 秘钥,不同的环境应该配置不同的秘钥,注意保存好,不要泄露
*/
public static final String APPSECRET_KEY = "congge_secret";
/**
* 加密生成token
* @param user
* @return
*/
public static String generateJsonWebToken(Users user){
if(user.getId() == null || user.getUsername()==null||user.getFaceImage()==null){
return null;
}
String token = Jwts.builder().setSubject(SUBJECT)
.claim("id", user.getId())
.claim("name",user.getUsername())
.claim("img", user.getFaceImage())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION ))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
return token;
}
/**
* 解密token获取用户信息
* @param token
* @return
*/
public static Claims checkJWT(String token){
try {
final Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* eyJhbGciOiJIUzI1NiJ9.
* eyJzdWIiOiJjb25nZ2UiLCJpZCI6IjExMDExIiwibmFtZSI6Im51b3dlaXNpa2kiLCJpbWciOiJ3d3cudW9rby5jb20vMS5wbmciLCJpYXQiOjE1NTQ5OTI1NzksImV4cCI6MTU1NTU5NzM3OX0.
* 6DJ9En-UBcTiMRldZeevJq3e1NxJgOWryUyim4_-tEE
* @param args
*/
public static void main(String[] args) {
Users user = new Users();
user.setId("11011");
user.setUsername("nuoweisiki");
user.setFaceImage("www.uoko.com/1.png");
String token = generateJsonWebToken(user);
System.out.println(token);
System.out.println("解密 ====================");
Claims claims = checkJWT(token);
if(claims != null){
String id = claims.get("id").toString();
String name = claims.get("name").toString();
String img = claims.get("img").toString();
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("img:" + img);
}
}
}
运行一下这段程序,可以看到如下结果,可以看出来,我们加密的用户信息是可以成功解密出来的,利用这个特性,可以将用户一些不敏感的信息通过jwt加解密的方式在客户端和服务端之间进行传递,就算是在分布式环境下,大家用的是同一套代码,加解密规则一样,因此对于同一个用户是可以很方便的进行操作,
那么实际业务中该如何使用呢?我认为可以存在这一一个场景,用户登录成功后,后端通过jwt产生一个token返回给客户端,客户端进行存储,以后每次访问相关资源时候,后端会有一个拦截器进行拦截,按照定义好的解密规则解密出用户信息,解密成功并且存在,则允许进行后续的操作,下面是一段模拟代码,有兴趣的小伙伴可以参考,
public class JwtInterceptor implements HandlerInterceptor{
/**
* 模拟拦截用户请求的url,并从url中读取前端传来的token,如果校验通过,则允许进行后续其他的操作
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("authorization");
Claims claims = JwtUtils.checkJWT(token);
String userId = claims.get("id").toString();
String userName = claims.get("name").toString();
String img = claims.get("img").toString();
UserService userService = SpringContextUtil.getBean("usersService");
Users user = userService.findById(userId);
if(user != null){
return true;
}else{
//生成token的动作是不是应该在用户登录成功的时候产生呢?
return false;
}
}
}