登录功能的设计-JWT的各种使用
一、JWT的前世今生
1.1 为什么要用jwt,前后端每次只传递一个json不就行了吗?
首先想到的一个问题————为什么要用jwt,前后端每次只传递一个json不就行了吗?
产生这个想法的原因是因为,后端用私钥生成的jwt,前端用公钥也能解析其中携带信息的部分,因此第一个想法就是,既然jwt携带的信息任何人拿到都可以解析,那为什么不直接传递一个json呢?每次在服务端把用户的json存着,用户每次请求时对比一下,就行了,这样也能做到多设备登录数量的控制。没错,这个想法,就是之前的session机制。
1.2 Session机制的原理与问题
原理
在Session机制中,服务器会为每个客户端创建一个会话(Session),并在后端记录会话ID,并也将会话ID存储在客户端的Cookie中。当客户端发送请求时,服务器会根据请求中的Session ID来识别当前用户的身份。Session机制怎么做到多设备登录的控制的呢?要实现多设备登录的控制,可以在服务器端维护一个记录用户登录状态的数据结构,例如一个Map,将用户ID和Session ID进行映射。当用户在另一个设备上登录时,可以将该设备上的Session ID更新到服务器端的数据结构中,这样在下次请求时,服务器就可以根据新的Session ID识别出当前用户的身份,并更新记录用户状态的数据结构。同时,为了防止Session ID被盗取或者篡改,可以对Session ID进行加密,或者设置其有效期限,以增强其安全性。如果发现某个Session ID存在安全风险,可以将其标记为无效,并要求客户端重新登录,从而保护用户的账号安全。
问题1:安全性不足
但是session机制存在一个严重的问题需要解决,Session机制不能完全防止伪造和篡改。由于Session是在服务器端维护的状态,客户端需要在每个请求中使用Session ID来标识自己的身份,因此如果Session ID被恶意获取或者篡改,则攻击者可以利用这个漏洞进行伪造和篡改。
为了解决这个问题,Web应用程序通常会采取一些额外的措施来增强Session的安全性。例如:
- 使用HTTPS协议来保护传输过程中的数据安全性。
- 采用一些防止Session ID被盗取或者篡改的措施,例如使用Cookie的HttpOnly和Secure属性,设置Session ID的有效期限等。
- 在服务器端对Session ID进行加密或者使用其他技术来增强其安全性,例如Token-based身份验证机制等。
综上所述,Session机制本身并不足以保证Web应用程序的安全性,需要配合其他措施来加强其安全性。而JWT机制则通过对负载进行签名来保证其完整性和真实性,从而更好地解决了伪造和篡改等安全问题。
问题2:非无状态性
由于session需要在服务器对session的状态进行保存,我们认为session机制是有状态的。因为服务器需要在每个客户端与服务器之间维护一个会话状态,用于跟踪用户的登录状态、权限等信息。这种状态是由服务器存储在内存或者磁盘上的,并在客户端与服务器之间进行交互时进行维护和更新。
具体来说,在使用Session机制时,当用户登录后,服务器会为该用户创建一个会话,并分配一个唯一的Session ID。在随后的请求中,客户端需要在每个请求的HTTP头或者URL参数中携带该Session ID,以便服务器能够识别用户身份并提供相应的服务。因此,Session机制需要服务器维护用户的登录状态和Session信息,这使得它成为一种有状态的身份验证机制。
有状态的非Rest风格接口的缺点:
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,无法进行水平扩展
- 客户端请求依赖服务端,多次请求必须访问同一台服务器
而我们需要一种无状态性的机制。定义如下。
在计算机科学中,无状态(stateless)指的是一种系统或者应用程序的设计模式,其中每个请求都是独立的,不需要在服务器端存储任何会话信息。在这种设计模式下,服务器不会维护任何会话状态,而是根据每个请求中携带的信息来处理请求并返回响应。
无状态的概念通常用于描述Web应用程序中的身份验证机制。传统的身份验证机制,如Session机制,需要服务器在内存或磁盘中存储会话状态,以便在用户请求时识别其身份和权限。而无状态的身份验证机制,如JWT(JSON Web Token),则不需要在服务器端存储会话信息,而是将用户的身份信息和权限等元数据嵌入到Token中,在每个请求中将该Token发送给服务器进行验证。
无状态的设计模式具有以下优点:
- 可扩展性:由于服务器不需要维护任何会话状态,因此可以更容易地实现水平扩展。
- 性能:由于服务器不需要在每个请求中读取和写入会话状态,因此可以提高系统的性能。
- 可靠性:由于无状态的设计模式可以使系统更加简单,因此可以降低系统出错的概率,提高系统的可靠性。
总之,无状态是一种在计算机科学中广泛使用的设计模式,它可以提高系统的可扩展性、性能和可靠性。
总结,选择jwt而不是session机制的理由如下:
使用JWT而不使用Session的最大理由是其无状态性,以及更好的可扩展性、性能和安全性。
1.3 jwt的结构
JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519),可以在不需要服务器端存储会话信息的情况下实现跨域认证。简单来说,jwt通过自身的结构,既可以保证携带的信息能被前端顺利获取,也能根据结构的验证机制,检测当前jwt是否被篡改。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
1.头部(Header)
JWT头部的作用是告诉接收方JWT的类型和签名算法,从而帮助接收方正确地解析JWT。JWT头部通常会被Base64编码后嵌入到JWT Token的第一部分中,与载荷部分和签名部分用点号(.)分隔开来,组成完整的JWT Token。
JWT头部使用JSON格式表示,通常包含两个属性:alg和typ。其中,alg属性表示所使用的签名算法,常用的有HS256(HMAC-SHA256)、RS256(RSA-SHA256)等;typ属性表示令牌的类型,通常为JWT。例如:
code{
"alg": "HS256",
"typ": "JWT"
}
以上代码是一个JWT头部的示例。它指定了哈希算法为HMAC SHA256,令牌类型为JWT。
JWT头部的格式不是固定的,它可以包含任意数量的自定义属性,以便扩展JWT的功能。例如,可以在JWT头部中添加kid属性来指定使用哪个密钥进行签名,或者添加cty属性来指定JWT Token的类型。
当接收方收到JWT Token时,它会解码JWT头部并使用其中的算法和类型信息来验证JWT Token的有效性。如果头部中指定的算法与接收方所支持的算法不一致,或者类型不正确,则接收方可能会拒绝该JWT Token。因此,正确配置JWT头部非常重要,它可以确保JWT Token被正确地解析和验证。
2.载荷(Payload)
JWT的载荷(Payload)部分包含了有关身份验证和授权的信息,例如用户ID、用户名、权限等元数据。JWT的载荷部分使用JSON格式表示,可以包含任意数量的自定义属性,以便扩展JWT的功能,但建议不要在载荷中存储敏感数据,如密码等。
JWT的载荷部分也是Base64编码的,Header和Payload都是Base64编码的字符串,它们之间由一个点号(.)连接,构成了JWT的第二个部分。
例如:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
以上代码是一个JWT负载的示例。它包含了一个主题(sub)声明,一个名称(name)声明和一个发行时间(iat)声明。
通常,JWT的载荷部分包含以下三种类型的属性:
-
Registered Claims/标准字段:这是一组预定义的属性,包含了一些标准的元数据,如iss(Issuer,发布者)、exp(Expiration Time,过期时间)、sub(Subject,主题)等。这些属性具有预定义的含义,可以帮助接收方更好地理解JWT Token的内容。
标准字段是JWT规范中定义的字段,包括:
- iss:Issuer,表示JWT的签发者
- sub:Subject,表示JWT所面向的用户
- aud:Audience,表示接收JWT的一方
- exp:Expiration time,表示JWT的过期时间
- nbf:Not before,表示JWT的生效时间
- iat:Issued at,表示JWT的签发时间
- jti:JWT ID,表示JWT的唯一标识符
-
Public Claims/公共字段:这是一组公共的自定义属性,可以被任何人使用,用于在JWT Token中包含一些公共的元数据。
公共字段是用户定义的字段,用于存储一些通用的数据,比如用户ID、用户名等等,不同的系统可以使用相同的公共字段来实现数据的共享和交互。例如:
- userId:用户ID,用于标识JWT所属的用户。
- userName:用户名,用于标识JWT所属的用户的用户名。
- roles:用户角色,用于标识JWT所属的用户的角色信息。
- email:用户邮箱,用于标识JWT所属的用户的邮箱地址。
- avatar:用户头像,用于标识JWT所属的用户的头像图片地址。
-
Private Claims/私有字段:这是一组私有的自定义属性,仅供特定的应用程序或组织使用,用于在JWT Token中包含一些私有的元数据。例如
- permissions:用户权限,用于标识JWT所属的用户拥有的权限信息。
- settings:用户设置,用于标识JWT所属的用户的系统设置信息。
- rememberMe:记住我,用于标识JWT是否为“记住我”登录时生成的。
- loginType:登录方式,用于标识JWT所属的用户的登录方式,如手机号码登录、邮箱登录等等。
- deviceInfo:设备信息,用于标识JWT所属的用户登录的设备信息,如设备型号、操作系统版本等等。
对于前端来说,是不需要区分JWT载荷中的公共字段、私有字段和标准字段之间的区别的。在使用JWT时,前端只需要知道JWT的三部分构成(头部、载荷和签名),以及如何从JWT中提取需要的信息即可。在解析JWT时,前端只需要对整个载荷进行解码,然后根据业务需求提取需要的信息即可。至于这些信息是标准字段、公共字段还是私有字段,并不影响前端的解析和使用。
3.签名(Signature)
JWT的签名是用于验证JWT的完整性和真实性的,确保JWT在传输过程中没有被篡改或伪造。签名由Header、Payload、密钥和加密算法生成,具体生成方式取决于所采用的加密算法。一般而言,可以通过HS256、HS384、HS512等对称加密算法或RS256、RS384、RS512等非对称加密算法生成JWT签名。
生成方式上,signature 是签证信息,该签证信息是通过header和payload,加上secret(密钥),通过算法加密生成。
公式 ;
先使用header中定义的加密算法,将header和payload进行加密,并使用点进行连接。如:加密后的head.加密后的payload。再使用相同的加密算法,对加密后的数据和签名信息进行加密。得到最终结果。
签名通常是使用Base64进行编码的,并与头部和负载使用句点连接而成。
例如:
signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
以上代码是一个使用HMAC SHA256算法进行签名的JWT签名示例。其中,secret是用于计算哈希值的私钥。
前端通常不需要验证JWT签名(解析都可以解析,因为是Base64编码。但验证不了,因为没有私钥),因为验证签名的过程通常由后端完成。后端可以使用相同的密钥和加密算法对JWT的Header和Payload部分进行签名验证,以确保JWT的完整性和真实性。如果签名验证通过,那么就可以信任JWT中包含的信息并允许用户进行相应的操作。如果签名验证不通过,则应该拒绝JWT并通知相关人员进行处理。
4.总结
完整的jwt字符串由三个Base64编码的部分组成(头部,载荷,签名),它们之间都用点号(.)连接,形如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第一个部分是Base64编码的Header,第二个部分是Base64编码的Payload,第三个部分是Signature。后端在JWT的验证过程中,需要对Header和Payload进行解码,然后通过密钥和加密算法计算Signature,最后将计算出的Signature与JWT中的Signature进行比较,以确定JWT的完整性和真实性。
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const payload = JSON.parse(atob(token.split('.')[1]));
console.log(payload);
// 输出:{ sub: '1234567890', name: 'John Doe', iat: 1516239022 }
总体来说,JWT的工作原理如下:
- 用户在进行身份验证或授权时,将其凭据发送给服务器。
- 服务器使用密钥对凭据进行哈希计算,生成JWT。
- 服务器将JWT发送回客户端。
- 客户端将JWT存储在本地,并在每个请求中将其作为授权标头发送回服务器。
- 服务器使用密钥对接收到的JWT进行哈希计算,并验证其完整性和身份验证信息。
- 如果JWT有效,则服务器响应请求。
由于JWT在负载中包含有关用户的信息,因此它可以在分布式系统中实现跨域身份验证,而无需进行每个请求的数据库或会话存储。
二、单/多设备登录/并发登录
2.1并发登录背景
需求点
单设备登录,多设备登录,需求点主要在于如何控制同时登录的设备数量,即同一时间,能有多少个账号能同时在线。
2.2 两种实现方式
实现方式:
-
方式1( 纯jwt ):如果每次只验证是否用户账号存于数据库,只返回一个jwt的登录设计,我认为是最基础的,应该称为非控制性登录(没有控制登录数量,相同账号登录多少个都行)
- 1.账号密码验证通过后,生成JWT,其中载荷中包括了
user用户信息+expire过期时间 - 2.把生成的JWT返回给前端即可,前端每次请求时都需要带上这个jwt token
- 3.如果后端解析JWT时发现过期,就让本次请求无效。
- 1.账号密码验证通过后,生成JWT,其中载荷中包括了
-
方式2(JWT+REDIS):如果要控制登录的数量,就要涉及到把登录的token存在缓存数据库中,同一个账号下可以有N个登录的token,但是每超过N个的token应当被认为是不合法的。因此这种key-value的链表式数据结构是可以存在redis里的,例如key是用户id,value是tokens的链表。
- 在用户登录成功后,服务器生成一个JWT,其中包含用户信息以及过期时间等。
- 服务器将JWT发送给客户端,客户端将JWT保存在本地(通常是在浏览器的cookie或localStorage中保存),并在后续的请求中将JWT作为Authorization头部或请求参数发送给服务器。
- 在服务器端,需要将JWT的有效性和真实性进行验证,包括检查JWT的签名是否合法,是否过期等。如果验证成功,则认为用户已经登录成功,并可以根据JWT中包含的信息进行相应的操作。
- 为了控制多设备登录数量,服务器还需要将每个用户id与每个用户id的JWT保存在缓存数据库中(数据结构为)。在每次请求中,服务器需要从缓存数据库中获取当前用户的所有JWT,然后根据设备标识判断当前请求是否合法。
- 如果当前请求是已登录的设备,则直接处理请求;否则,需要根据当前的登录设备数量和允许登录设备数量进行判断,如果已经达到最大登录设备数量,则拒绝登录请求,否则允许登录,并将新的JWT和用户id到缓存数据库中。
- 当用户登出时,需要将该用户在缓存数据库中的所有JWT都删除,以确保不能再次使用已失效的JWT进行登录。
需要注意的是,为了确保缓存数据库中的数据与JWT的一致性,需要在每次登录或登出时更新缓存数据库中的数据。同时,为了避免缓存数据库中的数据过期或出现异常情况,需要采用相应的策略进行缓存清理和异常处理。
2.3 并发登录数据一致性
在并发登录场景中,数据一致性的主要问题是用户的登录状态。如果多个并发登录请求同时更新了用户的登录状态,可能会导致数据不一致的情况。为了保证数据一致性,可以采用以下几种方式:
- 使用数据库事务:将登录操作放入数据库事务中,并设置适当的隔离级别,可以避免因为并发操作导致数据的不一致性。
- 使用Redis分布式锁:使用Redis等分布式存储系统提供的分布式锁来保证同一时刻只有一个登录请求能够成功获取锁并进行登录操作,其他登录请求需要等待锁的释放,避免并发更新导致的数据不一致。
- 使用强一致性方案:使用类似Zookeeper等分布式协调服务来实现强一致性方案,保证同一时刻只有一个登录请求能够成功更新用户登录状态,从而避免并发更新导致的数据不一致。
- 登录函数加锁。为了避免并发登录导致的数据不一致问题,可以对登录函数加锁来实现并发控制。具体可以使用线程锁(thread lock)或者进程锁(process lock)来实现,确保同一时间只有一个登录请求能够成功执行。
三、单点登录/统一单点登录/认证中心
3.1 单点登录的背景
在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。
但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员
来说,很不方便。于是,就想到是不是可以在一个系统登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。
单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
如图所示,图中有4个系统,分别是Application1、Application2、Application3、和SSO。Application1、Application2、Application3没有登录模块,而SSO只有登录模块,没有其他的业务模块,当Application1、Application2、Application3需要登录时,将跳到SSO系统,SSO系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。
大致流程如下:
总之你只要理解为:只要只有一个认证中心,就可以叫单点登录
3.2 统一登录与单点登录概念区分
定义区别
「单点登录」指用户只需输入一次账密,在一处完成登录,之后可以直接进入所有业务系统。想要完成单点登录的效果,必须有一个唯一身份源,其他业务系统必须配合完成改造和对接。
而「统一登录」,并不能算是个专业的名词,关于它与「单点登录」是否是一个概念,总之你只要理解为:只要只有一个认证中心,就可以叫单点登录
如果硬要给「统一登录」一个定义,我觉得应该理解为多平台登录,即可以使用同一个账户,登录多个不同的站点。其和「单点登录一样」,都是通过可信的第三方进行认证, 从第三方平台登录到其他站点,实现统一登录的效果。
应用场景区别
SSO 大部分是整合同一企业系统的解决方案,在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。例如,企业中可能有多个处理多个业务的应用系统邮箱、OA等),用户只要保持登录到一个系统,就可以无缝进入其他内部系统。
多平台登录常用于多[合作企业]账号的互通,社交账号与支付账号互通等,支持多平台登录。例如,淘宝支持微信、微博、QQ等多账户登录。使用户购买、预览行为的体验更友好,同时起到客户引流、营销跟踪效果。
通俗来讲:对于淘宝和天猫来说,这两个之间是单点登录,而使用支付宝或者微博登录淘宝时候就是统一登录。
统一身份认证呢?就是他们里面认证的地方,但又不止于此。
从微服务层面上来来说:淘宝的浏览部分、用户部分、支付部分等等甚至可能更细,这些内容只需要登录一次即可,这个登录就叫做单点登录。
-
统一登录跟单点登录相同点:
因为我们说各个模块之间只需要登录一次叫单点登录;
各个应用之间只需要登录一次(要授权,扫码等)叫统一登录。
-
不同点:
就是单点登录是一个组织下的模块或者叫一个公司下的的各个服务;
而统一登录就是更大范围的,各个公司之间那种的,比如支付宝、钉钉、facebook、twitter巴拉巴拉阿巴巴。
3.3 JWT+REDIS实现单点登录的三种方式
方式1:纯JWT方案
流程
- 用户去认证中心登录,认证中心生成jwt,返回给客户端。
- 客户端携带jwt请求多个系统
- 每个系统各自解析jwt,取出用户信息。能解析成功就说明jwt有效,继续处理自己的业务逻辑。
(所有系统的jwt密钥保持一致才能各自解析成功)
优缺点
优点:认证流程简单,服务端处理速度快。
缺点:因为简单,所以不安全。jwt一旦下发,有效期内就无法使其主动失效。
一句话总结:认证中心创建Jwt,其他系统解析。
方式2:Jwt + 认证中心Redis
流程:
- 用户去认证中心登录,认证中心生成jwt,保存到redis并返回给客户端。
- 客户端携带jwt去多个系统认证
- 每个系统只负责从请求中取出jwt, 传给认证中心。认证解析用户信息,
并与redis中的jwt校验,判断是否有效。然后返回用户信息给刚才发起验证请求的系统。
优缺点:
优点:安全性高,服务端能控制jwt主动失效。
缺点:每次请求需要认证的接口,都需要访问认证中心,耗时略长。
一句话终结:认证中心负责Jwt的创建与解析,其他系统不参与认证逻辑。
方式3:Jwt + 认证中心Redis + 多系统Redis
流程:
1.用户去认证中心登录,认证中心生成jwt,保存到redis并返回给客户端。
2.客户端携带jwt去多个系统认证
3.多系统(比如系统A)收到jwt,A解析并取出用户信息,先判断自己的A的redis中有没有jwt。
3.1 如果有,就合法,a系统可以继续执行业务逻辑。
3.2 如果没有就拿着jwt去认证中心验证。
3.2.1 如果通过,a系统就把这个jwt保存到自己的redis,并设置对应的失效时间。
下次这个jwt再来到a的时候,就不需要去认证中心校验了。
3.2.2 如果验证不通过此次请求就不合法,告诉客户端需要跳转登录页面,
去认证中心登录,返回步骤1。
优缺点:
优点:安全性高,平均认证过程较快。
缺点:服务端流程复杂,需要考虑jwt的同步问题。比如注销或重新登录后,认证中心删除旧jwt需要同步给其他系统,其他系统删除自己保存的jwt。
一句话总结:认证中心创建jwt,其他系统解析并校验,需要保持jwt同步。
总结:
- 方案1才是Jwt的本质。
- 方案2是基于Jwt的改进,用作我们公司新项目的登录系统。
- 方案3准确说是单点登录系统的标准流程,只是结合了Jwt。其他2种方案都是伪单点。
3.4 单点登录协议模型
OAuth2 授权协议模型
概述
OAuth2是一种授权协议,用于在应用程序之间共享用户资源的安全方式。它允许用户授权第三方应用程序访问他们存储在另一个服务上的资源,而不需要将用户名和密码提供给第三方应用程序。OAuth2通过使用访问令牌(access token)来授权第三方应用程序访问用户资源。
OAuth2协议中有以下几个主要角色:
- 资源所有者(Resource Owner):指的是拥有受保护资源的用户。
- 客户端(Client):指的是访问受保护资源的应用程序。
- 授权服务器(Authorization Server):指的是管理资源所有者授权的服务器。
- 资源服务器(Resource Server):指的是存储受保护资源的服务器。
OAuth2协议的授权过程通常分为以下几个步骤:
OAuth2协议的授权流程如下:
- 客户端向授权服务器注册,返回客户端ID与客户端密钥(密钥可以理解为一种加密了的密码)。
- 客户端向授权服务器发送授权请求。请求包括客户端ID、客户端密钥、授权范围等信息。
- 授权服务器验证客户端的身份,并要求用户进行身份验证。如果用户没有登录,授权服务器会引导用户进行登录。
- 用户同意授权。如果用户同意授权,授权服务器会向客户端发送授权码。
- 客户端使用授权码向授权服务器请求访问令牌。
- 授权服务器验证授权码的有效性,并向客户端发送访问令牌。
- 客户端使用访问令牌向资源服务器请求受保护的资源。
- 资源服务器验证访问令牌的有效性,并向客户端发送受保护的资源。(资源服务器是把令牌给认证服务器来验证令牌的合法性)
需要注意的是,OAuth2协议并不限制授权服务器和资源服务器必须在同一台服务器上,它们可以是不同的服务器,甚至可以由不同的组织管理。这样可以增加应用程序之间的互操作性,并允许资源所有者对第三方应用程序的授权进行更好的控制。
四种授权方式/四种令牌类型
OAuth2协议中的四种授权方式分别是:
-
授权码模式(Authorization Code Grant):适用于客户端有后端服务器的情况,用于获取访问令牌。客户端发起授权请求,用户授权,服务器返回授权码,客户端用授权码换取访问令牌。 场景:Web应用程序和后端服务器之间的授权。
为什么需要授权码模式,直接返回令牌不就行了?
答:
授权码模式主要用于解决安全性问题,以防止令牌被窃取或滥用。
直接返回令牌可能会存在一些潜在的安全风险。例如,如果令牌在传输过程中被截获,攻击者可以使用该令牌来访问受保护的资源。而授权码模式通过将令牌的生成和传输分开,可以有效地防止这种情况的发生。在授权码模式下,客户端只能通过授权码来获取令牌,而授权码只能在授权服务器和客户端之间的安全通道上进行传输,这大大增加了令牌被窃取的难度。
此外,授权码模式还可以帮助授权服务器更好地管理和监控授权的过程。例如,可以对授权码进行有效期限制,可以对授权码进行审计等。
授权码模式的授权流程如下:
- 用户访问客户端应用程序并选择使用第三方登录;
- 客户端应用程序将用户重定向到授权服务器,包括客户端ID、请求的权限范围、重定向URI和响应类型等信息;
- 用户在授权服务器上进行身份验证,并授权给客户端应用程序访问其受保护的资源;
- 授权服务器返回一个授权码给客户端应用程序;
- 客户端应用程序使用授权码向授权服务器请求访问令牌;
- 授权服务器验证授权码的有效性并向客户端应用程序颁发访问令牌。
- 客户端使用访问令牌向资源服务器请求访问受保护的资源。
- 资源服务器验证访问令牌的有效性,如果验证通过,则向客户端提供受保护的资源。
-
简化模式(Implicit Grant):适用于客户端没有后端服务器的情况,用于直接获取访问令牌。客户端发起授权请求,用户授权,服务器直接返回访问令牌。 场景:单页面应用程序,客户端直接与API交互。
- 用户访问客户端网站并发起授权请求,将响应类型设置为“token”。
- 认证服务器要求用户登录并授权请求,然后将访问令牌作为片段参数返回到客户端网站的重定向URI中。
- 客户端网站从URI中提取令牌。
- 客户端网站可以使用令牌访问受保护的资源。
-
密码模式(Resource Owner Password Credentials Grant):适用于客户端有高度信任级别且只能由用户直接授权的情况,客户端使用用户名和密码获取访问令牌。 场景:仅限于受信任的应用程序,如第一方应用程序。
在密码模式中,客户端应用程序需要获取终端用户的用户名和密码,并将其作为证书,与OAuth2.0服务器进行交互,以获取访问令牌。
- 客户端向授权服务器发送请求,包含客户端ID和客户端秘钥以及授权服务器支持的授权类型。
- 授权服务器验证客户端ID和秘钥是否合法,并检查请求中是否包含密码模式授权类型。
- 如果请求合法,授权服务器返回访问令牌(access token)和刷新令牌(refresh token)。
- 客户端使用访问令牌向资源服务器请求资源。
- 资源服务器验证访问令牌是否有效,如果有效,则返回请求的资源。
- 如果访问令牌已过期,客户端可以使用刷新令牌向授权服务器请求一个新的访问令牌。
- 授权服务器验证刷新令牌的有效性,并返回新的访问令牌和新的刷新令牌。
- 客户端使用新的访问令牌向资源服务器请求资源。
- 资源服务器验证访问令牌是否有效,如果有效,则返回请求的资源。
- 如果刷新令牌也过期了,客户端需要重新向授权服务器请求授权并获取新的访问令牌和刷新令牌。
-
客户端模式(Client Credentials Grant):适用于客户端需要使用自己的凭据获取访问令牌的情况,没有用户参与授权流程,只有客户端凭据和客户端ID。 场景:用于机器对机器的交互,如API与后台服务之间的通信。
这些授权方式允许在不暴露用户凭证的情况下,授权第三方应用程序访问特定的资源或操作。每个授权方式的使用场景不同,开发人员需要根据具体情况进行选择。
OAuth2协议中四种授权方式对应了常见的四种令牌类型及其作用:
-
授权码(Authorization Code):是OAuth2协议中最常见的一种令牌类型,用于在客户端和服务端之间进行授权码交换。客户端使用授权码来获取访问令牌(Access Token),并使用访问令牌来访问受保护资源。一般情况下,授权码只能使用一次,有效期较短。
注册时得到密钥与client_id;登录时带着密钥与client_id,返回授权码。通过授权码再次请求获取密钥。适用于第三方应用程序
-
隐藏式(Implicit):相比授权码模式,隐藏式模式省略了授权码的获取过程,直接将访问令牌返回给客户端。由于不需要授权码交换,隐藏式模式适用于无法安全保存客户端秘钥的移动端应用。但由于直接返回访问令牌存在一定风险,隐藏式模式下的访问令牌有效期比较短。
注册时不返回密钥只返回client_id;登录时带着client_id,返回token。
-
密码式(Resource Owner Password Credentials):这种方式要求客户端直接获取用户的账号密码,并使用这些信息直接向认证服务器申请访问令牌。密码式模式适用于只信任特定客户端的情况,不适用于第三方应用程序。
注册时返回密钥与client_id;登录时带着client_id,password与密钥,返回token
-
客户端凭证(Client Credentials):此种方式使用客户端凭证来申请访问令牌,不需要用户的参与。客户端凭证通常是指客户端的 ID 和密码,它们将被用于认证客户端。客户端凭证模式适用于一些无需用户参与的应用场景,例如后台服务之间的通信。
示例代码
一个使用 Python 实现 OAuth2 协议的示例代码:
import requests
# 获取 access_token
def get_access_token(client_id, client_secret, username, password):
data = {
'grant_type': 'password',
'client_id': client_id,
'client_secret': client_secret,
'username': username,
'password': password
}
response = requests.post('https://example.com/oauth/token', data=data)
return response.json()['access_token']
# 使用 access_token 访问受保护资源
def access_resource(access_token):
headers = {
'Authorization': 'Bearer ' + access_token
}
response = requests.get('https://example.com/protected_resource', headers=headers)
return response.json()
# 示例代码使用
client_id = 'your_client_id'
client_secret = 'your_client_secret'
username = 'your_username'
password = 'your_password'
access_token = get_access_token(client_id, client_secret, username, password)
resource = access_resource(access_token)
print(resource)
在示例代码中,首先需要通过 get_access_token 函数获取 access_token,然后使用 access_token 访问受保护资源,示例中的受保护资源为 https://example.com/protected_resource。在实际使用中,需要将示例代码中的参数替换为真实的值。
CAS认证协议模型
概述
CAS(Central Authentication Service)是一种单点登录协议,它允许用户只需要登录一次,就可以访问多个相互信任的应用系统。
CAS 协议的核心思想是:应用系统不直接认证用户身份,而是把认证交给一个中心化的认证服务器,每个应用系统都去认证服务器验证用户身份。
CAS 协议的实现方式一般包括以下几个步骤:
- 用户访问一个需要认证的应用系统,该系统重定向到 CAS 服务器的登录页面。
- 用户在 CAS 服务器的登录页面上输入用户名和密码,CAS 服务器验证用户身份,如果验证通过则生成一个登录凭证(Ticket),并把该凭证作为参数跳转回应用系统。
- 应用系统收到登录凭证后,向 CAS 服务器验证凭证的有效性。如果凭证有效,则认为用户已经登录,否则要求用户重新登录。
- 用户在其他需要认证的应用系统中访问时,直接跳转到该应用系统,该系统向 CAS 服务器验证用户是否已经登录,如果已经登录,则可以直接访问,否则要求用户重新登录。
需要注意的是,CAS 协议只提供了用户身份认证的功能,而没有提供授权功能。在实际应用中,需要通过其他方式实现授权,例如 RBAC(Role-Based Access Control)权限控制模型。
与OAuth2的不同
CAS(Central Authentication Service,中心认证服务)协议是一种用于实现单点登录的协议。CAS协议通过中心认证服务来实现多个应用系统之间的单点登录,用户只需要在一个系统中登录认证一次,就可以访问所有与该系统信任的系统。
相较之下,OAuth2协议是一种用于授权的协议,用于允许用户通过授权访问受保护的资源。OAuth2协议可以用于实现单点登录,但其主要目的是授权,而非认证。OAuth2协议中的授权流程是将用户的资源拥有者(Resource Owner)的授权,授权访问给第三方应用(Client)。
因此,CAS协议和OAuth2协议虽然都可以用于实现单点登录,但它们的实现原理和目的不同。CAS协议主要用于认证和单点登录,OAuth2协议主要用于授权。