常见前后端登录方案

正文开始之前,我们先要了解一个概念,就是什么是 登录态

主流Web应用比如浏览器是基于http协议的,而http协议是 无状态 的。什么是 无状态?就是服务器不知道是谁发送了这个http请求,无法识别区分用户身份。

所以登录态就是服务端用来区分用户身份,同时对用户进行记录的技术方案。

那怎么实现用户的登录态呢?常见的实现流程如下:

  1. 客户端用户输入登录凭据(如账户和密码),发送登录请求。
  2. 服务端校验用户是否合法(如认证和鉴权),合法后返回登录态,不合法返回第1步。
  3. 合法后携带登录态访问用户数据。

流程有了,如何实现呢?

常见的方案有HTTP基本认证、Cookie和Session认证、Token认证、单点登录认证等,下面一一介绍。

1. HTTP基本认证

HTTP基本认证是HTTP协议本身提供了一种服务端对客户端进行用户身份验证的方法。 流程如下:

nowjava.com
sequenceDiagram
autonumber
客户端->>服务端:Get / HTTP/1.1 Host:www.qq.com
服务端->>客户端:HTTP/1.1 401 Unauthorised WWW-Authenticate: Basic realm="qq.com"
客户端->>客户端:弹出登录窗口
客户端->>服务端:Get / HTTP/1.1 Host:www.qq.com Authorization: Basic xxxxxx
服务端->>客户端:验证成功,返回用户数据
  1. 客户端向服务端请求需要登录态的数据
  2. 服务端向客户端返回401状态码,要求客户端验证
  3. 客户端根据返回的 WWW-Authenticate: Basic realm="qq.com",弹出用户名和密码输入框要求用户进行验证。
  4. 用户输入用户名和密码后,客户端将用户名及密码以 Base64 格式发送给服务端。
  5. 服务端验证通过后返回用户数据。

这是一种比较简单的验证用户身份的方式,甚至不需要写代码,只要后端服务器配置一下即可。

优点:兼容性好,主流浏览器都支持

缺点:

  • 不安全,账号密码是Base64编码,很容易解码。
  • 无法主动注销,除非关闭标签或浏览器。

2. Cookie和Session认证

先了解两个概念,Cookie和Session是什么呢? 上面说到HTTP是一种无状态协议,而Cookie和Session可以弥补 HTTP 的无状态特性。

2.1 什么是Cookie

Cookie是客户端请求服务端时,由服务端创建并由客户端存储和管理的小文本文件。具体流程如下:

  • 客户端首次发起请求。
  • 服务端通过HTTP响应头里Set-Cookie字段返回Cookie信息。
  • 客户端再发起请求时会通过HTTP请求头里Cookie字段携带Cookie信息。

2.2 什么是Session

Session是客户端请求服务端时服务端会为这次请求创建一个数据结构,这个结构可以通过内存、文件、数据库等方式保存。具体流程如下:

  • 客户端首次发起请求。
  • 服务端收到请求并自动为该客户端创建特定的Session并返回SessionID,用来标识该客户端。
  • 客户端通过服务端响应获取SessionID,并在后续请求携带SessionID。
  • 服务端根据收到的SessionID,在服务端找对应的Session,进而获取到客户端信息。

2.3 Cookie和Session认证流程

nowjava.com
  1. 客户端向服务端发送认证信息(例如账号密码)
  2. 服务端根据客户端提供的认证信息执行验证逻辑,如果验证成功则生成Session并保存,同时通过响应头Set-Cookie字段返回对应的SessionID
  3. 客户端再次请求并在Cookie里携带SessionID。
  4. 服务端根据SessionID查找对应的Session,并根据业务逻辑返回相应的数据。

2.4 Cookie和Session认证优点

  • Cookie由客户端管理,支持设定有效期、安全加密、防篡改、请求路径等属性。
  • Session由服务端管理,支持有效期,可以存储各类数据。

2.4 Cookie和Session认证缺点

  • Cookie只能存储字符串,有大小和数量限制,对移动APP端支持不好,同时有跨域限制(主域不同)。
  • Session存储在服务端,对服务端有性能开销,客户端量太大会影响性能。如果集中存储(如存储在Redis),会带来额外的部署维护成本。

3. Token认证

Token又叫令牌,是服务端生成用来验证客户端身份的凭证,客户端每次请求都携带Token。 Token一般由以下数据组成:

uid(用户唯一的身份标识)
time(当前时间的时间戳)
sign(签名,由token的前几位+盐用哈希算法压缩成一定长的十六进制字符串)

3.1 Token认证流程

nowjava.com
  1. 客户端向服务端发送认证信息(例如账号密码)
  2. 服务端根据客户端提供的认证信息执行验证逻辑(如查询数据库),如果验证成功则生成Token并返回。
  3. 客户端存储(可以存在Cookie、LocalStorage或本地缓存里)收到的Token,再次请求时携带Token(可以通过HTTP请求头Authorization字段)。
  4. 服务端校验Token(如查询数据库),并根据业务逻辑返回相应的数据。

3.2 Token认证优点

  • 客户端可以用Cookie、LocalStorage等存储,服务端不需要存储。
  • 安全性高(有签名校验)。
  • 支持移动APP端。
  • 支持跨域。

3.3 Token认证缺点

  • 占用额外传输宽带,因为Token比较大,可能会消耗一定的流量。
  • 每次签名校验会消耗服务端性能。
  • 有效期短(避免被盗用)。

3.4 Refresh Token

3.3里说到为了避免被盗用Token一般有效期比较短。但是有效期太短会造成客户端不断重新登录,体验太差。有没有什么办法可以解决这个问题呢?

那就是再来一个Token,一个专门生成Token的Token,称为 Refresh Token

流程如下:

nowjava.com
  1. 客户端向服务端发送认证信息(例如账号密码)
  2. 服务端根据客户端提供的认证信息执行验证逻辑(如查询数据库),如果验证成功则生成Token和Refresh Token并返回。
  3. 客户端存储(可以存在Cookie、LocalStorage或本地缓存里)收到的Token和Refresh Token,再次请求时携带Token(可以通过HTTP请求头Authorization字段)。
  4. 服务端校验Token(如查询数据库),并根据业务逻辑返回相应的数据。
  5. 服务端发现Token过期了,拒绝了请求。
  6. 客户端重新请求并携带Refresh Token。
  7. 服务端校验Refresh Token并返回新Token和新Refresh Token。
  8. 客户端再次请求并携带新Token。

3.5 JWT

3.3、3.4里服务端校验客户端发过来的Token是否有效时,可能会查询数据库来验证。如果每次请求都要查询数据库,可能会带来额外性能消耗。

那这个有没有办法优化呢?

答案是有的,那就是JWT(JSON Web Token)。JWTAuth0 提出的通过 对JSON进行加密签名 来实现授权验证的方案。

JWT也是一种Token,由三部分组成: Header头部Payload负载Signature签名。它是一个很长的字符串,中间用点( . )分隔成三个部分,列如 :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header头部:

{
   "alg": "Hash算法(HMAC、SHA256或RSA)",
   "typ": "Token的类型(JWT)"
 }

Payload负载:

{
   "iss": "签发人(issuer)",
   "exp": "过期时间(expiration time)",
   "sub": "主题(subject)",
   "aud": "受众(audience)",
   "nbf": "生效时间(not before)",
   "iat": "签发时间(issued at)",
   "jti": "编号(JWT ID)",
   "uid": "自定义字段(可以存储用户ID等)",
 }

Signature 签名:

HMACSHA256(
   base64UrlEncode(header) + "." + base64UrlEncode(payload),
   secret //设置的密钥
)

JWT的流程和Token的基本一样,因为已经携带了客户端信息(如用户ID等),所以服务端校验Token时不需要查询数据库了。

4. 单点登录认证

上面说的都是同一个域名(或同一主域)下,通过Cookie或Token携带凭证实现登录态管理。但是如果有很多域名,如何实现用户在一个域名下登录后,访问另一个域名也能自动登录呢?

这就是单点登录问题(Single Sign On

要实现SSO,需要有一个CAS(Central Authentication Service)中央授权服务(假设域名为cas.com)来提供统一的登录功能。

假如现在有域名http://abc.com和http://123.com要实现互相自动登录。流程如下:

先访问http://abc.com

nowjava.com

再访问http://123.com

nowjava.com

先访问http://abc.com

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java