JWT
官方网址:JSON Web Token Introduction - jwt.io
传统的身份验证(session,cookies)
session认证:
? http协议本身是一种无状态的协议,而这就意味着如果用户向服务器提供账号密码进行用户认证,那么下一次请求时,用户还要再一次的进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发送的请求。因此在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉保存为cookie ,以便下一次请求。这就是传统的基于session 认证。
认证流程:
弊端:
-
每个用户经过我们的应用认证之后,我们的应用都要在服务器做一次记录,以方便用户瞎猜请求的鉴别,通常而言Session 都是保存在内存中,而随着认证用户的增多,服务端的开销明显增大 -
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这就意味着用户瞎猜请求还必须要请求在这台服务器中,这样在分布式的应用中,相应的限制了负载均衡器的能力,限制应用的扩展能力 -
因为时基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击(CSRF)
JWT(Json Web token)
什么是JWT?
?
? Json Web Token 令牌简称 JWT
? JSON Web令牌(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名
? 尽管JWT可以被加密以在各方之间提供保密性,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌对其他方隐藏这些声明。当使用公钥/私钥对令牌进行签名时,签名还证明只有持有私钥的一方才是签名方。
什么时候使用JWT?
-
授权:这是使用JWT最常见的场景。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用JWT的一种功能,因为它的开销小,并且能够轻松地跨不同的域使用。 -
信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名,例如,使用公钥/私钥对,您可以确保发送者是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。
Json Web令牌结构
组成结构:
? Header(头部)
? Payload(有效载荷)
? Signature (签名)
通常JWT的格式
? xxxxx.yyyyy.zzzzz
Header
? 标头通常由两部分组成:令牌的类型(JWT)和正在使用的签名算法(如HMAC SHA256或RSA)
{
"alg": "HS256",
"typ": "JWT"
}
? 对这个JSON进行Base64Url编码,形成JWT的第一部分。
Pyload
? 令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和附加数据的声明。有三种类型的索赔:注册索赔、公共索赔和私人索赔。
- 注册声明:这些声明是一组预定义的声明,不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。其中一些是:iss(发行人)、exp(到期时间)、sub(主题)、aud(观众)和其他。 (请注意,声明名称只有三个字符长,因为JWT是紧凑的。)
- 公开声明:这些声明可以由使用JWTs的人随意定义。但是为了避免冲突,应该在IANA JSON Web令牌注册表中定义它们,或者将它们定义为包含防冲突命名空间的URI。
- 私人索赔:这些自定义索赔是为了在同意使用它们的各方之间共享信息而创建的,既不是注册索赔,也不是公开索赔。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对有效负载进行Base64Url编码,以形成JSON Web令牌的第二部分
请注意,对于签名的令牌,此信息尽管受到了防篡改保护,但任何人都可以读取。除非经过加密,否则不要将机密信息放入JWT的有效负载或头元素中。
Signature
? 要创建签名部分,您必须获取编码的报头、编码的有效负载、一个秘密、报头中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名的目的:
? 签名用于验证消息在发送过程中没有被更改,并且,对于使用私钥签名的令牌,它还可以验证JWT的发送者是否是它所说的人。
? 最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容 被篡改,如果有人对头部以及负载的内容解码之后进行修改,在进行编码,最后加上之前签名组合成新的JWT的话,那么服务器会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的,如果要对新的头部和负载进行签名,在不知道服务器加密时使用的密钥的话,得出来的签名也是不一样的。
信息安全问题:
? 请注意,对于签名的令牌,此信息尽管受到了防篡改保护,但任何人都可以读取。除非经过加密,否则不要将机密信息放入JWT的有效负载或头元素中。
综合
输出是三个由点分隔的Base64 URL字符串,这些字符串可以在HTML和HTTP环境中轻松传递,同时与基于XML的标准(如SAML)相比更加紧凑。
下面显示了一个JWT,该JWT对前一个头和有效负载进行了编码,并使用机密签名。
使用JWT
怎样使用JWT
? 在身份验证中,当用户使用其凭据成功登录时,将返回一个JSON Web令牌。由于令牌是凭证,因此必须非常小心地防止安全问题。一般来说,您不应将代币保留的时间超过所需的时间。
? 由于缺乏安全性,您也不应将敏感会话数据存储在浏览器存储中。
? 每当用户想要访问受保护的路由或资源时,用户代理都应该发送JWT,通常在使用承载模式的授权报头中
? Authorization: Bearer
?
? 在某些情况下,这可能是一种无状态授权机制。服务器的受保护路由将在授权标头中检查有效的JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库中某些操作的需要,尽管情况并非总是如此。
? 如果令牌在授权标头中发送,则跨源资源共享(CORS)不会成为问题,因为它不使用cookie。
? 下图显示了如何获取JWT并将其用于访问API或资源:
- 应用程序或客户端向授权服务器请求授权。这是通过不同的授权流之一执行的。例如,典型的OpenID Connect兼容web应用程序将使用授权代码流通过/oauth/authorize端点。
- 当授权被授予时,授权服务器向应用程序返回一个访问令牌。
- 应用程序使用访问令牌访问受保护的资源(如API)。
请注意,对于签名令牌,令牌中包含的所有信息都会向用户或其他方公开,即使他们无法更改。这意味着您不应将机密信息放入令牌中。
为什么使用JWT
? 让我们谈谈JSON Web令牌(JWT)与简单Web令牌(SWT)和安全断言标记语言令牌(SAML)相比的优势。
? 由于JSON没有XML那么详细,所以编码时其大小也更小,使得JWT比SAML更紧凑。这使得JWT成为在HTML和HTTP环境中传递的好选择。
? 就安全性而言,SWT只能使用HMAC算法由共享密钥对称签名。但是,JWT和SAML令牌可以使用X.509证书形式的公钥/私钥对进行签名。与签署JSON的简单性相比,使用XML数字签名签署XML而不引入模糊的安全漏洞是非常困难的。
? JSON解析器在大多数编程语言中都很常见,因为它们直接映射到对象。相反,XML没有自然的文档到对象的映射。这使得使用JWT比使用SAML断言更容易。
? 关于使用,JWT在互联网范围内使用。这突出了JSON Web令牌在多个平台(尤其是移动平台)上客户端处理的便利性。
编码JWT和编码SAML的长度比较
编码实现
? 导入jwt maven依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
? 生成Token
header:通常情况下是默认携带的
@Test
void contextLoads() {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,100);
Map<String, Object> map = new HashMap<>();
map.put("alg","HS256");
map.put("typ","JWT");
Date date = new Date(60);
String sign = JWT.create()
.withExpiresAt(instance.getTime())
.withHeader(map)
.withClaim("name", "lichunhao")
.withClaim("name", "lichunhao")
.withClaim("name", "lichunhao")
.sign(Algorithm.HMAC256("sssdad"));
System.out.println(sign);
}
? 验证token
@Test
void test(){
JWTVerifier build = JWT.require(Algorithm.HMAC256("sssdad")).build();
DecodedJWT verify = build.verify("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibGljaHVuaGFvIiwiZXhwIjoxNjMxNjk2OTI0fQ.ZRiJ7JCW_Exx02cxCcqbrIJIA7pYrAMrwoD8sNoPEl4");
System.out.println(verify.getClaim("name"));
}
常见的异常信息:
SignatureVerificationException 签名不一致异常
TokenExpiredException 令牌过期异常
AlgorithmMismatchException 算法不匹配异常
InvalidClaimException 失效的payload 异常
封装工具类
? 封装的代码可能不相同,但是方法大致就是为
? 生成令牌
? 验证令牌(分析令牌错误)
public class JWTUtils {
private static final String SING = "!@#$!@#";
public static String getToken(Map<String, String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7);
JWTCreator.Builder builder = JWT.create();
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
String token = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));
return token;
}
public static boolean verifyToken(String token){
if (token == null){
return false;
}
try {
JWTVerifier build = JWT.require(Algorithm.HMAC256(SING)).build();
build.verify(token);
return true;
}catch (Exception e){
return false;
}
}
}
整合Springboot
导入maven依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
拦截器拦截:
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
HashMap<String , String > map = new HashMap<>();
try {
JWTUtils.verifyToken(token);
return true;
}catch (SignatureVerificationException e){
map.put("result","签名不一致");
}catch (TokenExpiredException e){
map.put("result","签名过期");
}catch (AlgorithmMismatchException e){
map.put("result","算法不匹配");
}catch (InvalidClaimException e){
map.put("result","无效的token");
}
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截A");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截后");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
springboot登录拦截:
? JWTUtils
public class JWTUtils {
private static final String SING = "!@#$!@#";
public static String getToken(Map<String, String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7);
JWTCreator.Builder builder = JWT.create();
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
String token = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));
return token;
}
public static boolean verifyToken(String token){
if (token == null){
return false;
}
try {
JWTVerifier build = JWT.require(Algorithm.HMAC256(SING)).build();
build.verify(token);
return true;
}catch (Exception e){
return false;
}
}
}
? 拦截器
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (JWTUtils.verifyToken(token)){
return true;
}
response.sendRedirect("/login");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截A");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截后");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
? 注入拦截器
@Configuration
public class LoginInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptor = registry.addInterceptor(new JWTInterceptor());
interceptor.excludePathPatterns("/login");
}
}
参考文献:官方文档 JSON Web Token Introduction - jwt.io
学习的方法有很多种,找到适合自己的会很轻松
|