背景:传统的Web应用中,使用session来存在用户的信息,每次用户认证通过以后,服务器需要创建一条记录
保存用户信息,通常是在内存中。
随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大,由于Session是在内存中的,这就带来一些扩展性的问题
servlet依赖于web容器
描述:JSON Web Token (JWT,token的一种),是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
JWT存放在客户端(前端),每次请求的请求头中,携带此JWT发送给服务器,服务器端负责接收 和验证 服务器端可以不用存储JWT,这样可以降低服务器的内存的开销 JWT和语言无关,扩展起来非常方便,无论是PC端还是移动端,都可以很容易的使用 不受cookie的限制
session和JWT的主要区别就是保存的位置,session是保存在服务端的,而JWT是保存在客户端的
JWT就是一个固定格式的字符串,jwt的官网是:https://jwt.io/
结构JWT固定各种的字符串,由三部分组成:
Header,头部
Payload,载荷
Signature,签名
把这三部分使用点(.)连接起来,就是一个JWT字符串
header一般的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。
JWT里验证和签名使用的算法列表如下:
{
"alg": "HS256",
"typ": "JWT"
}
载荷
payload主要用来包含声明(claims ),这个声明一般是关于实体(通常是用户)和其他数据的声明。
声明有三种类型:
registered public private
具体如下:
Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。
iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的 iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
Public claims : 可以随意定义
自定义数据:存放在token中存放的key-value值
Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明
{
"iss": "briup",
"iat": 1446593502,
"exp": 1446594722,
"aud": "www.briup.com",
"sub": "briup@briup.com",
"username": "tom"
}
注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的
把头部和载荷分别进行base64编码之后得到两个字符串,然后再将这两个编码后的字符串用英文句号.连接在一起(头部在前),形成新的字符串:aaa.bbb
签名最后,将上面拼接完的字符串用HS256算法进行加密,在加密的时候,还需要提供一个密钥(secret)。加密后的内容也是一个字符串,这个字符串就是签名。
把这个签名拼接在刚才的字符串后面就能得到完整的JWT字符串。
header部分和payload部分如果被篡改,由于篡改者不知道密钥是什么,也无法生成新的signature部分,
服务端也就无法通过。
在JWT中,消息体是透明的,使用签名可以保证消息不被篡改。 确保密钥不会泄露,否则会被篡改
例如,使用HMACSHA256加密算法,配合秘钥,将前俩部进行加密,生成签名
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
例如,将Header、Payload、Signature三部分使用点(.)连接起来
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2MmI2OWNlZC02YWNlLTRmYzAtOTk5MS00Y WUwMjIxODQ0OTciLCJleHAiOjE2MDYwNTQzNjl9.DNVhr36j66JpQBfcYoo64IRp84dKiQeaq7axHTBcP9 E
例如,使用官网提供的工具,可以对该JWT进行验证和解析
我们使用JWT封装的工具类,也可以完成此操作
依赖
com.auth0 java-jwt 3.11.0
工具类
public class JwtUtil {
private static final long EXPIRE_TIME = 5 * 60 * 1000;
private static final String SECRET = "jwt_secret";
public static String sign(String userId,Map info) {
try {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
return JWT.create()
// 将 user id 保存到 token 里面
.withAudience(userId)
// 存放自定义数据
.withClaim("info", info)
// 五分钟后token过期
.withExpiresAt(date)
// token 的密钥
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getUserId(String token) {
try {
String userId = JWT.decode(token).getAudience().get(0);
return userId;
} catch (JWTDecodeException e) {
return null;
}
}
public static Map getInfo(String token) {
try {
return JWT.decode(token).getClaim("info").asMap();
} catch (JWTDecodeException e) {
return null;
}
}
public static boolean checkSign(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
// .withClaim("username", username)
.build();
verifier.verify(token);
return true;
} catch (JWTVerificationException exception) {
throw new RuntimeException("token 无效,请重新获取");
}
}
}
拦截器
// 拦截认证资源
public class JwtInteceptors implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 判断是不是认证资源(根据拦截器配置 auth
private List security() {
return Collections.singletonList(new ApiKey("Authorization", "token", "header"));
}
private List securityContexts() {
List antPaths = new ArrayList();
antPaths.add("/auth
private Predicate antPathsCondition(List antPaths) {
List> list = new ArrayList<>();
antPaths.forEach(path -> list.add(PathSelectors.ant(path)));
return Predicates.or(list);
}
private List defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Collections.singletonList(new SecurityReference("Authorization", authorizationScopes));
}
}
这里比之前新增的代码如下:
新增代码如图所示,以及下面新增的几个方法
这里会显示一个“锁”的图标,表示这里有些接口是需要认证的
展开模块后,访问路径符合要求接口,也会显示“锁”的图标
点击锁的图标,添加请求头中的统一认证信息(token):
点击认证按钮后,进行访问测试:
这时候,有“锁”图标的接口,在访问的时候,都会携带刚刚设置的请求头中的认证信息token值
这时,无“锁”图标的接口,在访问的时候默认不会携带设置的请求头信息



