原创

解析JWT以及如何实现跨域

温馨提示:
本文最后更新于 2018年06月13日,已超过 2,355 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

什么是JWT?

JWT官网

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSAECDSA的公钥/私钥对对JWT进行签名

JWT的使用场景?

单机和分布式应用下登录校验,session 共享

单机和多节点tomcat应用登录检验

①、单机 tomcat 应用登录,sesssion 保存在浏览器和应用服务器会话之间,用户登录成功后,服务端会保证一个 session,也会给客户端一个 sessionId,客户端会把 sessionId 保存在 cookie 中,用户每次请求都会携带这个 sessionId
②、多节点 tomcat 应用登录,开启 session 数据共享后,每台服务器都能够读取 session。缺点是每个 session 都是占用内存和资源的,每个服务器节点都需要同步用户的数据,即一个数据需要存储多份到每个服务器,当用户量到达百万、千万级别的时,占用资源就严重,用户体验特别不好!!

分布式应用中 session 共享

①、真实的应用不可能单节点部署,所以就有个多节点登录 session 共享的问题需要解决。tomcat 支持 session 共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐
②、Reids 集群,存储登陆的 token,向外提供服务接口,Redis 可设置过期时间(服务端使用 UUID生成随机 64 位或者 128token ,放入 Redis 中,然后返回给客户端并存储)。
③、用户第一次登录成功时,需要先自行生成 token,然后将 token 返回到浏览器并存储在 cookie 中, 并在 Redis 服务器上以 tokenkey,用户信息作为 value 保存。后续用户再操作,可以通过 HttpServletRequest 对象直接读取 cookie 中的 token,并在 Redis 中取得相对应的用户数据进行比较(用户每次访问都携带此 token,服务端去 Redis 中校验是否有此用户即可)。
④、 缺点:必须部署 Redis,每次必须访问 RedisIO 开销特别大。

所以,最终方案就是使用JWT

JWT的结构?

JSON Web令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:

  • 标头
  • 有效载荷
  • 签名

因此,JWT通常如下所示。

xxxxx.yyyyy.zzzzz

比如,下面这个就是JWT:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsemhwbyIsImlkIjo2NiwibmFtZSI6Imx6aHBvIiwiaW1nIjoiVGhpcyBpcyBIZWFkSW1nIiwiaWF0IjoxNTcyMzYyNjkxLCJleHAiOjE1NzI5Njc0OTF9.eYojGsaX9Vk5v6o9JqKcRJK2SnXXxXDtY5AeqtusSTs

JWT结构.PNG

标头(Header)

标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。

{
  "alg": "HS256",
  "typ": "JWT"
}

然后,此JSON被Base64Url编码以形成JWT的第一部分。

上面这个JWT例子:eyJhbGciOiJIUzI1NiJ9就是标头。

有效载荷(Payload)

令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。索赔有以下三种类型:注册的公共的私人索赔。

  • 已注册的权利要求:这些是一组非强制性的但建议使用的预定义权利要求,以提供一组有用的,可互操作的权利要求。其中一些是: iss(发出者), exp(到期时间), sub(主题), aud(受众)

    请注意,声明名称仅是三个字符,因为JWT是紧凑的。

  • 公共权利:这些可以随意通过那些使用JWTs来定义。但是为避免冲突,应在 IANA JSON Web令牌注册表中定义它们,或将其定义为包含抗冲突名称空间的URI。

  • 私人索赔:这是创建共享使用它们同意并既不是当事人之间的信息自定义声明注册公众的权利要求。

有效负载示例可能是:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后,对有效负载进行Base64Url编码,以形成JSON Web令牌的第二部分。

请注意,对于已签名的令牌,此信息尽管可以防止篡改,但任何人都可以读取。除非将其加密,否则请勿将机密信息放入JWT的有效负载或报头元素中。

之前的JWT例子:eyJzdWIiOiJsemhwbyIsImlkIjo2NiwibmFtZSI6Imx6aHBvIiwiaW1nIjoiVGhpcyBpcyBIZWFkSW1nIiwiaWF0IjoxNTcyMzYyNjkxLCJleHAiOjE1NzI5Njc0OTF9就是有效载荷。

签名(Signature)

要创建签名部分,您必须获取编码的标头,编码的有效载荷,机密,标头中指定的算法,并对其进行签名。

例如,如果要使用HMAC SHA256算法,则将通过以下方式创建签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在整个过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。

在之前的例子,eYojGsaX9Vk5v6o9JqKcRJK2SnXXxXDtY5AeqtusSTs就是签名。

三者结合在一起,输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。

JWT的原理?

服务器认证以后,生成一个 JSON 对象发回给用户,以后用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。也就是说服务器就不保存任何 session 数据了,即服务器变成无状态了,从而比较容易实现扩展。

java 简单来说,就是通过一定规范来生成 token,然后可以通过解密算法逆向解密 token,这样就可以获取用户信息 。

JWT流程.png

  1. 应用程序或客户端向授权服务器请求授权。这是通过不同的授权流程之一执行的。例如,典型的符合OpenID Connect的 Web应用程序将/oauth/authorize使用授权代码流通过端点。
  2. 授予授权后,授权服务器会将访问令牌返回给应用程序。
  3. 应用程序使用访问令牌来访问受保护的资源(例如API)。

JWT的优缺点?

  • 优点:生产的 token 可以包含基本信息,比如 id、用户昵称、头像等信息,避免再次查库;存储在客户端,不占用服务端的内存资源。
  • 缺点:token 是经过 base64 编码,所以可以解码,因此 token 加密前的对象不应该包含敏感信息(如用户权限,密码等) 。

JWT 格式组成:头部+负载+签名 ( header + payload + signature )

头部:主要是描述签名算法。
负载:主要描述是加密对象的信息,如用户的 id 等,也可以加些规范里面的东西,如 iss 签发者,exp 过期时间,sub 面向的用户。
签名:主要是把前面两部分进行加密,防止别人拿到 token 进行base 解密后篡改 token。

JWT的使用方式?

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

JWT官方提供的Java跨域的包: 我使用的https://github.com/jwtk/jjwt

https://jwt.io/#debugger 这里面有很多,各种语言的,参考。

科普一下跨域

什么是跨域?

参考: https://cloud.tencent.com/developer/article/1175899

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是\浏览器施加的**安全限制。

所谓同源是指,域名,协议,端口均相同。

举个例子:

http://www.lzhpo.com/index.html 调用 http://www.lzhpo.com/server.php (非跨域)

http://www.lzhpo.com/index.html 调用 http://www.liuzhaopo.top/server.php (主域名不同:123/456,跨域)

如何解决跨域问题?

如果不做跨域设置的话,在8080端口调用8081端口的接口,则报错:test1:1 Access to XMLHttpRequest at 'http://localhost:8081/hello' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

springBoot如何做跨域?

其它方法:

  • 方式1:返回新的CorsFilter
  • 方式2:重写WebMvcConfigurer
  • 方式3:使用注解(@CrossOrigin)
  • 方式4:手工设置响应头(HttpServletResponse )

参考: https://www.jianshu.com/p/477e7eaa6c2f

我讲一下我常用的两种方式:

  1. 在被调用的那一方的方法接口加上注解@CrossOrigin实现跨域。

  2. 配置类实现WebMvcConfigurer接口,然后重写addCorsMappings方法。

    @Configuration
    public class WebConfigurer implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedMethods("GET", "POST", "DELETE", "PUT","PATCH");
        }
    }
    

推荐文章

http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

https://www.cnblogs.com/jpfss/p/10929458.html

https://zhuanlan.zhihu.com/p/80681729

本文目录