随着前端登录场景的日益复杂化和技术思想的不断演进,前端在登录方面的知识结构变得越来越复杂。对于前端开发者来说,在日常工作中根据不同的登录场景提供合适的解决方案是我们的职责所在,本文将梳理前端登录的演变过程。
1、无状态的HTTP
HTTP是无状态的,每条请求都是独立进行的。同一个用户多次发起请求,服务端无法识别多个HTTP请求来自同一个用户。为了解决这个问题,引入了会话机制(session)。
2、基于会话(session)的认证
会话(session)机制,使每条HTTP请求之间保持客户端的状态信息。session对象保存在服务端的内存或数据库中,这个session对象包含有用户的基本信息。sessionId(会话ID)保存在客户端中的cookie或localstorage中,每次客户端向服务端发送请求时,都会携带sessionId,服务端拿到sessionId对该请求进行身份验证。sessionId在请求传输过程中,可以被放置在cookie中,也可以放置在body中。
基于会话的认证方式是传统的方法,在很长的时间内被使用。但也存在一些缺点:
- 服务器负担大。服务端需要为每个用户维护一个会话对象,这可能在大规模应用中导致服务器负载增加
- 水平扩展困难。在分布式架构中,即应用程序在多个服务器上运行时,用户可能会在不同的服务器上交替访问应用程序,所以需要确保服务器之间可以共享会话状态。
- 不适用于无状态服务。对于RESTful API这种无状态服务,更适合基于令牌的认证方式。
3、跨域认证
随着前后端开发模式的变化,前端和后端更倾向于分开部署,前端的静态资源部署在一个服务器中,后端的API部署在另外一个服务器中。这意味着前端的登录需要进行跨域认证。跨域认证即使用跨域请求的方式,进行身份认证。常见的几种跨域认证方案:
- JSOP:只能用于跨域请求,不能进行身份验证
- CORS:可以进行跨域请求和身份认证。但是当多个业务系统调用同一个API服务下的接口时,随着业务系统数量的增长,后端开发人员需要频繁地更改跨域配置才能使新开发的系统可以进行跨域请求。当出现这种情况时,使用nginx反向代理将跨域的控制权移交至前端,是更好的方案。
- 代理:使用nginx做代理,将跨域请求转发至目标服务器,并将响应返回给客户端。
iframe场景下,使用localstorage做跨域存储,通过postmessage和iframe的方式跨域共享localstorage,但是safari不能使用postMessage,可以改用url方式跨域共享localstorage
4、基于令牌(token)的认证
为了解决在分布式架构中session认证存在的问题,可以使用基于token的认证方式。由于token只会存储在客户端,这样可以减少服务端的负担和提升了服务端水平扩展能力。
基于令牌(token)的认证时序图
优点:
- 减少服务端的负担。因为token只会存储在客户端上,不会存储在服务端。
- 提升了服务端水平扩展能力。因为服务端水平扩展不再需要处理会话状态的同步逻辑。
缺点:
- 签发的token无法撤销。token包含了有效期信息,在整个有效期内不能撤销,直至过期。
- 无法实现单点登出。如果使用令牌方式实现单点登录SSO功能,那么难以实现单点登出SLO。因为单点登出涉及到在所有相关联系统中注销用户,所以要实现此功能需要额外的机制配合。
以上是最基础版本的令牌的认证方式,还存在着一些缺点,主要用来阐述令牌认证的核心流程。要规避掉上述缺点,还需要配合其他机制(下面OAUTH部分会更深一步讲解)。
令牌(token)的生成
token最常见的方式是JWT(JSON Web Token),JWT是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT由三部分组成,由.
分割,形如:Header.Payload.Signature
。
1. Header(头部)
Header部分是一个JSON对象,描述JWT元数据,如下:
javascript
{
"alg": "HS256",
"typ": "JWT"
}
alg
表示签名的算法,type
表示令牌的类型。最后将上面的JSON对象使用Base64URL算法转成字符串。
2. Payload(负载)
payload部分是一个JSON对象,表示JWT实际传输的数据,官方定义了如下字段供选择:
javascript
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
也可以自定义字段,最后将上面的JSON对象使用Base64URL算法转成字符串。
3. Signature(签名)
signature部分是对前两部分的签名,防止数据被篡改。公式如下:
javascript
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
secret
是由服务端才有的密钥。
5、单点登录(SSO)
用户只需要登录一次,就可以在多个相关联的系统上访问,避免了多次登录的麻烦。单点登录简化了登录流程,提升了用户体验。目前有两种比较常见的实现方式:CAS
和OIDC
。
CAS
CAS(Central Authentication Service)是一种用于实现单点登录SSO的开源协议。它提供了一个中心化的认证服务器(CAS),CAS服务器负责用户的登录和身份认证,并生成一个票据(ticket)用于访问其他应用程序。如下图是CAS标准流程的时序图:
需要注意的几点:
- CAS是基于票据(ticket)实现单点登录的。票据是一种身份验证凭据,与token有一定区别。
- CAS中包含有TGT和ST两种ticket。TGT是长期有效的凭据,用于获取短期的ST凭据;ST凭据用来向特定服务验证身份的。
OIDC(OpenID Connect)
OAUTH是一个授权协议,在全世界得到了广泛的应用,目前是2.0版本,但并不是专门针对单点登录SSO设计的协议。OIDC是基于OAUTH实现的身份认证协议,是OAUTH的超集,可以实现单点登录功能。与CAS不同的是,OIDC是基于token的认证方式。
1. OAUTH2.0
OAUTH是一个授权协议,是为了解决同一个账号可以登录所有应用的问题。现在很多第三方应用都接入了微信登录,那么微信登录就是一个大的身份认证服务,所有接入了微信登录体系的APP,都可以使用微信账号直接登录。
1.1. OAUTH2.0 原理
OAuth 2.0的运行流程如下图,摘自RFC 6749。
认证服务器除了返回access token,refresh token也是可选项。
- access token:表示访问令牌。有效期短,解决token无法撤销问题。
- refresh token:表示更新令牌。有效期长,用于获取access token。
2. OIDC
下图是网上找到的一张基于token实现单点登录的时序图,虽然与标准的OIDC流程不一样,但是也大致说清楚了基于token实现单点登录的流程
CAS与OIDC的总结:
- CAS业务系统是基于session的保持登录态,而OIDC业务系统是基于token的
- CAS单点登录的方式更适合企业内部各个业务系统之间统一单点登录;而OIDC除了单点登录,还可以为第三方软件提供授权服务