想全面理解JWT?一文足矣!
JWT(JSON Web Tokens)的 Header:
JWT的 Header 通常包含两部分:token类型(type)和使用的算法(alg)。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
JWT的 Payload(负载):
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,也可以添加其他必要的字段。
JWT(JSON Web Token)的payload部分用于存储声明信息。这些声明是关于实体(通常是用户)以及额外的数据的声明。因此,理论上,您确实可以在JWT的payload中存储用户的权限信息。
但是,使用JWT存储权限信息需要考虑以下几点:
大小限制:JWT是作为HTTP头部在客户端和服务器之间传输的。HTTP头部太大可能会导致一些问题,因此不建议在JWT中存储大量的数据。如果一个用户有很多详细的权限,那么在JWT中存储这些信息可能不是一个好选择。
安全性:尽管JWT的payload内容可以使用HS256、RS256等算法进行签名以确保其不被篡改,但payload的内容通常是可读的。除非你使用JWE(JSON Web Encryption)对其进行加密,否则不应在其中存储敏感信息。
权限变化:如果您决定在JWT中存储权限信息,当用户的权限发生变化时,这些更改将不会立即反映在JWT中,除非您为用户生成一个新的token。这可能导致权限更新有延迟。
过期策略:JWT通常有一个过期时间,当token过期后,用户需要重新验证以获取新的token。你需要确保token的有效期与业务需求相匹配,以及确保在token过期后有适当的处理机制。
维护性:随着时间的推移,权限结构可能会发生变化,如果权限直接硬编码到JWT中,这可能会导致一些维护上的复杂性。
总之,尽管技术上可以在JWT的payload中存储用户的权限信息,但在决定这样做之前,需要权衡上述提到的各种因素。在许多情况下,建议仅在JWT中存储用户的标识信息(如用户ID),并在需要时在服务器端查询用户的权限。
JWT中的 sub
字段:
sub
字段代表这个 JWT 的主题。这通常是一个用户的标识符,代表这个 token 关联的主体。这可以是用户的 ID,用户名,或者其他能唯一识别用户的标识。你可以在 sub
中包含任何信息,只要这个信息对于主体的唯一标识有意义即可。这个字段不会用于表示用户的权限级别,通常权限级别这类的信息会在 payload 的其它自定义字段中表示。
JWT的组成:
JWT由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。
RSA如何运用到JWT中:
RSA签名算法是一种非对称加密算法,在JWT中,可以选择RSA作为签名算法(例如"RS256")来生成和验证签名。在生成签名时,使用私钥进行签名;而在验证签名时,使用公钥进行验证。这样做的好处是,私钥只需要在服务器端存储,公钥可以公开,甚至可以嵌入在JWT中,增强了安全性。
防止JWT被盗用主要依赖于以下策略:
使用HTTPS:确保所有的通信都是加密的,这样可以避免中间人攻击,减少Token被拦截的风险。
短时间的有效期:使JWT具有短暂的生命周期。这样,即使令牌被窃取,它也只能在有限的时间内被滥用。当令牌过期后,用户需要重新认证以获取新的令牌。
使用“注销”列表:虽然JWT是无状态的,但可以实施一个特定的列表来跟踪哪些令牌已被注销或已被视为无效,以在它们的有效期结束前阻止它们的使用。
不存储在本地存储中:避免将JWT存储在浏览器的localStorage或sessionStorage中,因为它们容易受到跨站脚本攻击(XSS)。考虑使用HttpOnly的cookie。
使用双Token策略:使用访问令牌和刷新令牌策略。访问令牌具有较短的有效期,而刷新令牌具有较长的有效期并用于在访问令牌过期后获取新的访问令牌。
使用双Token策略时,会涉及到两种令牌:访问令牌(Access Token)和刷新令牌(Refresh Token)。以下是其基本流程:
用户认证:当用户首次登录或注册时,他们会提交他们的凭证(如用户名和密码)。
颁发双令牌:验证用户凭证成功后,服务器会颁发一个短期的访问令牌和一个长期的刷新令牌。
客户端存储:客户端收到这两个令牌后,通常会将访问令牌存储在内存中,而将刷新令牌存储在更持久的位置,如HTTP Only的cookie中。
请求资源:当客户端需要请求受保护的资源时,它会在请求头中附带访问令牌。
验证访问令牌:服务器接收到请求后,会验证访问令牌的有效性和过期时间。
访问令牌过期:如果访问令牌已过期,客户端会使用刷新令牌请求一个新的访问令牌。
颁发新的访问令牌:服务器验证刷新令牌的有效性后,会颁发一个新的访问令牌给客户端。
刷新令牌过期或失效:如果刷新令牌也过期或被判定为无效(例如,由于用户注销或被迫下线),客户端必须要求用户重新进行身份验证。
双Token策略能够提高安全性的原因包括:
限制滥用时间窗口:由于访问令牌的有效期很短,即使它被窃取,恶意行为者只有有限的时间来滥用它。
分隔存储:访问令牌和刷新令牌的存储方式通常是分开的。例如,访问令牌可能存储在内存中,而刷新令牌存储在HTTP Only的cookie中,这使得XSS攻击更难同时获取两个令牌。
增强的撤销能力:如果系统检测到任何异常行为或需要强制用户注销,只需使刷新令牌无效即可。这样,即使访问令牌仍在其短暂的有效期内,也无法再使用它来获取新的访问令牌。
降低刷新令牌的暴露风险:由于访问令牌是最常使用的令牌(每次请求受保护的资源时),它比刷新令牌有更多的机会被暴露。双Token策略确保即使访问令牌被暴露,刷新令牌仍然安全,从而增加了一个额外的安全层。
综上所述,双Token策略通过增加额外的障碍和降低单点风险,提高了系统的安全性。
JWT通常使用exp
(过期时间)声明来定义令牌的生命周期。一旦JWT达到其过期时间,它就不能再被用于身份验证或授权,并且需要被替换:
自动刷新:在前端,当令牌过期时,可以使用刷新令牌自动获取新的访问令牌,而无需用户介入。
提示用户重新登录:当访问令牌和刷新令牌都过期时,您可以要求用户重新登录以重新开始会话。
状态性:传统的session管理是有状态的,服务器存储用户的session信息,客户端只保留一个sessionId。而JWT是无状态的,每次请求都包含所有必要的信息,服务器不需要存储任何用户状态。
存储:session信息通常存储在服务器上,这可能是内存、数据库、缓存等。JWT通常存储在客户端。
可扩展性:JWT在分布式系统中更易于扩展,因为它是无状态的。而session在分布式系统中可能需要额外的配置,例如使用Redis进行session共享。
大小和性能:session ID通常较短,因此在HTTP请求中占用的空间较小。而JWT可能会因为存储了多的声明而变得较大。但JWT省去了服务器查找session数据的步骤,可能会稍微提高性能。
安全性:JWT可以使用签名和加密来保证其完整性和安全性。但如前所述,它们可能被盗用,而session ID也可能被窃取。
在决定使用JWT还是session时,应根据应用的需求和特点进行权衡。
JWT用于信息交换的例子:
假设我们有两个系统A和B,它们需要进行安全的信息交换。比如,系统A需要将一些用户的信息传递给系统B。系统A可以生成一个JWT,其中的payload包含这些用户信息,并将JWT发送给系统B。系统B接收到JWT后,可以用系统A的公钥来验证JWT的签名,确认JWT确实是来自系统A,而且在传输过程中没有被篡改。然后,系统B就可以解析JWT的payload来获取用户的信息。
问题:JWT是无状态的,服务器不存储任何关于JWT的信息。因此,一旦JWT被生成,就无法从服务器端控制它的生命周期。如果在JWT中设置了过期时间,并且我们想在没有让用户重新登录的情况下更新该过期时间,那么这就变得有些棘手。
解决方案:一种常见的做法是在用户请求时,检查JWT的过期时间,如果即将过期,服务器会在响应头部中添加一个新的JWT,客户端在接收到新的JWT后,替换掉旧的JWT。这样,只要用户持续活跃,JWT就可以一直被刷新。
Spring Security并没有直接提供JWT的刷新机制。给JWT续期一般需要我们自行实现。一种常见的做法是在用户请求时,检查JWT的过期时间,如果即将过期,生成一个新的JWT返回给客户端。例如,我们可以通过Spring Security的过滤器来实现这个功能,当接收到用户的每个请求时,都检查JWT是否即将过期,如果是,则生成新的JWT并在响应中返回给客户端。这就需要我们在设计系统时,合理地设置JWT的有效期,并合理地设计刷新机制,以达到既能保证系统的安全性,又能提高用户体验的效果。
传输过程中,始终使用HTTPS,防止被拦截。
尽可能地减少JWT的生命周期,比如设置为15分钟,这样即使被盗取,攻击者能使用的时间也有限。
存储JWT时,不要存储在localStorage或sessionStorage中,因为这些存储容易被XSS攻击。可以使用httpOnly的cookie来存储JWT,这样javascript就无法访问到JWT,能有效防止XSS攻击。
JWT的payload部分只存储必要的信息,不要存储敏感数据,因为这部分可以被任何人解码查看。
JWT的过期时间通常在payload中由"exp"字段指定。当服务器收到一个JWT时,会查看"exp"字段来判断这个JWT是否过期。如果过期了,服务器就会拒绝这个请求,并要求客户端重新获取JWT。客户端重新获取JWT通常需要用户重新登录,或者在JWT即将过期之前,使用旧的JWT来获取一个新的JWT。
另一个办法就是服务端每次收到一个jwt就会进行检测是否快要过期,如果快要过期就去授权中心获取一个新的jwt并且返回给客户端,客户端替换旧的jwt并且重发请求即可。
1 session是存储在服务器上的,每次用户发出请求时,都会发送一个session ID,服务器用这个ID来查找对应的session。因此,session是状态的,需要服务器存储每个用户的状态信息。
JWT是存储在客户端的,服务器不存储任何关于JWT的信息。因此,JWT是无状态的,能减少服务器的存储负担。每次用户发出请求时,都会发送JWT,服务器用这个JWT来验证用户的身份并提取出必要的信息。
2 在分布式系统中的表现
JWT与分布式系统:你是对的,JWT 在分布式系统中非常有用。因为 JWT 是无状态的,并将认证信息存储在令牌本身,不需要服务器去维护或验证用户的 session。这种方式在微服务架构和负载均衡等环境中非常方便,因为不同的服务器可以处理同一个用户的不同请求,而无需共享用户的 session 数据。
Session和分布式系统:你正确地指出了,如果你要在分布式系统中使用基于 session 的认证,你需要一个共享的存储来保存所有的 session 数据,以确保每个服务器都可以访问。常见的解决方案是使用像 Redis 这样的内存数据存储系统来保存 session 数据。这增加了系统的复杂性,但也使你能够利用 session 的优点,比如服务器端控制的 session 生命周期和更丰富的授权选项。
3 Session被盗用的风险:确实,如果攻击者盗取了用户的 session ID,他们就可以冒充该用户发出请求。然而,攻击者无法直接访问到存储在服务器端 session 中的信息。需要注意的是,这个风险也适用于 JWT,如果攻击者盗取了 JWT,他们也可以冒充用户发送请求。不过,由于 JWT 是自包含的,攻击者还可以查看 JWT 的内容,所以不能在 JWT 中存储敏感信息。
过程如下:
用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。
注意:Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session;
Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。
Cookie和Session都是客户端与服务器之间保持状态的解决方案 :
1,存储的位置不同,cookie:存放在客户端,session:存放在服务端。Session存储的数据比较安全
2,存储的数据类型不同 两者都是key-value的结构,但针对value的类型是有差异的 cookie:value只能是字符串类型,session:value是Object类型
3,存储的数据大小限制不同 cookie:大小受浏览器的限制,很多是是4K的大小, session:理论上受当前内存的限制,
4,生命周期的控制 cookie的生命周期当浏览器关闭的时候,就消亡了 (1)cookie的生命周期是累计的,从创建时,就开始计时,20分钟后,cookie生命周期结束, (2)session的生命周期是间隔的,从创建时,开始计时如在20分钟,没有访问session,那么session生命周期被销毁
在你的描述中,“cookie的生命周期是累计的”这句话指的是cookie的生命周期是从它被创建的那一刻开始算起,不论在此期间用户是否有互动活动,到达设定的生命周期(比如你例子中的20分钟)后,cookie就会自动过期。
相反,“session的生命周期是间隔的”则是指,session的生命周期是从它被创建开始算起,但它依赖于用户的活动。如果用户在某个时间间隔内(比如20分钟)没有任何互动活动,session就会被销毁,无论这个时间间隔出现在何时。这也就是说,如果用户在20分钟内有任何互动活动,这个计时器就会被重置,从新的活动开始重新计时。
在很多系统中,会有这样的设置来避免长时间的无操作导致的资源占用,也可以在一定程度上提升系统的安全性,比如防止账号在公共场合被未经授权的人访问。
"累计"和"间隔"在这里是描述两种不同生命周期处理方式的术语。这两个词可以帮助我们更好地理解和区分Cookie和Session的生命周期。
“累计”的含义在于,无论用户是否进行交互,Cookie在被创建时就开始计时,一旦到达其预设的生命周期时长(例如20分钟),Cookie就会过期。
而“间隔”的含义在于,Session的计时机制并不是连续的。而是依赖于用户的活动状态。如果用户在一段时间内(如20分钟)没有任何交互,Session就会过期。但如果用户在这段时间内有交互行为,Session的计时就会重新开始,也就是说计时器的“间隔”会被重置。
这两个特征名词被选用,是因为它们能够很好地概括和表述Cookie和Session的生命周期管理方式。这些术语可以帮助开发者更好地理解和设计他们的网络应用。
Session:Session是在服务器上存储的数据结构,每个用户都会有一个唯一的Session,并且服务器会存储这个Session的ID。当用户访问网站时,服务器会检查用户请求的Session ID,以确定用户的身份。Session的生命周期一般依赖于用户的活动,如果用户在某段时间内没有活动,Session就会过期。
Cookie:Cookie是在用户浏览器上存储的一小段文本数据。它通常用于保存用户的一些偏好设置,或者用于在用户的不同请求之间保持状态(例如登录状态)。当用户访问网站时,浏览器会将Cookie发送到服务器。Cookie的生命周期可以由服务器设置,并且在过期前,它们会一直存储在用户的浏览器上。
JWT (Json Web Token):JWT是一种在两个系统之间安全传输信息的方式。它是一个包含了一些数据的加密的JSON对象,可以被用作无状态的认证方式。这意味着服务器不需要存储任何用户状态信息,因为所有必要的信息都在JWT中。服务器只需要验证JWT是否有效即可。
这三者可以配合使用。例如,Cookie可以被用来存储JWT,以便在用户的不同请求之间保持状态。同时,Session也可以存储JWT或其他用户信息。
在现代的大规模高并发场景下,JWT可能会被更多地使用。这是因为JWT是无状态的,这意味着服务器不需要为每个用户维护一个Session,这可以大大减少服务器的存储需求,并使得系统更易于扩展。此外,JWT也可以很容易地在不同的服务器之间共享,这使得它非常适合用在微服务架构中。然而,这并不意味着Cookie和Session没有被使用,实际上,这三种技术在许多应用中都可以看到,具体使用哪种技术取决于应用的需求和环境。
传统的Session认证方式依赖于Cookie,但Cookie有一些限制,比如跨域问题。不同的域名之间是不能共享Cookie的(除非在服务端设置规避这个同源策略),这限制了Session的使用场景。
JWT通过将用户信息编码到Token中,并让客户端保存并发送这个Token,来解决这个问题。因为Token只是一个字符串,可以通过任何方式发送(例如,通过HTTP头、请求参数等),所以它不受同源策略的限制,可以轻松实现跨域验证。
首先,为了理解这个问题,我们需要区分两个重要的概念:跨站请求伪造(Cross-Site Request Forgery,CSRF)和跨来源资源共享(Cross-Origin Resource Sharing,CORS)。这两者都与跨域问题有关,但它们解决的是不同的问题。
CSRF:这是一种攻击方法,其中恶意网站引导受害者执行不是其意图的动作,而这个动作是在受害者在其他网站中认证过之后才可能执行的。由于浏览器会自动携带cookie发起请求,因此攻击者可以利用这一点来发起伪造的请求。为了防御CSRF,常用的方法是使用CSRF令牌,而不是依赖cookies alone。
CORS:这是一个由浏览器实施的安全特性,它允许服务器声明哪些来源(origin)可以访问其资源。Access-Control-Allow-Origin
就是CORS中的一个HTTP头部,它定义了哪些外部源可以访问资源。
回到你的问题:
Cookie受到的跨域限制:默认情况下,cookie只能被设置到创建它的域名。例如,example.com
不能设置一个cookie给another-site.com
,反之亦然。此外,跨域的AJAX请求默认不会发送cookie。
Access-Control-Allow-Origin
和其他CORS头部:这些头部允许服务器告诉浏览器哪些跨域请求是被接受的。例如,如果你希望从example.com
上发送一个请求到api.example.com
并希望cookie被发送,那么api.example.com
应该包含一个响应头:Access-Control-Allow-Credentials: true
。此外,请求的源站(example.com
)应该被列在Access-Control-Allow-Origin
头部中。同时,发起请求的代码中,应当设置withCredentials
为true
来确保cookies在请求中被发送。
总之,Access-Control-Allow-Origin
和其他相关的CORS头部确实可以帮助规避cookie的跨域限制,但是需要确保服务器和客户端都被正确地配置,以便安全地实现跨域共享。
单点登录(SSO)是指在多个应用中,用户只需要登录一次就能访问所有的应用。JWT可以轻松实现这个功能,因为一旦用户在一个应用中登录并获得JWT,他就可以使用这个JWT访问该公司下所有的应用,只要这些应用都接受这个JWT即可。只需要在所有的应用之间共享一个JWT的密钥,就可以在所有应用中实现单点登录。
是的,单点登录(SSO)的概念可以扩展到微服务架构中。在一个由多个微服务构成的系统中,每个微服务可以被看作是一个独立的"应用"。通过使用单点登录,用户可以只登录一次,然后访问这个系统中的所有微服务。这样不仅提高了用户体验,还能减轻服务端管理多个会话的复杂性。
如果希望限制用户只能在一个设备上登录,可以在JWT的payload部分添加一些额外的信息,比如设备ID。当用户尝试在一个新的设备上登录时,服务器可以检查新的JWT中的设备ID是否与存储在服务器上的设备ID匹配。如果不匹配,服务器就可以拒绝这个新的登录请求。这样,就可以防止用户在不同的设备上重复登录。需要注意的是,这种方法需要服务器存储用户的设备ID,这可能会导致服务器的负担加重。