什么是Json Web Token
JWT代表JSON Web Token,是一种用于在网络应用中传递信息的安全、紧凑的标准。它主要用于身份验证和授权,并且被广泛用于前后端分离的应用和单点登录系统。
JWT由三部分组成,通过点号(.)分隔,分别是:
- Header(头部):包含了两部分信息,令牌的类型(通常是"JWT")和所使用的加密算法,例如HMAC SHA256或RSA。
- Payload(负载):也称为Claims(声明),包含了一些实际需要传递的数据。这部分数据可以是预定义的标准声明(例如用户ID、过期时间等),也可以是自定义的声明。
- Signature(签名):使用指定的算法对Header和Payload进行签名,以确保令牌的完整性和真实性。签名需要使用一个秘钥,这样服务器可以使用秘钥来验证签名,从而确认该令牌的合法性。
在用户进行登录认证成功后,服务器会生成一个JWT并返回给客户端。客户端在后续的请求中携带这个JWT作为认证凭证,服务端通过验证JWT的签名来确定用户的身份和权限。由于JWT是自包含的,服务端不需要在数据库中查询用户信息,从而减轻了服务器的负担,并且提高了性能和可扩展性。
需要注意的是,由于JWT是基于Base64编码的,所以虽然它是安全的,但是不应该在JWT中存储敏感信息,因为Base64编码可以被解码。如果需要在JWT中传递敏感信息,应该对其进行加密处理。
Json web Token的应用场景
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
- Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
基于Token的身份认证 与 基于服务器的身份认证
-
基于服务器的身份认证
- 传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。
- Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
- Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
- CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
- CSRF : 用户很容易受到CSRF攻击。
-
JWT与Session的异同点
- 它们都是存储用户信息,然而,Session是在服务器端的,而JWT是在客户端的。
- Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
- JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
- Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
Json Web Token的认证流程
Json Web Token的使用
-
引入依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.3.0</version> </dependency>
-
JWT的简单使用
class Demo1ApplicationTests {
private String tokenStr = "token-JWT";
// 创建 JSON Web Token (JWT) 的方法,其中包含了用户名、用户ID和过期时间
String createJWT() {
Calendar calendar = Calendar.getInstance();
// 将当前时间增加 150 秒(2 分钟30秒)
calendar.add(Calendar.SECOND, 150);
return JWT.create()
.withClaim("username", "jack") // 添加一个名为 "username" 的声明,值为 "jack"
.withClaim("userid", 1) // 添加一个名为 "userid" 的声明,值为 1
.withExpiresAt(calendar.getTime()) // 将令牌的过期时间设置为计算后的时间
.sign(Algorithm.HMAC256(tokenStr)); // 使用 HMAC256 算法和给定的 tokenStr 对令牌进行签名
}
@Test
void TestJWT() {
System.out.println(createJWT()); // 为了测试目的,将生成的 JWT 打印到控制台
// 使用相同的 tokenStr 验证 JWT 并获取解码后的令牌
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(tokenStr)).build();
DecodedJWT verify = verifier.verify(createJWT());
System.out.println(verify.getClaim("username").asString()); // 打印解码后的令牌中的 "username" 声明
System.out.println(verify.getClaim("userid").asInt()); // 打印解码后的令牌中的 "userid" 声明
}
}
-
JWT工具类的封装
public class JWTUtil { private static final String SIGN = "secretKey"; public static String getToken(Map<String,String> map){ Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.DATE,3); JWTCreator.Builder builder = JWT.create(); map.forEach((k,v)->{ builder.withClaim(k,v); }); String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SIGN)); return token; } public static DecodedJWT verify(String token){ return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token); } }
-
创建拦截器
public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); System.out.println(token); Map<String,Object> map = new HashMap<>(); try { JWTUtil.verify(token); map.put("state",true); map.put("msg","认证成功"); return true; }catch (SignatureVerificationException e){ map.put("msg","无效签名"); e.printStackTrace(); }catch (TokenExpiredException e){ map.put("msg","token过期"); e.printStackTrace(); }catch (AlgorithmMismatchException e){ map.put("msg","算法不一致"); e.printStackTrace(); }catch (Exception e){ map.put("msg","token无效"); e.printStackTrace(); } map.put("state",false); String json = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); return false; } }
-
配置拦截器
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/user"); } }