IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> JWT入门 -> 正文阅读

[网络协议]JWT入门

JWT

image-20210915133105729

官方网址:JSON Web Token Introduction - jwt.io

传统的身份验证(session,cookies)

session认证:

? http协议本身是一种无状态的协议,而这就意味着如果用户向服务器提供账号密码进行用户认证,那么下一次请求时,用户还要再一次的进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发送的请求。因此在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉保存为cookie ,以便下一次请求。这就是传统的基于session 认证。

认证流程:

image-20210915141555595

弊端:

  • 每个用户经过我们的应用认证之后,我们的应用都要在服务器做一次记录,以方便用户瞎猜请求的鉴别,通常而言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对前一个头和有效负载进行了编码,并使用机密签名。

image-20210915135429082

使用JWT

怎样使用JWT

? 在身份验证中,当用户使用其凭据成功登录时,将返回一个JSON Web令牌。由于令牌是凭证,因此必须非常小心地防止安全问题。一般来说,您不应将代币保留的时间超过所需的时间。

? 由于缺乏安全性,您也不应将敏感会话数据存储在浏览器存储中。

? 每当用户想要访问受保护的路由或资源时,用户代理都应该发送JWT,通常在使用承载模式的授权报头中

? Authorization: Bearer

?

? 在某些情况下,这可能是一种无状态授权机制。服务器的受保护路由将在授权标头中检查有效的JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库中某些操作的需要,尽管情况并非总是如此。

? 如果令牌在授权标头中发送,则跨源资源共享(CORS)不会成为问题,因为它不使用cookie。

? 下图显示了如何获取JWT并将其用于访问API或资源:

image-20210916102152400

  • 应用程序或客户端向授权服务器请求授权。这是通过不同的授权流之一执行的。例如,典型的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令牌在多个平台(尤其是移动平台)上客户端处理的便利性。

image-20210916102412652

image-20210916102425685

编码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) // 头部header 可以省略 
            .withClaim("name", "lichunhao")  // payload 字段 可以多个
            .withClaim("name", "lichunhao")
            .withClaim("name", "lichunhao")
            .sign(Algorithm.HMAC256("sssdad")); // signature 签名
    System.out.println(sign);
}

/*
	输出:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibGljaHVuaGFvIiwiZXhwIjoxNjMxNjkyMDQzfQ.kvHejyHxcarPD5fe_AuoRMT9Z_DNLviSfCMcKqv1f9k
*/

? 验证token

  @Test
    void test(){
        // 创建验证对象
        JWTVerifier build = JWT.require(Algorithm.HMAC256("sssdad")).build();  // 利用密钥解密
        DecodedJWT verify = 			  			                  build.verify("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibGljaHVuaGFvIiwiZXhwIjoxNjMxNjk2OTI0fQ.ZRiJ7JCW_Exx02cxCcqbrIJIA7pYrAMrwoD8sNoPEl4"); //  验证 token
        System.out.println(verify.getClaim("name")); // 获取payload 字段

    }


常见的异常信息:

SignatureVerificationException   签名不一致异常

TokenExpiredException	令牌过期异常

AlgorithmMismatchException	算法不匹配异常

InvalidClaimException	失效的payload 异常

封装工具类

? 封装的代码可能不相同,但是方法大致就是为

? 生成令牌

? 验证令牌(分析令牌错误)


public class JWTUtils {

    private static final String SING = "!@#$!@#";

    // 生成token  这里使用默认的header 头部信息
    public static String getToken(Map<String, String> map) {
        // 设置过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, 7);
        //创建jwt
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        // 指定过期时间,生成token
        String token = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SING));

        return token;
    }

    // 验证token 返回解码的数据
   /* public static DecodedJWT verifyToken(String token) {
        return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }*/

   /* // 获取token信息
    public static DecodedJWT getTokenInfo(String 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 {
        
        //token  放在header中
        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 = "!@#$!@#";

    // 生成token  这里使用默认的header 头部信息
    public static String getToken(Map<String, String> map) {
        // 设置过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, 7);
        //创建jwt
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        // 指定过期时间,生成token
        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 {


    // 请求处理器方法拦截前,进行令牌验证
    /*
    *   重定向到login 登录页面 token 验证不通过,或者为空
    *
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 获取 token
        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"); //  放行login 接口
        //interceptor.addPathPatterns("/**"); 默认全拦截action
    }

}

参考文献:官方文档 JSON Web Token Introduction - jwt.io

学习的方法有很多种,找到适合自己的会很轻松

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-09-18 10:36:22  更:2021-09-18 10:38:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年6日历 -2024/6/27 1:54:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码