网站登录

  |  

要想知道网站的登录方式为什么使用jwt还是得知道网站的发展历程来看。

为了方便叙事,年代记已经混淆,忽略即可,只是为了理清概念。

1.0 时代

最初的时候,网站都是纯粹的静态网站,基本上一台机器就能满足需求,每一次请求都会向后端请求新页面,此时用户的登录信息都是保存在cookie和session中的。

cookie是保存在客户端的,用户是可以篡改的,所以是不安全的。session是保存在服务器端的,用户是无法篡改的,所以是安全的。

weibo-cookies

可以看到这里面定义了一个WBPSESS的cookie,盲猜这个是weibo security session的缩写。

cookie是保存在客户端的,用户是可以篡改的。虽然session是保存在服务器端的,但是session的id是保存在cookie中的,我们可以替换成别人的session
id,这样就可以伪装成别人登录了。

session只是能保证存储信息是安全合法的,但是不保证传输过程是安全的。

cookie和session的传输都是浏览器自带的行为,不需要特殊处理,只要有cookie就会自动带上,所以这两种方式都是无感知的。

2.0 时代

后来,随着网站的发展,一台机器一个进程已经扛不住了,因此出现了一台机器多个进程,这样的话,session信息如果存储在内存中就无法共享了,用户的登录信息就无法共享了。

因此出现了分布式session,用户的登录信息都是保存在数据库中的,这样的话,用户的登录信息是安全的,因为数据库是保存在服务器端的,用户是无法篡改的。

但是这样的话,每次请求都需要向数据库请求用户的登录信息,这样的话,性能就会下降,时间耗费在io上。换成redis的话,情况是一样的。

同样的时期,ajax出现了。ajax是一种异步请求,可以不刷新页面就请求数据,web的访问方式就发生了变化,不再是每次请求都向后端请求新页面,而是向后端请求数据,然后前端自己渲染页面。

前端自己渲染页面的话,灵活性就会提高,用户体验就会提高,但是问题随之而来,比如CSRF(跨站请求伪造 cross-site request forgery)
攻击和XSS(跨站脚本攻击 cross-site scripting)攻击。

CSRF 攻击

CSRF攻击是一种利用用户身份的攻击方式,攻击者可以伪造用户的请求,这样的话,用户的登录信息就会被盗取。

csrf

比如:

  1. user登录了工商银行,工商银行给user分配了一个session id,保存在cookie中,然后user看了一下自己的账户余额,工资已经发下来了。
  2. user访问了不合法的网站,这个网站里面有一个img标签,src是工商银行的转账接口,这个接口是一个get请求,参数是转账金额和转账账户。
  3. user想放大图片,不断点击图片,这样的话,就会不断的向工商银行发送转账请求,因为已经登录了,所以cookie是会自动带上的,这样的话,就会不断的转账。
  4. 第二天,user再看自己的账户余额,发现自己的钱不见了。

整个过程十分隐秘,银行没有预警的情况下,user完全无法察觉。

XSS 攻击

XSS攻击是一种利用用户身份的攻击方式,攻击者可以在网页中插入恶意脚本,这样的话,用户的登录信息就会被盗取。

比如:

  1. user看到勇哥直播,想给勇哥打赏,于是点击了礼物。
  2. 但是评论区有人发了一个恶意评论,这个评论是一段js代码,这段代码直接修改了礼物充值的接口,把user的钱转到了攻击者的账户。
  3. user充值了1000元,但是发现自己的账户余额没有增加,而攻击者的账户余额增加了1000元。

为了应对这些情况,人们有了各种各样的解决方案,这不是今天的重点。

3.0 时代

为了应对各种问题,浏览器设置了同源策略,这样的话,不同域名的网站就无法访问对方的cookie了,这样的话,CSRF攻击就无法进行了。

什么是同源策略呢?

一个域名地址由协议、域名、端口、请求路径、查询参数组成。

same-origin

如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。

浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作DOM,那么会有⼀套基础的安全策略的制约,我们把这称为同源策略。

它的存在可以保护用户隐私信息,防止身份伪造等(读取Cookie)。

同源策略主要表现在 DOM、web数据 和 网络 这三个层面。

  • DOM 层面:页面嵌页面(iframe)无法进行相互操作。
  • web数据层面:限制cookie、localStorage和indexDB的读取,不同源的网站无法读取对方的cookie、localStorage和indexDB。
  • 网络层面:限制跨域请求。

跨域过程

注意跨域过程:

  1. 浏览器向服务器发送请求。
  2. 服务器返回响应。
  3. 浏览器解析响应。
  4. 发现不是同源,拦截请求。

所以可以知道,跨域不是请求发不出去,而是请求发出去了,服务器正常接收请求并返回,但是浏览器拦截了响应。

跨域行为

同源策略是浏览器的行为,不是HTTP的规范。

但是有三个标签是允许跨域加载资源:

  • <img src=''>
  • <link href=''>
  • <script src=''>

因为以上内容允许跨域加载资源,所以一般优化的时候,会把静态资源放在cdn上,这样的话,用户访问网站的时候,就会向cdn请求资源,这样的话,用户的访问速度就会提高。

解决方法

什么时候会遇到跨域问题呢?比如”今日头条”。头条不可能所有的新闻都是自己的,所以会有很多新闻是从其他网站转载过来的,这样的话,就会遇到跨域问题,因为不同的新闻来源的是不同源的。

怎么解决跨域问题呢?国内一般就三种。

  • JSONP
    • 还记得有三种标签是允许跨域加载资源的吗?script标签就是其中之一,所以可以通过script标签加载资源,这样的话,就可以跨域了。
    • JSONP就是利用script标签动态加载资源,然后在资源的回调函数中处理数据。
    • 但是缺点也很明显,首先JSONP只支持GET请求,不支持POST请求。其次JSONP需要前后端配合,后端需要返回一个回调函数,前端需要处理这个回调函数,类似gRPC的方式了
  • CORS
    • CORS(cross-origin resource sharing)是一种跨域资源共享的方式,服务器设置。
    • 浏览器只要获取到Access-Control-Allow-Origin的响应头,判断合法,就会放行请求。实现了跨域。
    • 这种方式的优点是只需要后端配合,前端不需要做任何处理。
    • 面试题:Access-Control-Allow-Origin是谁设置的?怎么设置的?
  • 代理
    • 在前后端之间添加一个转发的代理服务器,这样的话,就可以实现跨域了。
    • 这种方式的优点是完全不需要前后端有任何配合,只要都面向这个额外的服务器开发即可。
    • 面试题:为什么要用代理解决跨域问题?代理上不会跨域吗?

jwt

为啥要说到同源策略呢?还记得同源策略会限制什么吗?会限制cookie、localStorage和indexDB的读取。会导致什么问题呢?会导致session无法通过cookie传递,因为不同域名的网站无法访问对方的cookie了。

那怎么办?这时候jwt就出现了。(通过配置也能实现cookie传递,这里列举一般情况)

jwt(json web token)很早就有,但是在这个时候才开始大面积流行。jwt是 json web token 的缩写,是一种跨域传递非敏感信息的方式,它不存储在服务器端,而是存储在客户端。

一个典型的jwt长这样:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao

你可能会有疑问,不是json吗?怎么是一串字符呢?先不忙,先看看jwt的组成,它是由三部分组成的,分别是header(头部)、payload(载荷)和signature(签名)。

header和payload都是json格式数据然后使用base64编码得到,因此想要获得真正的数据,只需要解码即可。

比如这里的header是:

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

他代表了这个jwt的加密方式和类型。

这里的payload是:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

他代表了这个jwt的载荷,也就是用户的信息。

理论上payload是可以存储任何信息的,但是官方其实也给出了建议,如果你使用开源库的话,就会发现开源库对这些字段都有一定的校验。

  1. iss (Issuer):表示签发该 JWT 的发行者。
  2. sub (Subject):表示该 JWT 所面向的用户。
  3. aud (Audience):表示该 JWT 的预期接收者。
  4. exp (Expiration Time):表示该 JWT 的过期时间,UNIX 时间戳格式。
  5. nbf (Not Before):表示在该时间之前 JWT 不会被接受和处理,UNIX 时间戳格式。
  6. iat (Issued At):表示该 JWT 的签发时间,UNIX 时间戳格式。
  7. jti (JWT ID):表示该 JWT 的唯一标识符。
  8. scope:表示用户的权限。字符串数组。
  9. data:自定义字段。存放用户自定义的信息。

从这里可以看到,jwt信息十分容易获取,因此jwt是不适合存储敏感信息的。

签名是由header、payload和盐加密得到的,具体过程略,这样的话,就可以保证jwt的完整性,如果有人篡改了jwt,那么签名就会不匹配。

和jwt一起经常出现的两个名词还有jws(json web signature)和jwe(json web encryption)。这两个都是开放标准,用于在jwt上进行签名和加密。

jws就是只对内容进行签名,内容本身不加密。与我们常见的jwt类似。jwe是对内容进行加密,然后再对加密后的内容进行签名。这样的话,就可以保证内容的机密性和完整性。当然成本也会提高。

至此,jwt开始崭露头角,但是查看它的过程就知道,从session到jwt,其实是将io的时间转移到了cpu上,因为每次请求都需要解码jwt,大量请求的话,cpu的负担就会很重。

面试题:

  1. session和jwt本质上就不同,为什么总是拿来比较?
  2. 为什么有些系统会选择jwt,有些系统会选择session?

题外话时间

题外话01:jwt的签名安全吗?

不断会有人说jwt替换了session是因为jwt更加安全,用户无法修改。然而,事实真的是这样吗?

我们也知道jwt的第三段是签名,那么我们不要这个签名行不行呢?答案是可以的,因为jwt的加密方法在header中,所以我们可以直接修改header,定义
alg 为 none,这样的话,就可以得到一个没有签名的jwt了。

如果组件是开源框架,不是自己写的,那么这个框架会通过header中的 alg 来判断是否需要签名,如果 alg 是 none,那么就不会进行签名。

此时,你修改的是哪个用户,你现在就是哪个用户了。

题外话02:jwt真的简化了取数据的流程了吗?

并没有,我们之前也说了,jwt只适合存储非敏感信息,而且数据量不能过大,否则每次传输也会占用网络带宽。

那么需要获取具体信息怎么办呢?

这时候就要根据jwt的载荷定义的用户标识,然后去数据库中查询对应用户表以及关联表等。本质上是查询延后了而已,并没有简化,而大多数视频只谈论前半部分步骤,貌似较少了io。

题外话03:token 是什么?

在手机app、小程序等场景中,我们没有办法直接使用cookie/session,于是一些人定义了token,这个token是一串字符,用来代表用户的身份。这个token是保存在客户端的,每次请求的时候,都会带上这个token。

有没有似曾相识的感觉?

没错,这个token就是另外一种形式的session,只不过session浏览器会自动传递,而token还得自己维护。

题外话04: API Key 是什么?

API Key 是一种用于访问 API 的密钥,它是一串字符,用来代表用户的身份。这个 API Key 是保存在客户端的,每次请求的时候,都会带上这个
API Key。他已经被绝大多数开放API所使用。

wechat-api-key

jwt、cookie、API Key的对比如下:

jwt cookie API Key
应用场景 前后端、后端服务 前后端 后端服务
认证对象 主要是用户 用户 系统、应用
撤销 不方便 方便 方便
生成方式 动态生成 动态生成 预先分配

jwt 缺点如下:

  1. jwt无法实时退出所有client,因为jwt是保存在客户端的,用户是无法篡改的,所以只能等待jwt过期。
  2. 信息修改无法及时同步。比如jwt存储了用户的信息,而此时用户修改了信息,但是jwt是不会自动更新的,只能等待jwt过期。
  3. jwt泄露无法马上将token无效。

三种认证方式各有优劣,没有绝对的好坏之分,只有适合不适合。很多网站也会把jwt和cookie结合使用,jwt存储用户的信息,cookie存储jwt,简化开发,也防止乱存的jwt泄露。

4.0 时代

看起来网站已经和现在差不多了,还有什么发展呢?

其实也不算发展,只是起了一个名字,同一时代,jwt流行起来,还有个技术流行起来,就是分布式系统。

分布式系统和jwt一样,自古有之,但是认证问题还没有统一。此时一个用户可能会访问多个系统,每个系统都需要认证且认证系统不一样的话,不仅麻烦还不安全。

一种解决方案就是单点登录(SSO,single sign-on)。

SSO是一种认证机制,用户只需要登录一次,就可以访问多个系统,这样的话,用户的登录信息就是安全的,因为用户只需要登录一次。

CAS

目前比较流行的就是CAS(Central Authentication Service 中央认证服务器)。CAS是一种开源的单点登录协议,它的原理是这样的:

  1. 用户访问网站A,网站A发现用户没有登录,就会重定向到CAS服务器。
  2. 用户向CAS认证,然后CAS服务器会给用户一个ST(Service Ticket),一个TGT(Ticket Granting Ticket),并且重定向到网站A。
  3. 用户拿着ST去网站A,网站A获取到ST
  4. 网站A不知真假,拿着ST和自己的服务标识去CAS服务器验证,验证通过返回给网站A。
  5. 网站A得到验证通过的信息,就会给用户创建session或者jwt。
  6. 只要session或者jwt没有过期,用户就可以一直访问网站A。

以上是用户访问网站A的过程,用户访问网站B的过程是一样的。

  1. 用户继续访问网站B,网站B发现用户没有登录,就会重定向到CAS服务器。
  2. 但是此时用户已经有了TGT,所以不需要再次登录,CAS服务器会直接给用户一个ST,然后重定向到网站B。
  3. 用户拿着ST去网站B,网站B获取到ST……之后的步骤和网站A一样

CAS的过程对于用户是透明的,用户的感受是只需要登录一次,就可以访问多个系统。当然上面的过程是简化的,实际上还有很多细节。

OAuth2和OIDC介绍

OIDC(OpenID Connect)
是OpenID的升级版,OpenID是一种认证协议,OIDC是一种认证协议,它是基于OAuth2的,OAuth2是一种授权协议。OpenID的官网在这里:https://openid.net/

OAuth2是一种授权协议,现在一般都是使用OAuth2,OAuth1已经被淘汰了。OAuth2的文档在这里:https://oauth.net/2/

在继续往下讲之前,我们先来认清两个概念:

  • 认证(authentication):是确认用户的身份,比如用户名和密码、指纹、人脸识别等。
  • 授权(authorization):是确认用户的权限,比如用户有没有权限访问某个资源。

OAuth2是一种授权协议,它的目的是让用户授权第三方应用访问自己的资源,比如用户授权第三方应用访问自己的微博、微信等。

为什么说OAuth2是一种授权协议呢?因为第三方仅需要用户的授权即可,它不关心用户是谁,有没有认证。

实际情况中只实现了OAuth2的应用是比较少的,还往往会实现OIDC,口语中将两者统称为OAuth2,实际上是有些不准确的。

OIDC = OAuth2 + 认证机制,是一种OAuth2的升级版,经常用于联合登录或单点登录的场景。

为什么不迭代OAuth2,而是新定义了一个OIDC呢?正所谓一流企业做标准,二流企业做品牌,三流企业做产品,OpenID已经走上了从标准到品牌的道路,只能说利益驱动。

OIDC过程

这里我们通过腾讯会议通过微信登录的过程来了解一下OIDC的过程。

  1. 我(用户)打开腾讯会议(客户端 client),然后点击微信登录。
  2. 腾讯会议(客户端 client)向微信平台(认证服务器 authorization server)发起请求,传入了自己的 client_id、scope和callback_url。
  3. 微信窗口提示我(用户)登录,同时提醒我账号将访问我的哪些信息。我(用户)点击屏幕进行了登录。
  4. 微信平台(认证服务器 authorization server)验证我(用户)的身份,然后通过callback_url重定向到腾讯会议(客户端
    client)并返回了code(授权码)。
  5. 腾讯会议(客户端 client)拿着code(授权码)向微信平台(认证服务器 authorization server)发起请求,传入了自己的
    client_id、client_secret、code
  6. 认证成功后返回了access_token(访问令牌)、refresh_token(刷新令牌)、openid(用户标识)和 unionid(用户统一标识)。
  7. 腾讯会议(客户端 client)会根据unionid去自己的数据库中查询用户信息,如果存在则返回登录状态。
  8. 如果不存在则会通过 openid 去微信平台(认证服务器 authorization server)获取用户信息,然后保存到自己的数据库中,然后返回登录状态。

OIDC和OAuth2的比较

角色

OAuth2中有四种角色:

  • 客户端(client),就是腾讯会议。
  • 资源拥有者(resource owner),就是我。
  • 授权服务器(authorization server),就是微信平台。
  • 资源服务器(resource server),就是腾讯会议的数据库。

OIDC聚焦在认证环节,所以没有资源服务器的角色,其他三种也有所变化:

  • 客户端称作 Relaying Party,就是腾讯会议。
  • 资源所有者称作 终端用户(End-User),就是我。
  • 认证服务器称作 OpenID Provider,就是微信平台。

CAS 可以使 openid 的 provider,也就是 openid 的 provider 可以承担单点认证中的 CAS 的角色。

授权方式

OAuth2中有四种授权方式:

  • 授权码模式(authorization code),就是上面的过程,优点是不需要暴露用户的密码,就可以获取用户的授权,目前使用最多。
  • 简化模式(implicit)
  • 密码模式(password)
  • 客户端模式(client credentials)

服务接口

OAuth2中定义了两个服务接口:

  • 授权服务接口(authorization endpoint),用于获取授权码。
  • 令牌服务接口(token endpoint),用于获取访问令牌。

OIDC中新增了一个userinfo服务接口,用于获取用户信息。

令牌

scope定义了具体需要获取的权限,由服务实现方定义,没有统一的标准。OIDC中新增了openid的scope,令牌借口的返回响应中会包含openid属性,这个ID是一个jwt

OAuth2中定义了两种令牌:

  • 访问令牌(access token),用于访问资源。
  • 刷新令牌(refresh token),用于刷新访问令牌。

OIDC中新增了一种令牌 openid,当 scope 中指定 openid 时,就会获得 openid 令牌。

文章目录
  1. 1. 1.0 时代
  2. 2. 2.0 时代
    1. 2.1. CSRF 攻击
    2. 2.2. XSS 攻击
  3. 3. 3.0 时代
    1. 3.1. 跨域过程
    2. 3.2. 跨域行为
    3. 3.3. 解决方法
    4. 3.4. jwt
    5. 3.5. 题外话时间
  4. 4. 4.0 时代
    1. 4.1. CAS
    2. 4.2. OAuth2和OIDC介绍
    3. 4.3. OIDC过程
    4. 4.4. OIDC和OAuth2的比较