从有状态应用(Session)到无状态应用(JWT),以及 SSO 和 OAuth2
不管用哪种方式认证用户,都可能被中间人攻击窃取 SessionID 或 Token,从而发生 CSRF 攻击。解决方式就是全站 HTTPS。现在 Let’s Encrypt 已经支持免费的通配符 HTTPS 证书了。
0. 引子
HTTP 协议是无状态的,要保存用户状态需要额外的机制。
0.1 开始
刚开始时,多数公司使用的技术栈是:单台云服务器上安装所需的所有软件,包括 Nginx 提供 Web 服务,MySQL 数据库,PHP-FPM 应用程序服务。这时候使用的用户认证协议使用最简单的 Session。客户端的每个请求都会携带 Cookie,其中保存了 SessionID 字段,服务器可以通过这个 SessionID 字段访问到对应的 Session(例如 PHP 中的 $_SESSION
),从而识别出用户登录状态。Session 中还可以添加一些常用的字段进来(比如用户名、手机号等),避免对数据库的频繁访问。
0.2 发展
后来,随着用户量增大、并发增大,单台服务器搞不定了,于是搞了个水平扩展的服务器集群,通过 Nginx 或 LVS 实现负载均衡。这时发现个问题,用户登录后 Session 是保存到集群中的某一台服务器上的。要使 Session 机制可以在分布式环境下继续工作,需要一些额外操作。而且对于现在的大前端(浏览器、APP、小程序)趋势来说,Cookie 机制略显累赘。
而这时,JWT 认证协议完全满足需求。协议简单清晰,花一个下午就可以搞清楚。
0.3 壮大
多产品线
公司发展过程中,产品线会慢慢增多,比如百度的贴吧、网盘、浏览器等。这时,需要一套单点登录机制 SSO(Single sign-on),用户只要一次登录,就可以使用这一系列产品。SSO 描述了认证的问题。
SSO 需要一个独立的认证中心 CAS(Central Authentication Service,中央认证服务),只有认证中心能提供登录入口,接受用户的用户名密码等凭证,其他系统无登录入口,只接受认证中心的间接授权。这里有个开源的 CAS:apereo CAS,其服务端用 Java 实现,客户端支持多种语言。其架构文档可以参考 这里。
微服务
单体项目拆分成微服务后,可以更加灵活。通常所有的服务都在网关之后,所有请求都发送到网关,由网关统一转发。微服务的网关通常实现了 OAuth,成为认证授权中心,用于判断是否有足够权限。微服务之间可以通过 JWT 进行访问鉴权,避免身份认证。
成为开放平台
随着公司用户增多(假设跟微信一样,有几亿用户),合作企业也越来越多。如果每次都要在后台通过人工给合作伙伴配置账号密码,分配权限管理,那太麻烦了。同时,一些企业有自己的平台,想要利用我的用户账号体系实现在这些平台上的登录(授权登录)。对于用户的图片,一些图片打印公司也想在经过用户同意后,直接访问到我服务器上的用户图片,优化体验。
总之,就是只要用户同意,他可以分享自己的所有资源(账号、图片等)。这时,就需要 OAuth2 了。这是一个授权框架,描述了各种授权的问题。
0.4 关于 authorization(授权) 和 authentication(认证)
- authorization(授权):表示允许做某些事情
- authentication(认证):判断真实性
例如,用户登录论坛时,需要先用用户名和密码认证用户有没有权限登录,如果密码正确则认证通过,登录成功。用户登录后,判断其角色并授予相应的权限,例如超级管理员可以删除所有人,版主可以删除其版块的帖子。
1. Session
1.1 Session 原理
最传统的用户认证方式。用户首次访问应用服务器后建立会话,服务器可以使用 Set-Cookie 这个 HTTP Header,将会话的 SessionID 写入在用户端保存的 Cookie 中(具体的名字可以自行设置,系统中统一即可)。下次用户再次向这个域名发请求时会携带所有 Cookie 信息,包括这个 SessionID。
Session 信息保存在服务器端,而用于唯一标识这个 Session 的 SessionID 则保存在对应客户端的 Cookie 中。SessionID 这个会话标识符本质上是一个随机字符串,每个用户的 SessionID 都不一样。
Session 中可以保存很多信息。例如设置一个 IsLogin 字段,用户通过账号密码登录后,将这个字段设置为 TRUE。这样,在 Session 的有效期内(比如 2 小时),即使用户关闭网页,再次打开后仍会保持登录状态(除非用户清理了 Cookie,导致其访问服务器时没有携带 SessionID 字段)。对于其他的常用字段(如 userID、userName等)也可以添加到 Session 中,以减少数据库的访问压力,但注意不要太大,因为所有用户的会话信息都是保存在服务器的内存中的。
1.2 通过 Fiddler 抓包分析 Session
下面的示例,基于 PHP 语言,CodeIgniter 框架。同时,省略了无关的 HTTP Header,重点分析 Session 相关字段。
1. 首次访问某个网站
在第一次访问一个网站时,浏览器中没有对应 Cookie 信息,所有请求的 HTTP Header 中没有 Cookie 这个字段。如果应用服务器支持会话,可以在为这个用户创建 Session 后,通过在响应的 HTTP Header 中使用 Set-Cookie 字段将这个会话的 SessionID 保存到浏览器的 Cookie 中。可以看到我这里对应的 SessionID 的名字是 ci_session:
-----------------------------------------请求的 HTTP Header-----------------------------------------
GET http://tuan.local.cn/ HTTP/1.1
Host: tuan.local.cn
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
If-Modified-Since: Thu, 10 May 2018 06:20:36 GMT
...
-----------------------------------------响应的 HTTP Header-----------------------------------------
HTTP/1.1 200 OK
Date: Thu, 10 May 2018 06:21:13 GMT
Content-Type: text/html; charset=UTF-8
Set-Cookie: ci_session=hfvn0lq3ct62mppl86n2du535bc4stcf; expires=Thu, 10-May-2018 08:21:13 GMT; Max-Age=7200; path=/; HttpOnly
...
这里 Set-Cookie 中的各个字段解释如下,完整的中文版解释参考 这里:
- ci_session:SessionID,这个会话对应的服务器上的 Session 的唯一标识符。
- expires:Cookie 的有效期。
- Max-Age:Cookie 过期前的秒数。
- path:可以在 Header 中使用这个 Cookie 的 URL 路径,这里表示这个域名下的所有请求都会携带这个 Cookie。
- HttpOnly:表示这个 Cookie 无法通过 JavaScript 的
Document.cookie
属性或 XMLHttpRequest 和 Request 这两个 API 访问,避免 XSS(cross-site scripting,跨站脚本攻击)。
2. 再次访问这个网站
每次通过域名或 IP 地址访问时,浏览器都会检查是否有可用的 Cookie,如果有,则放到请求的 HTTP Header 中一同发送到服务器:
-----------------------------------------请求的 HTTP Header-------------------------------------------
GET http://tuan.local.cn/ HTTP/1.1
Host: tuan.local.cn
Cookie: ci_session=hfvn0lq3ct62mppl86n2du535bc4stcf
...
-----------------------------------------响应的 HTTP Header-------------------------------------------
HTTP/1.1 200 OK
Date: Thu, 10 May 2018 06:22:02 GMT
Content-Type: text/html; charset=UTF-8
...
3. 登录
登录成功之后,登录请求对应的响应会再次设置 Cookie 字段,重新设置 Cookie 字段的有效期。我的应用程序中设置 Session 为两个小时的有效期:
这里演示的是通过 AJAX 登录,所以有 Origin 和 X-Requested-With 这两个由浏览器自动设置的字段:
-----------------------------------------请求的 HTTP Header-------------------------------------------
POST http://tuan.local.cn/index/login_password HTTP/1.1
Host: tuan.local.cn
Origin: http://tuan.local.cn
X-Requested-With: XMLHttpRequest
Content-Type: application/json
Referer: http://tuan.local.cn/
Cookie: ci_session=hfvn0lq3ct62mppl86n2du535bc4stcf
...
{"Mobile":"18866668888","Password":"888666"}
-----------------------------------------响应的 HTTP Header-------------------------------------------
HTTP/1.1 200 OK
Set-Cookie: ci_session=hfvn0lq3ct62mppl86n2du535bc4stcf; expires=Thu, 10-May-2018 08:22:33 GMT; Max-Age=7200; path=/; HttpOnly
...
4. 登录后的访问
跟正常访问没有区别,只是携带的 Cookie 中有 SessionID,且服务器端对应的 Session 中需要(比如 IsLogin=true,自己设置)标识已登录状态:
-----------------------------------------请求的 HTTP Header-------------------------------------------
GET http://tuan.local.cn/ HTTP/1.1
Host: tuan.local.cn
Cookie: ci_session=hfvn0lq3ct62mppl86n2du535bc4stcf
...
-----------------------------------------响应的 HTTP Header-------------------------------------------
HTTP/1.1 200 OK
Date: Thu, 10 May 2018 06:22:34 GMT
...
1.3 Session 的不足
Session 的主要问题有:
- 服务器压力大:每个用户在认证后,Session 信息都会保存在服务器的内存中,开销大。
- 难以扩展:对于基于 Session 的分布式系统,要实现负载均衡,有两个办法:确保同一用户始终访问同一个服务器,或在多台服务器之间同步 Session。对于前者,Nginx 也可以用 ip_hash 把同一来源的 IP(同一 C 段)指向后端的同一台机器。对于后者则需要通过 Session Sticky 机制在多台服务器之间同步 Session(例如 Nginx 的扩展模块 nginx-sticky-module。假设 Session 存储在 A 服务器上,而用户访问了 B 服务器,则可以将 Session 从 A 同步到 B,但是如果存储 Session 的 A 服务器挂掉,还是会导致用户掉线)。
还有,就是目前大前端的发展,除了浏览器外,各种 APP、小程序层出不穷,而非浏览器下环境下避免使用 Cookie 可能会更简单。
2. JWT
Session 之所以这么麻烦,是因为需要在服务器端保存信息,那我把信息保存在客户端,不就可以避免这个麻烦了嘛。JWT 就是这么个思路,服务器端保存加密机制及密钥,对用户指定字段进行加密后的字符串保存在客户端,用户下次请求时携带加密前的字段和加密后的字符串,如果跟服务器加密结果匹配,则认为登录成功。
2.1 JWT 原理
JWT(JSON web token)是一种认证协议,可以发布接入令牌(Access Token,保持在客户端)并对发布的签名接入令牌进行验证。令牌(Token)本身包含一系列声明,应用程序可以根据这些声明限制用户对资源的访问。
JWT 由三段信息构成的:
- header
- payload
- signature
JWT 示例:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhIn0.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE1MjU5NDM4MTksIm5iZiI6MTUyNTk0Mzg3OSwiZXhwIjoxNTI1OTQ3NDE5LCJ1aWQiOjF9.jL-Hrl8obZlLGutjr-nVPCSoF2ObFh-rWfSwSZxoxzs
1. header 部分
Header 部分用于声明协议类型和加密方式。
上面的 JWT 示例的 header 部分经过 base64_decode 后得到原始 JSON 字符串,内容如下:
{
"typ":"JWT",
"alg":"HS256",
"jti":"4f1g23a12aa"
}
其中,typ 内容固定为 JWT,alg 表示加密算法,这里使用的是 HMAC SHA256。
2. payload 部分
payload 部分用于存放负载,将明文信息经过 base64 编码后存储,未经加密,不可存储敏感信息。包括以下三种:
- JWT 标准中注册的声明
- 公共声明
- 私有声明
JWT 标准中注册的声明(不强制使用)有以下几种,完整版可以 参考这里:
- iat:Issued At,签发时间
- iss:Issuer,JWT 签发者
- sub:subject,JWT 所面向的订阅者,每个 Issuer 范围内是唯一的
- aud:Audience,JWT 的接收方
- exp:Expiration Time,过期时间,这个过期时间必须要大于签发时间
- nbf:定义在什么时间之前,该 JWT 都是不可用的.
- jti:JWT 的唯一身份标识,主要用来作为一次性 Token,避免重放攻击
上面 JWT 示例中的 payload 部分对应的 JSON 字符串为:
{
"iss":"http:\/\/example.com",
"aud":"http:\/\/example.org",
"jti":"4f1g23a12aa",
"iat":1525943995,
"nbf":1525944055,
"exp":1525947595,
"userID":6666,
"userName":"kika",
"userSex":"m"
}
这个 payload 中添加了几个自定义字段。
3. signature 部分
将 header 和 payload 经过 base64 编码后,用 .
句点拼接成一个字符串,通过 HMACSHA256(Java 的方法)或 hash_hmac(PHP 的方法),使用指定密钥加密这个字符串得到 signature。
JAVA:
sig = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);
PHP:
$sig = hash_hmac('sha256', base64_encode($header) + "." + base64_decode($payload), $secret);
JWT 支持两种签名方式:
- 密钥:基于字符串,简单,安全性低
- RSA 和 ECDSA 签名:基于公钥和私钥,需要先生成私钥文件,签名时指定这个文件的位置
2.2 JWT 特点
- 信息基于 base64 编码转换为 ASCII 码,传输可靠。
- 信息是不加密存储的,不可存敏感信息。
- JWT 本质上是通过时间换空间,服务器不存储用户状态信息,但是每个用户请求都会消耗 CPU 时间来验证 Token。
- 基于 Token 的鉴权机制保持了 HTTP 协议的无状态型,从而实现更简单的水平扩展。
- 需要在服务器端额外编程(Session 则不用)。
- 生成签名字段时,支持使用密钥字符串签名(安全性较低),也支持使用 RSA、ECDSA 私钥签名。
用户登陆后,可以把一些常用字段(用户标识,是否是管理员,权限有哪些等等可以公开的信息)用 JWT 编码存储在 Cookie 中,每次服务器读取到 Cookie 后就可以解析到当前用户对应的信息,减小数据库压力。也可以用 Authorization: Bearer <jwttoken>
的方式通过 HTTP Header 仅发送 JWT 的 Token。
2.3 JWT 工作流程
- 用户通过账号密码发起登录请求
- 服务器验证通过后,设置 header 和 payload,并得到加密后的签名,然后将这三部分作为 Token 发送给用户
- 客户端保存 Token,并在每个请求中附加这个 Token
- 如果请求携带了 Token,服务器会验证这个 Token 并根据验证结果进行不同处理
发送请求时,Token 放在请求的 HTTP Header 中。另外,如果发生跨域,例如 www.xx.com
下发出到 api.xx.com
的请求,需要在服务端开启 CORS(跨域资源共享):
Access-Control-Allow-Origin: *
2.4 通过 Fiddler 抓包分析 JWT
下面的示例,基于 PHP 语言,CodeIgniter 框架。同时,省略了无关的 HTTP Header,重点分析 JWT 相关字段。
通过 Cookie 实现
1. 登录成功,Token 创建并设置客户端的 Cookie
-----------------------------------------请求的 HTTP Header-------------------------------------------
GET http://jwt.com/welcome/create_token HTTP/1.1
Host: jwt.com
...
-----------------------------------------响应的 HTTP Header-------------------------------------------
HTTP/1.1 200 OK
Date: Fri, 11 May 2018 02:27:19 GMT
Set-Cookie: jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhIn0.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE1MjYwMDU2MzksIm5iZiI6MTUyNjAwNTY5OSwiZXhwIjoxNTI2MDA5MjM5LCJ1c2VySUQiOjY2NjYsInVzZXJOYW1lIjoia2lrYSIsInVzZXJTZXgiOiJtIn0.MvYG6L71mM_AJj5FT4--RzCluIQ__nqgYSe9RTj8VCk; expires=Fri, 11-May-2018 04:27:19 GMT; Max-Age=7200; path=/
Content-Length: 1052
...
2. 用户再次访问时,携带 Cookie
服务器端从 Cookie 中提取 jwt 这个字段后验证签名,如果通过验证则认为内容可靠,解析其中的内容并以此决定用户登录状态、权限等:
-----------------------------------------请求的 HTTP Header-------------------------------------------
GET http://jwt.com/welcome/ HTTP/1.1
Host: jwt.com
Cookie: jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhIn0.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE1MjYwMDU2MzksIm5iZiI6MTUyNjAwNTY5OSwiZXhwIjoxNTI2MDA5MjM5LCJ1c2VySUQiOjY2NjYsInVzZXJOYW1lIjoia2lrYSIsInVzZXJTZXgiOiJtIn0.MvYG6L71mM_AJj5FT4--RzCluIQ__nqgYSe9RTj8VCk
...
通过 HTTP Header 字段 Authorization 实现
1. 登录成功,服务器创建并设置客户端的 Authorization 这个 HTTP Header
-----------------------------------------请求的 HTTP Header-------------------------------------------
GET http://jwt.com/welcome/create_token HTTP/1.1
Host: jwt.com
...
-----------------------------------------响应的 HTTP Header-------------------------------------------
HTTP/1.1 200 OK
Date: Fri, 11 May 2018 02:35:19 GMT
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhIn0.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE1MjYwMDc3NTQsIm5iZiI6MTUyNjAwNzgxNCwiZXhwIjoxNTI2MDExMzU0LCJ1c2VySUQiOjY2NjYsInVzZXJOYW1lIjoia2lrYSIsInVzZXJTZXgiOiJtIn0.CBsw7_rDC-GeJBob2JwCOITp7L80g_VT9KUtSVmKYKY
Host: jwt.com
2. 用户再次访问时,携带 Authorization
-----------------------------------------请求的 HTTP Header-------------------------------------------
GET http://jwt.com/welcome/ HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhIn0.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE1MjYwMDc3NTQsIm5iZiI6MTUyNjAwNzgxNCwiZXhwIjoxNTI2MDExMzU0LCJ1c2VySUQiOjY2NjYsInVzZXJOYW1lIjoia2lrYSIsInVzZXJTZXgiOiJtIn0.CBsw7_rDC-GeJBob2JwCOITp7L80g_VT9KUtSVmKYKY
Host: jwt.com
后端服务器对这个 Authorization 进行判断即可。
2.5 示例(基于 PHP)
对于 PHP,可以使用的 JWT 库有 jwt、jwt-auth。这里以第一个 jwt 为例,具体操作请结合所使用语言及框架和安装的 JWT 库。
2.5.1 使用 composer 安装 JWT 库
composer require lcobucci/jwt
注意,PHP 版本需要 5.5+,同时需要开启 OpenSSL 扩展。
2.5.2 通过 JWT 库生成 Token
使用秘钥签名
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256;
public function create_token() {
$builder = new Builder();
$signer = new Sha256();
// 设置签发者
$builder->setIssuer('http://xx.com');
// 设置接收者
$builder->setAudience('http://xx.com');
// 设置 ID,可以用来区分
$builder->setId('4f1g23a12aa', true);
// 设置签发时间
$builder->setIssuedAt(time());
// 在 60 秒内该 token 无法使用
$builder->setNotBefore(time() + 60);
// 设置过期时间位 2 小时
$builder->setExpiration(time() + 7200);
// 设置自定义的 payload 信息
$builder->set('userID', 6666);
$builder->set('userName', 'kika');
$builder->set('userSex', 'm');
// sha256 签名,密钥字符串可以自定义
$builder->sign($signer, 'signatureString');
// 获取生成的token
$token = $builder->getToken();
// 可以通过 Cookie 传输
set_cookie('jwt', $token, 7200);
// 也可以通过 HTTP Header 传输,在前端保存 token 后添加到 HTTP Header 即可:Authorization: Bearer xx.xx.xx
// 查看字段内容
$token = explode('.', $token);
echo base64_decode($token[0]).'<br/>';
echo base64_decode($token[1]).'<br/>';
}
使用 RSA 和 ECDSA 签名
把上面使用字符串加密的这一行:
$builder->sign($signer, 'signatureString');
替换为使用密钥文件加密即可,需要提供私钥地址:
$builder->sign($signer, $keychain->getPrivateKey('私钥地址'));
2.5.3 前端通过 Cookie 或 HTTP Header 访问
在每一个请求头里加入 Authorization,并加上 Bearer:
fetch('api/user', {
headers: {
'Authorization': 'Bearer ' + token
}
})
2.5.4 验证签名
通过 Cookie 传输 JWT 信息:
if ($token = get_cookie('jwt')) {
$rs = $this->verify_token($token);
if ($rs) {
echo 'you have right jwt<br />';
} else {
echo 'error<br />';
}
}
通过 HTTP Header 传输 JWT 信息:
$headers = apache_request_headers();
if (!empty($headers['Authorization']) && $token = $headers['Authorization']) {
$token = substr($token, strpos($token, 'Bearer ') + 7);
$rs = $this->verify_token($token);
if ($rs) {
echo 'you have right jwt from Authorization<br />';
} else {
echo 'error Authorization<br />';
}
}
2.5.5 提取数据
直接从 $token
中获取所有数据:
public function get_claims ($token) {
$parser = new Parser();
$parse = $parser->parse($token);
return $parse->getClaims();
}
也可以获取单条数据:
$parse->getClaim('aud');
3. OAuth 2.0
内容比较多,另写一篇,参考 这里。
从有状态应用(Session)到无状态应用(JWT),以及 SSO 和 OAuth2的更多相关文章
- 关于Spring Security中无Session和无状态stateless
Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, ...
- 有状态的bean和无状态的bean的区别
有状态会话bean :每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”:一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束.即每个用户最初都会得到一个初 ...
- SSH深度历险(三) EJB Session Bean有状态和无状态的差别与联系
刚開始对两种sessionbean存在误解.觉得有状态是实例一直存在,保存每次调用后的状态,并对下一次调用起作用.而觉得无状态是每次调用实例化一次,不保留用户信息.细致分析并用实践检验后,会发现,事实 ...
- SSH深度历险(三) EJB Session Bean有状态和无状态的区别与联系
刚开始对两种sessionbean存在误解,认为有状态是实例一直存在,保存每次调用后的状态,并对下一次调用起作用,而认为无状态是每次调用实例化一次,不保留用户信息.仔细分析并用实践检验后,会发现,事实 ...
- 无状态shiro认证组件(禁用默认session)
准备内容 简单的shiro无状态认证 无状态认证拦截器 import com.hjzgg.stateless.shiroSimpleWeb.Constants; import com.hjzgg.st ...
- 无状态服务(stateless service)
一.定义 无状态服务(stateless service)对单次请求的处理,不依赖其他请求,也就是说,处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如说数据库),服务器本 ...
- 对有状态bean和无状态bean的理解(转)
现实中,很多朋友对两种session bean存在误解,认为有状态是实例一直存在,保存每次调用后的状态,并对下一次调用起作用,而认为无状态是每次调用实例化一次,不保留用户信息.仔细分析并用实践检验后, ...
- 这个知识点不错,,学习一下先。。。无状态服务(stateless service)(转)
这样的应用,显得高级一些哟~~:) +================== http://kyfxbl.iteye.com/blog/1831869 ========================= ...
- HTTP协议是无状态协议,怎么理解?
Http是一个无状态协议,同一个会话的连续两个请求互相不了解,他们由最新实例化的环境进行解析,除了应用本身可能已经存储在全局对象中的全部信息外,该环境不保存与会话有关的不论什么信息. 自己的理解,在 ...
- (转)Spring Bean Scope 有状态的Bean 无状态的Bean
有状态会话bean :每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”:一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束.即每个用户最初都会得到一 ...
随机推荐
- Django的ORM常用查找操作总结
作者:python技术人 博客:https://www.cnblogs.com/lpdeboke/ 首先这里给出一个用户信息model class UserModel(models.Model): u ...
- STM32启动地址设置及从非0x800000 开始调试程序
首先设置程序的启动地址,STM32默认的启动地址是从0x8000000开始的,现在我要设置程序向后偏移10K地址,也就是从0x8002800启动. 需要分两步完成上面操作: 一.Keil MDK设置: ...
- [常用类]StringBuffer 类,以及 StringBuilder 类
线程安全,可变的字符序列. 字符串缓冲区就像一个String ,但可以修改. 在任何时间点,它包含一些特定的字符序列,但可以通过某些方法调用来更改序列的长度和内容. 字符串缓冲区可以安全地被多个线程使 ...
- k8s结合helm部署
一.安装Helm helm教程以及安装可以参考这篇文章 二.Heml说明 常见的helm模板如下 myapp - chart 包目录名 ├── charts - 依赖的子包目录,里面可以包含多个依赖的 ...
- 剑指offer学习读书笔记--二维数组中的查找
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都是按照从上到下递增的顺序排序.请设计一个函数,输入这样的一个二维数组和一个整数,判断数组是否含有这个整数. 1 2 8 9 2 4 9 1 ...
- MongoDB的使用学习之(一)开篇
本人是菜鸟-1级,整理这个系列,之所以用整理,而不是写,是因为本人不是从头自己读源码,一个一个字母翻译过来的,而是记录整个学习过程,查看别人好的文章,收集好的资料,并有自己的一些项目代码,并从中得到点 ...
- ASE Code Search
重现基线模型 Hamel's model 基线模型原理 如何实现semantic search?在已有数据库的基础上,衡量一个句子和每段代码的相关性再进行排序,选出最优代码片段即可实现一个通用的cod ...
- Maya多版本下载与激活方法
目录 1. 安装激活说明 1. 安装激活说明 Maya2019:https://www.cnblogs.com/coco56/p/11425559.html Maya2017:https://www. ...
- 苹果账号需要的邓白氏D-U-N-S编码更新信息最新方法,官方已不受理邮件
公司从上海搬迁到深圳,公司名称相应变更,但之前注册的苹果开发者账号上的名字还是就的,尝试在后台提交更新申请,官方给了邮件,要求邮件提交证明材料,证明材料提交后,苹果又反馈和邓白氏的资料不匹配,要求先修 ...
- CentOS 7 FTP的安装与配置
CentOS7 FTP安装与配置 1.FTP的安装 #安装yum install -y vsftpd #设置开机启动systemctl enable vsftpd.service #启动systemc ...