Authentication
1. Basic Authentication
老王,你家的狗咋还咬我呢?
那不是大黄没见过你嘛。
HTTP提供了用于权限控制和认证的通用框架,最常用的认证方案就是Basic Authentication
认证的步骤:
接收到客户端的未认证请求后,服务端向客户端发送
challenge
(质询信息),提供如何验证- 客户端输入用户名和密码,发送经过
Base64
编码后的用户名密码组合字符串 - 服务端检查证书(credentials),通过返回
200
,否则返回403
.
- 客户端输入用户名和密码,发送经过
响应头 质询信息
状态码
401
表示Unauthorized
WWW-Authenticate
字段,提供如何进行验证的信息,一般为WWW-Authenticate: Basic realm="xxxxxx"
,表示用Basic HTTP验证方案
请求头 Authorization字段
以用户名admin
密码password
为例
将用户名密码用
:
联结admin:password
- 通过
Base64
算法将联结后的字符串编码 - 将认证方法和空格添加到编码结果前,得到
Authorization
字段
- 通过
Express实现Basic Auth
新建app.js
1 | function basicAuth(req, res, next) { |
2. session-based Auth
2.1 Cookie and Session
诶?你这次进来大黄怎么没咬你?
上次喂大黄吃了饼干,它总得记住我吧!
HTTP是无状态协议,为了在请求时保持状态,需要session
和cookies
。
啥是无状态协议?
In computing, a stateless protocol is a communications protocol that treats each request as an independent transaction that is unrelated to any previous request so that the communication consists of independent pairs of request and response. A stateless protocol does not require the server to retain session information or status about each communications partner for the duration of multiple requests. In contrast, a protocol which requires keeping of the internal state on the server is known as a stateful protocol.
摘自wiki.
通俗地说,你每次来,大黄每次都咬你,那大黄就是个傻狗,无状态的,记不住你。
你喂大黄吃饼干,它就记住你了。
这跟Authentication有啥关系?
HTTP是无状态的,你认证完一次后,再次登录:还要输入用户名和密码,再认证一次,不会记得你认证过这回事儿。
2.2 session-based Auth步骤
1. 用户发送带有用户名和密码的请求
2. 服务端储存该用户信息,并设置cookie到服务端
3. 后续的客户端请求都会带上cookie
4. 服务端查找用户信息并返回
HTTP Cookie
HTTP Cookie是服务器发送给客户端,并保存在本地的数据,浏览器客户端再向服务器发起请求时,会带上Cookie一并发送给服务器。
有了Cookie,就可以保持状态,比如你的登录信息。再认证时,会将cookie也发送,服务器根据cookie就可以知道是不是来自同一个浏览器。
来看一下维基百科的描述
Another popular use of cookies is for logging into websites. When the user visits a website’s login page, the web server typically sends the client a cookie containing a unique session identifier. When the user successfully logs in, the server remembers that that particular session identifier has been authenticated and grants the user access to its services.
如何创建Cookie
服务器通过Set-Cookie
响应头设置(下面通过express-session自动设置cookie)
Cookie的属性
Domain
和Path
Domain
和Path
属性定义了cookie的作用域,告知浏览器该cookie术语哪个网址。举个例子,在sports.myweb.com
下发送请求:1
2
3
4HTTP/1.1 200 OK
Set-Cookie: LSID=DQAAAK…Eaem_vYg; Path=/login; Expires=Wed, 23 Mar 2020 20:20:20 GMT; Secure; HttpOnly
Set-Cookie: HSID=CYWFUm…CKrbyt; Domain=.myweb.com; Path=/; Expires=Wed, 23 Mar 2020 20:20:20 GMT; HttpOnly
Set-Cookie: SSID=BQ3T…RYUEv; Secure; HttpOnly第一个cookie,
LSID
没有Domain
属性,cookie只能用于包含sports.myweb.com/login
的请求;其余两个cookie用于.myweb.com
的子域名请求中Expires
和Max-Age
Expires
属性定义了删除cookie的时间,若未设置,关闭浏览器后删除;Max-Age
定义了cookie的有效期。Secure
和HttpOnly
Secure
属性规定cookie只能在加密连接的情况下使用;HttpOnly
属性规定浏览器不要通过HTTP请求以外的通道公开cookie(cookie不能通过document.cookie获得)
Session
cookie在认证后的每一次请求时,都会发送到服务器,来验证已经认证过。不同的客户端向同一台服务器发送cookie,服务器必须分辨出对于同一服务的不同客户端请求。那每个已经认证的客户必须在服务端有记录,并且要有唯一的ID来标记。
所以session在Authentication中就是用来跟踪用户HTTP会话信息的,session里面存点啥(以express-session中间件为例)?
- cookie : Options object for the session ID cookie. The default value is
{ path: '/', httpOnly: true, secure: false, maxAge: null }
. - genid: Function to generate the session ID. Default is to use uuid
- name:The name of the session ID cookie to set in the response (and read from in the request).
- proxy: Trust the reverse proxy when setting secure cookies.
- resave: If true forces a session to be saved back to store even if it was not modified in the request.
- rolling: Forces a cookie to be set on every request.
- saveUninitialized: If true it forces a newly created session without any modifications to be saved to the session store.
- secret: It is a required option and is used for signing the session ID cookie.
- store: Session store instance. Default is to use memory store.
- unset: Controls the handling of session object in the store after it is unset. Either
delete
orkeep
the session object. Default is to keep the session object
显然session是一个复合数据,用一个对象来存储。
2.3 Express实现
下面时用cookie-parser
和express-session
中间件实现的一个例子:这里使用signedCookies
更改app.js
如下
1 | var session = require('express-session') |
session是永久存储在服务器上的,存在内存中,重启服务器会清除掉。所以登录信息要存储到数据库。
3. Token-Based Authentication
3.1 session-based authentication的弊端(为什么要用token-based authentication)?
扩展性
程序扩展性变差:用户认证后,服务器做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须在这台服务器上才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力,也就意味着限制了扩展的能力
移动端(???)
> [移动端处理cookie有些问题]( https://www.socialmediatoday.com/content/cookies-mobile-do-they-exist ), 移动端用原生接口发请求最大特点是每一次都新建一个session。 在web端,首次登录成功后,之后的每次请求都带上认证通过的信息;但在移动端,每一次请求一个新的session.
跨域请求
CSRF
既然token-based Auth作为替代出现,一定是解决了上述问题,也就是token的特点:
1. self-contained 自身包含信息
2. shareable 可以在其他应用中使用
我们先来看一看token认证的步骤:
3.2 token认证的步骤
1. 用户发送带有用户名和密码的请求
2. 服务端验证身份
3. 服务端创建并发送token到客户端(服务端啥也没存)
4. 后续的客户端请求都会带上token
5. 服务端验证token,通过则响应数据
为什么token能够自身包含信息并能在其他应用中使用?我们来看一看token的组成:
3.3 token(以JSON Web Token为例)
Header
- type token的类型,这里用的是JSON Web Token
- alg 使用的
hash
算法,这里用的是HMAC SHA256
Header使用
Base64Url
编码Payload
JWT的主体,也成为
JWT Claims
,存储要传输的信息和token的其他信息Registered Claims
一组预定义的声明,这些声明非强制,但是推荐。包括iss(issuer),exp(expiration time),sub(subject),aud(audience)等。
Public Claims
自定义信息,如用户名和其他重要信息
Private Claims
令牌创建者和使用者可以同意使用私有claim名称。容易冲突,小心使用
Signature
要创建签名,须获取编码后的header、payload,密钥和header中指定的算法,进行签名,例如:
1
2
3
4HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)密钥是服务端持有的签名,来验证token或签署新token
3.4 Express实现
使用jsonwebtoken
中间件签发并验证token;使用passport-jwt
中间件支持JWT认证策略
更改authenticate.js
1 | var JwtStrategy = require('passport-jwt').Strategy |
更改users.js
1 | ... |
在各router文件中各个路由添加验证方法verifyUser
1 | var authenticate = require('../authenticate') |