赞
踩
验证当前用户的身份
,证明“你是你自己”
(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系统里录入的指纹相匹配时,就打卡成功)只要你能收到邮箱/验证码,就默认你是账号的主人
实现认证和授权的前提
是需要一种媒介(证书
) 来标记访问者的身份
照身帖
。照身帖由官府发放,是一块打磨光滑细密的竹板,上面刻有持有人的头像和籍贯信息。国人必须持有,如若没有就被认为是黑户,或者间谍之类的。身份证
,是用于证明持有人身份的一种法定证件。通过身份证,我们可以办理手机卡/银行卡/个人贷款/交通出行等等,这就是认证的凭证
。游客模式和登录模式
。游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就需要登录或者注册账号。当用户登录成功后,服务器会给该用户使用的浏览器
颁发一个令牌(token)
,这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌
,就可以使用游客模式下无法使用的功能。 洛:大爷,楼上322住的是马冬梅家吧?
大爷:马都什么?
夏洛:马冬梅。
大爷:什么都没啊?
夏洛:马冬梅啊。
大爷:马什么没?
夏洛:行,大爷你先凉快着吧。
要了解cookie先要了解HTTP是无状态的Web服务器,什么是无状态
呢?
一次对话完成后下一次对话完全不知道上一次对话发生了什么
。
- 如果在Web服务器中只是用来管理静态文件还好说,对方是谁并不重要,把文件从磁盘中读取出来发出去即可。但是随着网络的不断发展,比如电商中的购物车只有记住了用户的身份才能够执行接下来的一系列动作。所以此时就需要我们无状态的服务器记住一些事情。
那么Web服务器是如何记住一些事情呢?
- 既然Web服务器记不住东西,
那么我们就在外部想办法记住,相当于服务器给每个客户端都贴上了一个小纸条
。上面记录了服务器给我们返回的一些信息。然后服务器看到这张小纸条就知道我们是谁了。
HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人
。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我)
,就必须主动的去维护一个状态
,这个状态用于告知服务端前后两个请求是否来自同一浏览器
。而这个状态需要通过 cookie 或者 session 去实现。
cookie 存储在客户端: cookie 是服务器发送到用户浏览器
并保存在本地
的一小块数据,它会在浏览器
下次向同一服务器再发起请求时
被携带并发送到服务器上。
cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用
,一级域名和二级域名之间是允许共享使用的(靠的是 domain,下面会讲)。
Cookie是由服务器产生的
第一次访问服务端时
,服务器此时肯定不知道他的身份,所以创建一个独特的身份标识数据,格式为key=value
,放入到Set-Cookie
字段里,随着响应报文发给浏览器
。Set-Cookie字段
以后就知道这是服务器给的身份标识,于是就保存起来
,下次请求时会自动将此key=value值放入到Cookie字段中发给服务端。
请求报文
后,发现Cookie字段
中有值,就能根据此值识别用户的身份然后提供个性化的服务。接下来我们用代码演示一下服务器是如何生成Cookie
@RequestMapping("/testCookies")
public String cookies(HttpServletResponse response){
response.addCookie(new Cookie("testUser","xxxx"));
return "cookies";
}
1.首次访问服务器时发送的请求 http://localhost:8005/testCookies
,可以看到服务器返回的响应中有Set-Cookie字段
。而里面的key=value值正是我们服务器中设置的值。
2.再次刷新这个页面可以看到在请求体中已经设置了Cookie字段
,并且将我们的值也带过去了。这样服务器就能够根据Cookie中的值记住我们的信息了。
Cookie是存放在浏览器中的
因此浏览器替我们管理了Cookie的数据
,如果此时换成了Firefox等其他的浏览器,因为Cookie刚才是存储在Chrome里面的,所以服务器又蒙圈了,不知道你是谁,就会给Firefox再次贴上小纸条。
属性 | 说明 |
---|---|
name=value | 键值对,设置 Cookie 的名称及相对应的值,都必须是字符串类型 - 如果值为 Unicode 字符,需要为字符编码。- 如果值 为二进制数据 ,则需要使用BASE64 编码 。 |
domain | 指定 cookie所属域名 ,默认是当前域名 |
path | 指定 cookie 在哪个路径(路由)下生效,默认是 ‘/’。 如果设置为/abc ,则只有/abc 下的路由可以访问到该 cookie,如:/abc/read 。 |
maxAge | cookie 的失效时间 ,单位秒 。如果为 整数 ,则该 cookie 在 maxAge 秒后失效。如果为 负数 ,该 cookie 为临时 cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 cookie 。如果为 0 ,表示删除该 cookie 。默认为 -1 。- 比 expires 好用。 |
expires | 过期时间 ,在设置的某个时间点后该 cookie 就会失效。一般浏览器的 cookie 都是默认储存的,当关闭浏览器结束这个会话的时候,这个 cookie 也就会被删除 |
secure | 该 cookie 是否仅被使用安全协议传输。安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。 当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。 |
httpOnly | 如果给某个 cookie 设置了 httpOnly 属性,则无法通过 JS 脚本 读取到该 cookie 的信息 ,但还是能通过 Application 中手动修改 cookie ,所以只是在一定程度上可以防止 XSS 攻击 ,不是绝对的安全 |
Path属性可以指定 cookie 在哪个路径(路由)下生效,默认是 ‘/’。
测试后台设置为cookie.setPath("/testCookies")
,先访问http://localhost:8005/testCookies
,在访问http://localhost:8005
,左图请求路径与后台setPath
指定的路径是一样的,所以有cookie,而右边不一样,所以没有Cookie, 说明可以指定 cookie 在哪个路径(路由)下生效
**domain属性可以指定 cookie
所属域名
,默认是当前域名
**
设置为cookie.setDomain("localhost")
,先访问http://localhost:8005/testCookies
,在访问http://172.16.42.81:8005/testCookies
,看下图左边有Cookie,右边没有Cookie。说明可以指定 cookie 在哪个域名下生效
HTML5提供了两种本地存储的方式 sessionStorage 和 localStorage
基于 cookie 实现的
,Session存储在服务器端
,客户端只存储SessionId
session机制比cookie安全
,在一次会话中将重要信息保存在Session中,浏览器只记录SessionId
,且一个SessionId对应一次会话请求。session 认证流程:
第一次请求服务器
的时候,服务器根据用户提交的相关信息,创建对应的Session
请求返回时
将此Session
的唯一标识信息SessionID
返回给浏览器浏览器
接收到服务器返回的SessionID
信息后,会将此信息存入到 Cookie 中,同时 Cookie
记录此SessionID
属于哪个域名用户第二次访问服务器
的时候,请求会自动判断此域名下是否存在 Cookie
信息,如果存在自动将 Cookie
信息也发送给服务端
,服务端会从 Cookie
中获取SessionID
,再根据SessionID
查找对应的Session
信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session
证明用户已经登录可执行后面操作。根据以上流程可知,SessionID** 是**连接 Cookie 和 Session 的一道桥梁
,大部分系统也是根据此原理来验证用户登录状态。
@RequestMapping("/testSession")
@ResponseBody
public String testSession(HttpSession session){
session.setAttribute("testSession","this is my session");
return "testSession";
}
@RequestMapping("/testGetSession")
@ResponseBody
public String testGetSession(HttpSession session){
Object testSession = session.getAttribute("testSession");
return String.valueOf(testSession);
}
第一次请求服务器
访问http://localhost:8005/testSession
在响应头
中在Set-Cookie
中返回一个SessionId
。然后浏览器记住此SessionId
下次访问时可以带着此SessionId
,然后就能根据此SessionId
找到存储在服务端的信息了。
在访问路径http://localhost:8005/testGetSession
,发现得到了我们上面存储在Session中的信息。
那么Session什么时候过期呢?
如果没设置,默认是关了浏览器就没了
,即再打开浏览器的时候初次请求头中是没有SessionId了。默认是30分钟。
Session是如何被管理的?
容器
中被管理的,什么是容器呢?Tomcat
、Jetty
等都是容器。接下来我们拿最常用的Tomcat为例来看下Tomcat是如何管理Session的。
在源码ManageBase
的createSession
方法是用来创建Session的。
@Override
public Session createSession(String sessionId) {
//首先判断Session数量是不是到了最大值,最大Session数可以通过参数设置
if ((maxActiveSessions >= 0) &&
(getActiveSessions() >= maxActiveSessions)) {
rejectedSessions++;
throw new TooManyActiveSessionsException(
sm.getString("managerBase.createSession.ise"),
maxActiveSessions);
}
// 重用或者创建一个新的Session对象,请注意在Tomcat中Session就是StandardSession
// 它是HttpSession的具体实现类,而HttpSession是Servlet规范中定义的接口
Session session = createEmptySession();
// 初始化新Session的值
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
// 设置Session过期时间是30分钟
session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
String id = sessionId;
if (id == null) {
//生成sessionId
id = generateSessionId();
}
session.setId(id);// 这里会将Session添加到ConcurrentHashMap中
sessionCounter++;
//将创建时间添加到LinkedList中,并且把最先添加的时间移除
//主要还是方便清理过期Session
SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
synchronized (sessionCreationTiming) {
sessionCreationTiming.add(timing);
sessionCreationTiming.poll();
}
return (session);
}
到此我们明白了Session是如何创建出来的,创建出 来后Session会被保存到一个ConcurrentHashMap中
。可以看StandardSession
类。
Session是存储在
Tomcat
的容器中,如果后端机器是多台的话,在多个机器间是无法共享Session的
,此时可以使用Spring提供的分布式Session的解决方案
,是将Session放在了Redis中
。
存储在服务器端的
,Cookie 是存储在客户端的
。字符串数据
,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型
。保存的数据不能超过 4K
,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
访问资源接口(API)时所需要的资源凭证
简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
特点:
token 的身份验证流程:
总的来说就是客户端在首次登陆以后,服务端再次接收http请求的时候,就只认token了,请求只要每次把token带上就行了,服务器端会拦截所有的请求,然后校验token的合法性,合法就放行,不合法就
返回401(鉴权失败)
。
令牌
,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。生成 JWT
https://www.jsonwebtoken.io/
https://jwt.io/
JWT 认证流程:
Authorization: Bearer <token>
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
GET /calendar/v1/events
Host: api.example.com
Authorization: Bearer <token>
http://www.example.com/user?token=xxx
相同:
区别:
注意:
cookie 无法跨域
移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
集群部署
的时候,会遇到多台 web 服务器之间如何做 session 共享的问题。因为 session 是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 session 的服务器,那么该服务器就无法拿到之前已经放入到 session 中的登录凭证之类的信息了。明文存储
密码绝不要使用弱哈希或已被破解的哈希算法
,像 MD5 或 SHA1 ,只使用强密码哈希算法。绝不要以明文形式显示或发送密码
,即使是对密码的所有者也应该这样。如果你需要 “忘记密码” 的功能,可以随机生成一个新的一次性的(这点很重要)密码
,然后把这个密码发送给用户。任何一个服务器上的 session 发生改变(增删改),该节点会把这个 session 的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要 session ,以此来保证 session 同步
采用 Ngnix 中的 ip_hash 机制,将某个 ip的所有请求都定向到同一台服务器上,即将用户与服务器绑定。 用户第一次请求时,负载均衡器将用户的请求转发到了 A 服务器上,如果负载均衡器设置了粘性 session 的话,那么用户以后的每次请求都会转发到 A 服务器上,相当于把用户和 A 服务器粘到了一块,这就是粘性 session 机制。
Memcached 、Redis
来缓存 session,但是要求 Memcached 或 Redis 必须是集群
将 session 存储到 数据库
中,保证 session 的持久化
只要关闭浏览器 ,session 真的就消失了?
不对。对 session 来说,除非程序通知服务器删除一个 session,否则服务器会一直保留,程序一般都是在用户做登出
的时候发个指令去删除 session。
然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分 session 机制都使用会话 cookie 来保存 session id,而关闭浏览器后这个 session id 就消失了,再次连接服务器时也就无法找到原来的 session。如果服务器设置的 cookie 被保存在硬盘上,或者使用某种手段改写浏览器发出的 HTTP 请求头,把原来的 session id 发送给服务器,则再次打开浏览器仍然能够打开原来的 session。
恰恰是由于关闭浏览器不会导致 session 被删除,迫使服务器为 session 设置了一个失效时间,当距离客户端上一次使用 session 的时间超过这个失效时间时,服务器就认为客户端已经停止了活动,才会把 session 删除以节省存储空间。
参考以下文章
详解 Cookie,Session,Token
一文彻底搞懂Cookie、Session、Token到底是什么
彻底了解Cookie、Session、Token到底是什么
前端鉴权
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。