一点一滴成长

导航

Http认证(鉴权)

1、Http无状态

  Http无状态指的是当前请求并不会记录它的上一次请求信息,所以当一个用户登录成功后,继续访问其它页面(再次请求数据)的话,怎样标识该用户是已登录状态。

  用户身份认证,主要有BASIC认证(基本认证)、DIGEST认证(摘要认证)、SSL客户端认证、FormBase认证(基于表单认证),Bearer认证(基于token的认证)。

2、BASIC认证

  如下所示,客户端发送请求后服务器会返回401响应码以及WWW-Authenticate首部字段,该字段内包含认证方式BASIC和相关认证信息。客户端收到401响应后在Authorization字段上添加用户ID和密码(二者应以冒号连接),并以Base64编码后发送。认证成功后服务器返回状态码200,失败会返回401。

  593856-20230316161351502-1835682304

  Base64就是一种基于64个可打印字符来表示二进制数据的方法,具体转换步骤: 

       第一步,将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
  第二步,将上面的24个二进制位每6个一组,共分为4组。
  第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
  第四步,根据Base64编码对照表(0对应'A',1对应'B',...63对应'/')获得对应的值。

  BASIC认证的缺点:用户名和密码只是经过了Base64编码,相当于是明文传输。

3、DIGEST认证

  DIGEST认证流程同BASIC认证流程类似,不同的是服务端返回的WWW-Authenticate首部字段的值为认证方式DIGEST及其对应的认证信息,客户端回应的Authorization头里为用户名、密码、一次性随机数(服务端返回)组合后进行MD5运算的值,服务端收到后再根据保存的用户名、密码、一次性随机数来计算它们的MD5值,然后与客户端发来的MD5值。进行比较以判断验证是否通过。一次性随机数是服务端发送的,因为每次请求都会重新生成这个随机数,所以称为一次性随机数,使用它可以防止MD5值被攻击者截获,然后使用这个MD5伪造客户向服务端发送请求,因为此时的MD5已失效(因为每次请求服务端发送的一次性随机数不同,所以一次请求对应一个MD5值,即每次请求的MD5值不同)。

  DIGEST认证的缺点: 每次请求客户端和服务端都要进行认证,高并发场景下(MD5)耗费较多计算资源;而且现在的服务端一般不会直接保存用户密码,而是保存密码的MD5值或密码+salt的MD5值,所以服务端就无法对用户名+用户密码做MD5运算。

  MD5+salt(盐):在密码之前或之后加上一个随机生成的字符串(字母、数字、特殊符号组合),如mima123456salt&!k96, 再进行MD5加密。盐值应该​足够长且随机(相同密码每次加密结果不同)​,建议长度16字节以上,每个用户应该使用独立的盐值,​密码库被破解后,加盐的密码增加了破解难度。但MD5算法本身存在碰撞漏洞,目前较易被破解,现在密码存储推荐使用高安全的bcrypt,其自动为密码生成随机盐值。

4、SSL客户端认证

  HTTPS协议使用证书来验证服务端的真伪,也可以使用证书来对客户进行验证。一般是将客户端证书发给客户,客户将证书安装到自己的电脑(浏览器)上,客户端向服务端发送证书以进行验证。银行等机构会使用该认证机制来认证客户。 

  SSL客户端认证的缺点:客户需要安装证书。

5、FormBase认证(表单认证)

  基于表单的认证不是http协议标准,客户端需要登录的时候,服务端返回一个登录表单:用户名输入框+密码输入框+提交按钮,点击提交后表单数据(通常为post)发送到服务端,用户名密码匹配后(如与数据库中哈希值对比)生成会话标识Session通常存储在服务器内存或 Redis 等持久化系统中),然后将Session ID通过cookie返回给客户端(在首部字段Set-Cookie内写入Session ID),客户端在后续的请求中通过cookie携带Session ID,服务端通过 Session ID 查找 Session信息以验证用户身份。Session ID应该使用难以揣测的字符串,服务器需要对其进行有效期管理,另外,为减轻跨站脚本攻击(XSS)造成的损失,建议事先在Cookie内加上httponly属性。表单除了用户名密码模式,也可以是验证码模式,记住密码等。

  FormBase认证使用cookie来存储Session ID,缺点是:①、cookie默认不能跨域;②、后台服务如果是集群的话,首次请求在A登录成功,再次请求被分配到了B上,但B中并没有Session相关信息,用户状态失效;③、cookie主要是与浏览器交互,对于移动端不太好支持。鉴于表单认证的缺点,推荐使用下面的JWT认证。

6、Bearer认证(JWT认证)

  jwt认证是基于token(令牌)的认证,其认证流程:用户登录成功,服务器生成token返回给客户端,客户端将收到的token保存在localStorage或sessionStorage或Cookie中,后续客户端的请求都会在Authorization头中携带该token以供服务端验证权限,如下所示:

  111

  token中通常包含用户 ID、权限、过期时间等信息,所以服务端无需查询数据库就可以对用户进行鉴权。token可通过JWT或OAuth2获得,因为token是通过指定加密算法再结合服务器私钥生成的签名,所以token很难伪造。服务端使用的私钥可以通过随机字符串生成工具生成,应避免使用简单字符串,长度至少 256 位。

  为了提高安全性,一般使用两个token,一个访问token,一个刷新token,access token即前面用来验证用户身份的token,一般设置较短的过期时间,如1小时,refresh token一般设置较长的时间,如7天,当访问token过期时,客户端使用刷新token来向服务请求新的访问token,无需用户重新登录(直到客户登出或刷新token过期)。具体流程为:

    ①、 用户登录后服务端生成access token和refresh token发送给前端,返回示例如下所示。前端将access token保存在内存如window.sessionStorage中,refresh token存储在cookie中。

       112

    ②、前端每次调用需要权限的 API 时,在请求头中携带 Access Token,服务端验证Access Token通过且未过期则返回请求的资源。

    ③、当 Access Token 过期,服务器返回 401,前端发起 “刷新请求”(如 POST /api/token/refresh),浏览器自动从 Cookie 中携带Refresh Token,服务器验证Refresh Token通过且未过期的话生成新的Access Token和Refresh Token(可选,增强安全性)返回给前端,前端收到后更新token并重新发起API请求。

    ④、当Refresh Token 过期,“刷新请求”返回401,前端收到后跳转至登录页。

    ⑤、如果Access Token或者Refresh Token验证未通过,服务器除了返回 401,还应该在响应体中通过具体字段区注明原因,比如{"error":"invalid token"},前端收到后跳转至登录页面。

    ⑥、当用户登出时,前端删除保存在window.sessionStorage的Access Token,后端返回Set-Cookie头将Refresh Token 置空并设置过期时间为过去(删除cookie),如Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Max-Age=0; 。

    ⑦、为了防止用户登出以后前端继续拿着原来的Access Token来请求接口,当用户登出的时候应该将Access Token保存在登出黑名单中,所以在前面第②步中验证Access Token之前,应该先检查该Token是否在登出黑名单中,如果在黑名单中的话返回401,提示token已失效。登出黑名单的key应该是Access Token的唯一标识jti(JWT ID)或其哈希值(SHA256),其value设置为"invalid"。为了避免黑名单无需增长,应该设置key的过期时间为Access Token本身的剩余有效期,即exp - 当前时间(当Access Token过期以后黑名单中虽然没有了它,但前端继续拿着它请求接口的话会被判定过期从而拒绝访问)。黑名单key的过期时间可以略长于Access Token本身的剩余有效期,以防误差。

    ⑧、为了防止用户登出以后前端继续拿着原来的Refresh Token来请求接口,在前面第①步服务端生成Refresh Token后应该将其保存到redis中(存储token的唯一标识jti或哈希值,其过期时间与Refresh Token一致),用户登出的时候从redis删除该token。当前端请求“刷新接口”的时候,先检查Refresh Token是否在redis中,不存在的话拒绝请求。

  使用双token的其他注意事项:

     ①、Refresh Token 的唯一用途是调用“刷新请求”,业务接口使用Access Token。

     ②、Refresh Token可以关联用户的设备指纹(如浏览器 UA、操作系统、IP 属地等),当用户IP地址变化后,或者从Windows 切换到 iOS,,服务器可拒绝刷新,要求用户重新验证。一般不会将Access Token关联用户设备指纹,因为Access Token有效期短(一般15分钟),即使被盗,攻击的时间窗口较短,而如果Refresh Token被盗的话(有效期一般为7天-30天),攻击者可长期获取新Access Token,所以Refresh Token的安全需求要高于Access Token,当用户设备指纹有变化就要求用户重新登录可以有效防止Refresh Token被盗。而且由于每次请求都会验证Access Token,再添加IP验证的话会显著增加性能损耗,而每次请求刷新接口的时候验证用户指纹的性能损耗显著降低。

     ③、为了保证刷新接口能够携带Refresh Token,登录接口与刷新接口的域名应该一致,且登录接口的已将cookie的path属性设置为/,比如刷新接口为http://domin/api/login,刷新接口为http://domain/api/token/refresh。

     ④、对非法的Access Token或Refresh Token,可以进行请求日志记录(包括请求 IP、时间、Token 特征等),若短时间内出现大量非法 Token 请求,可能是暴力破解或批量攻击,需触发风控措施(如临时封禁 IP)。

     ⑤、因为Access Token过期的时候才会请求刷新接口,所以刷新接口可以额外限制请求频率(如 1 分钟内最多 2 次),以阻止暴力攻击。

     ⑥、当检测到用户状态异常的时候,可以将Access Token保存到登出黑名单中,将Refresh Token从redis中删除,从而使用户为登出状态。

     ⑦、如果想要能控制用户的登录状态的话,在前面第⑧步存储Refresh Token的时候,使用userID作为key(查询的时候可以从token中获取userID),Refresh Token和Access Token作为value(map形式),如下所示。要设置某个用户为登出状态的话,从redis查询用户的Access Token,然后将Access Token存储到登出黑名单,然后将userID键删除。还可以在value中记录登录的时间、定位、IP地址等信息。

      119

     ⑧、如果要支持多设备登录互不影响的话,为每个设备生成各自的Access Token-Refresh Token,每个设备的token互不影响:用户登录的时候附加deviceID到请求,服务端生成token的时候将该deviceID作为自定义Claim写入JWT的Payload部分,这样每台设备的token就不同,从而做到各设备的登录、登出操作互不影响。如果想要能控制用户某个设备的登录状态的话,存储Refresh Token的时候,使用userID:deviceID作为key,也可以以userID为key,然后value为deviceID构成的map,如下所示,这样就能查询用户所有的登录设备(比如当前已经是同时登录设备的最大数量的话,当就可以拒绝新设备的登录)。

        111

 

posted on 2025-08-01 14:11  整鬼专家  阅读(64)  评论(0)    收藏  举报