什么是JWT
Json web token(JWT)是为了网络应用环境间传递声明而执行的一种基于JSON的开发标准(RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登陆(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
传统的 session 流程
- 浏览器发起请求登陆
- 服务端验证身份,生成身份验证信息,存储在服务端,并且告诉浏览器写入 Cookie
- 浏览器发起请求获取用户资料,此时 Cookie 内容也跟随这发送到服务器
- 服务器发现 Cookie 中有身份信息,验明正身
- 服务器返回该用户的用户资料
JWT 流程
- 浏览器发起请求登陆
- 服务端验证身份,根据算法,将用户标识符打包生成 token, 并且返回给浏览器
- 浏览器发起请求获取用户资料,把刚刚拿到的 token 一起发送给服务器
- 服务器发现数据中有 token,验明正身
- 服务器返回该用户的用户资料
区别
- session 存储在服务端占用服务器资源,而 JWT 存储在客户端
- session 存储在 Cookie 中,存在伪造跨站请求伪造攻击的风险
- session 只存在一台服务器上,那么下次请求就必须请求这台服务器,不利于分布式应用
- 存储在客户端的 JWT 比存储在服务端的 session 更具有扩展性
弊端
JWT中设置过期时间后必须等过期时间到后失效并不能随着登出失效,不过可以结合Redis进行使用,将Token存储在Redis中,设置Redis的过期时间
使用demo
一、引入包(其他诸如redis的包这边没列出)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.1.0</version>
</dependency>
二、编写类JwtHelper
package com.zhanghf.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.time.DateUtils;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
public class JwtHelper {
private static final String SECRET = "session_secret"; //生成token的前缀
private static final String ISSUER = "heaven_token"; //定义发布者
//生成token
public static String genToken(Map<String, String> claims){
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET); //tocken生成的算法
JWTCreator.Builder builder = JWT.create().withIssuer(ISSUER).withExpiresAt(DateUtils.addDays(new Date(), 1)); //设置过期时间为1天
claims.forEach((k,v) -> builder.withClaim(k, v)); //将claims存储到token中
return builder.sign(algorithm).toString(); //签名并且返回token
} catch (IllegalArgumentException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
//验证token
public static Map<String, String> verifyToken(String token) {
Algorithm algorithm = null;
try {
algorithm = Algorithm.HMAC256(SECRET);
} catch (IllegalArgumentException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
DecodedJWT jwt = verifier.verify(token);
Map<String, Claim> map = jwt.getClaims();
Map<String, String> resultMap = Maps.newHashMap();
map.forEach((k,v) -> resultMap.put(k, v.asString()));
return resultMap;
}
}
上述中的SECRET和ISSUER分别为生成token的前缀和定义发布者,可自行修改。genToken方法里面设置过期时间为1天,也可自己定义。
三、登录、鉴权、登录代码
package com.zhanghf.jwt;
import com.google.common.collect.ImmutableMap;
import redis.clients.jedis.Jedis;
import java.time.Instant;
import java.util.Map;
/**
* Created by YQ11053 on 2019/1/5 0005.
*/
public class JwtDemo {
static Jedis jedis = new Jedis("127.0.0.7",6379);
public static void main(String[] args){
// String token = auth("test@qq.com","123456");
// String token = getLoginedUserByToken("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtb29jX3VzZXIiLCJuYW1lIjoiNDQxMzM5OTA1QHFxLmNvbSIsImV4cCI6MTU0NjgyNzEyNywiZW1haWwiOiIxMjM0NTYiLCJ0cyI6IjE1NDY3NDA3MjcifQ.yAunUJCDP_szq5wQbsoCAlga-FFzwLWiI-IETSXGwCc");
// System.out.println("token:"+token);
invalidate("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtb29jX3VzZXIiLCJuYW1lIjoiNDQxMzM5OTA1QHFxLmNvbSIsImV4cCI6MTU0NjgyNzEyNywiZW1haWwiOiIxMjM0NTYiLCJ0cyI6IjE1NDY3NDA3MjcifQ.yAunUJCDP_szq5wQbsoCAlga-FFzwLWiI-IETSXGwCc");
}
/******************************登录*************************************************/
/**
* 校验用户名密码、生成token并返回用户对象
* @param email
* @param passwd
* @return
*/
public static String auth(String email, String passwd) {
String token = onLogin(email,passwd);
return token;
}
//生成token方法
private static String onLogin(String userName,String email) {
String token = JwtHelper.genToken(ImmutableMap.of("email", email, "name", userName,"ts", Instant.now().getEpochSecond()+""));
renewToken(token,email);
return token;
}
//通过redis存储token,就可以随时修改token失效时间,不然只能在jwtHelper中设置的1天过期
private static String renewToken(String token, String email) {
jedis.set(email,token);
jedis.expire(email,300);
return token;
}
/******************************鉴权*************************************************/
public static String getLoginedUserByToken(String token) {
Map<String, String> map = null;
try {
map = JwtHelper.verifyToken(token);
} catch (Exception e) {
System.out.println("错误");
}
String email = map.get("email");
Long expired = jedis.ttl(email);
if (expired > 0L) {
renewToken(token, email);
}
return token;
}
/******************************鉴权*************************************************/
//登出设置token失效的方法
public static void invalidate(String token) {
Map<String, String> map = JwtHelper.verifyToken(token); //校验token
jedis.del(map.get("email"));
}
}