前言:银行安全面试认证授权,登录登出安全开发问题。
认证
Authentication(认证) 是验证您的身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。
授权
Authorization(授权) 发生在 Authentication(认证) 之后。它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如 admin,system。有些对系统资源操作比如删除、添加、更新只能特定人才具有。
RBAC 模型
RBAC 模型通过角色关联权限,角色同时又关联用户的授权的方式。一个用户可以拥有若干角色,每一个角色又可以被分配若干权限。
创建不同的角色并为不同的角色分配不同的权限范围(菜单)。
Cookie
应用案例:
1、Cookie
中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的基本信息给填了。
2、HTTP 协议是无状态的。使用Cookie
保存Session
或者Token
,向后端发送请求的时候带上Cookie
,后端获取Session
或者Token
记录用户当前的状态。
3、Cookie
可以用来记录和分析用户行为,将这些信息存放在Cookie
服务器获取你在某个页面的停留状态或者看了哪些商品。
SpringBoot代码实现:
1、设置Cookie
返回给客户端
@GetMapping("/change-username")
public String setCookie(HttpServletResponse response) {
// 创建一个 cookie
Cookie cookie = new Cookie("username", "Jovan");
//设置 cookie过期时间
cookie.setMaxAge(7 * 24 * 60 * 60); // expires in 7 days
//添加到 response 中
response.addCookie(cookie);
return "Username is changed!";
}
2、@CookieValue
注解获取特定的 cookie 的值
@GetMapping("/")
public String readCookie(@CookieValue(value = "username", defaultValue = "Atta") String username) {
return "Hey! My username is " username;
}
3、读取所有的Cookie
值
@GetMapping("/all-cookies")
public String readAllCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
return Arrays.stream(cookies)
.map(c -> c.getName() "=" c.getValue()).collect(Collectors.joining(", "));
}
return "No cookies";
}
Session
应用案例:
1、HTTP 协议是无状态的。当你要添加商品到购物车时,系统不知道是哪个用户操作的。服务端给特定的用户创建特定的Session
之后就可以标识这个用户并且跟踪这个用户,服务器记录用户的状态。
单服务器节点Session-Cookie 方案进行身份验证
应用案例:
1、用户成功登陆系统(服务器生成Session
一般会选择存放在 Redis数据库),然后返回给客户端具有 SessionID 的 Cookie。
2、当用户向后端发起请求的时候会把SessionID
带上,这样后端就知道你的身份状态。
功能步骤:
1、用户向服务器发送用户名、密码、验证码用于登陆系统。
2、服务器验证通过后,服务器为用户创建一个Session
,并将Session
信息使用Redis存储起来。
3、服务器向用户返回一个 SessionID
,写入用户的 Cookie
。
4、当用户保持登录状态时,Cookie
将与每个后续请求一起被发送出去。
5、服务器可以将存储在 Cookie
上的 SessionID
与存储在内存中或者数据库中的 Session
信息进行比较,以验证用户的状态。
Session-Cookie存在的问题:
1、依赖Session
的关键业务一定要确保客户端开启了Cookie。
2、注意 Session
的过期时间(一般是5分钟,退出系统应立刻消除对应的Session记录)。
3、多服务器节点 Session-Cookie。
4、Session信息服务器的可用性。
多服务器节点下 Session-Cookie 方案进行身份验证
应用案例:
部署了两份相同的服务 A,B,用户第一次登陆的时候 ,Nginx 通过负载均衡机制将用户请求转发到 A 服务器,此时用户的 Session信息保存在 A 服务器。结果,用户第二次访问的时候 Nginx 将请求路由到 B 服务器,由于 B 服务器没有保存用户的 Session信息,导致用户需要重新进行登陆。
最佳解决方案:
单独使用所有服务器都能访问的数据节点(Redis缓存)来存放Session信息。为了保证高可用,数据节点尽量避免是单节点。
如何处理客户端禁用Cookie问题(移动端)
将SessionID
加密之后放在请求的url
里面再传入后端与存储在内存中或者数据库中的 Session 信息进行比较
,但是安全性下降,不法分子拿到Session后进行Session分析或者是模拟请求。
Session-Cookie 方案进行身份验证的跨站请求伪造(CSRF)问题
应用案例:
进行Session
认证的时候,我们一般使用Cookie
来存储SessionId
,当我们登陆后后端生成一个SessionId
放在 Cookie 中返回给客户端,服务端通过 Redis 或者其他存储工具记录保存着这个SessionId
,客户端登录以后每次请求都会带上这个SessionId
,服务端通过这个SessionId
来判断标示你的状态。如果别人通过Cookie
拿到了SessionId
后就可以代替你的身份访问系统了。Session
认证中Cookie
中的SessionId
是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到伪装攻击请求(携带Cookie信息)效果。
解决方法:
使用Token
的话就不会存在这个问题,在我们登录成功获得Token
之后,一般会存放localStorage
(浏览器本地存储)中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个Token
,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带Token
的,所以这个请求将是非法的。
安全注意点:
不论是Cookie还是Token都无法避免跨站脚本攻击(Cross Site Scripting)XSS,通过脚本盗用信息比如Cookie
Token
Token 不需要自己存放Session
信息就能实现身份验证的方式,服务器端就不需要保存Session
数据了,只用在客户端保存服务端返回给用户的Token
为什么使用Token
1、Session
信息需要保存一份在服务器端需要我们保证保存Session
信息服务器的可用性。
2、不适合移动端(依赖Cookie
)。
JSON Web Token (JWT)组成部分
- Header : 描述 JWT 的元数据,定义了生成签名的算法以及
Token
的类型。 - Payload : 用来存放实际需要传递的数据。
- Signature(签名) :服务器通过
Payload
、Header
和一个密钥(secret
)使用Header
里面指定的签名算法(默认是 HMAC SHA256)生成。
Token方案进行身份验证
应用案例:
基于 Token 进行身份验证的的应用程序中,服务器通过Payload
、Header
和一个密钥(secret
)创建令牌(Token
)并将Token
发送给客户端。客户端将Token
保存在 Cookie 或者 localStorage 里面。以后客户端发出的所有请求都会携带这个令牌。可以把它放在 Cookie 里面自动发送,但是这样不能跨域。更好的做法是放在 HTTP Header 的 Authorization 字段中:Authorization: Bearer Token
功能步骤:
- 用户向服务器发送用户名、密码和验证码用于登陆系统。
- 身份验证服务响应并返回了签名的 JWT(上面包含了用户身份的内容)。
- 用户以后每次向后端发请求都在Header中带上JWT。
- 用户检查JWT并获取用户身份信息。
Token认证优势
1、无状态:token 自身包含了身份验证所需要的所有信息,使得我们的服务器不需要存储 Session 信息,大大减轻了服务端的压力。由于token 的无状态,也导致了它最大的缺点。即当后端在token有效期内废弃一个token或者更改它的权限的话,不会立即生效,一般需要等到有效期过后才可以。另外,当用户Logout 的话,token也还有效。除非,我们在后端增加额外的处理逻辑。
2、有效避免了跨站请求伪造(CSRF) 攻击:在我们登录成功获得Token之后,一般会选择存放localStorage(浏览器本地存储)中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个Token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带Token的,所以这个请求将是非法的。大部分情况下Token存放在 localstorage下都是最好的选择。
3、适合移动端应用:使用Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),所以Session 不适合移动端。token可以被客户端存储就能够使用,而且 token还可以跨语言使用。
4、单点登录友好:使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的Session 信息保存在Redis服务器上,并且还会遇到常见的Cookie跨域的问题。使用token进行认证的话, token被保存在客户端,不会存在服务器保存Session信息问题。HTTP Header的Authorization字段解决跨域问题。
Token认证问题及最佳实践
1、注销登录(退出登录,修改密码,服务端修改了某个用户具有的权限或者角色,用户的帐户被删除/暂停,用户由管理员注销)场景下 token 还有效问题:问题不存在于Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。使用 token 认证的方式就不好解决了,token一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。
最佳实践:
token 存入内存数据库:token 存入redis 内存数据库。如果需要让某个 token 失效就直接从 redis 中删除这个token。导致每次使用 token发送请求都要先从DB中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则。
黑名单机制:redis内存数据库维护一个黑名单。如果想让某个 token 失效的话就直接将这个 token 加入到黑名单,每次使用 token 进行请求的话都会先判断这个 token 是否存在于黑名单中。
修改密钥:为每个用户都创建一个专属密钥,如果我们想让某个 token 失效,我们直接修改对应用户的密钥。但是存在以下问题:(1)如果服务是分布式的,每次发出新的 token 时都必须在多台服务器上同步密钥。你需要将密钥存储在数据库或其他外部服务中,这样和 Session 认证就没太大区别。 (2) 如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。
保持令牌的有效期限短并经常轮换:导致用户登录状态不会被持久记录,而且需要用户经常登录。
用户名/密码哈希值:使用用户的用户名/密码的哈希值对 token 进行签名。如果用户名/密码更改,任何先前的令牌将自动无法验证。
2、token续签问题:token过期后如何认证,如何实现动态刷新 token,避免用户经常需要重新登录。
最佳实践:
类似Session认证:假设服务端给的 token 有效期设置为30分钟,服务端每次进行校验时,如果发现 token 的有效期马上快过期了,服务端就重新生成 token给客户端。客户端每次请求都检查新旧token,如果不一致,则更新本地的token。
每次请求都返回新token:客户端每次请求资源都生成新的token,开销会比较大。
token有效期设置到半夜:保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
用户登录返回两个token:第一个是 accessToken ,它的过期时间 token 本身的过期时间比如为半个小时,另外一个是 refreshToken它的过期时间更长一点比如为1天。客户端登录后,将 accessToken和refreshToken 保存在本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果有效,服务端就生成新的 accessToken 给客户端。否则,客户端就重新登录。但是存在以下问题:(1)需要客户端来配合。(2)用户注销的时候需要同时保证两个 token 都无效。(3)重新请求获取 token 的过程中会有短暂 token不可用的情况
总结:JWT 最适合的场景是不需要服务端保存用户状态的场景,如果考虑到 token注销和 token续签的场景话,没有特别好的解决方案,大部分解决方案都给token加上了状态,有点类似Session认证。
SSO
SSO(Single Sign On) 单点登录,用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。
应用案例:
登陆了京东金融之后,同时也成功登陆京东的京东超市、京东国际、京东生鲜等子系统。
单点登录认证优势:
1、用户角度:用户能够做到一次登录多次使用,无需记录多套用户名和密码。
2、系统管理员角度:管理员只需维护好一个统一的账号中心。
3、新系统开发角度:新系统开发时只需直接对接统一的账号中心。
功能模块:
功能模块 | 说明 |
---|---|
系统站点 | 需要登录的站点 |
SSO站点-登录 | 提供登录的页面 |
SSO站点-登出 | 提供注销登录的入口 |
SSO服务-登录 | 提供登录服务 |
SSO服务-登录状态 | 提供登录状态校验/登录信息查询的服务 |
SSO服务-登出 | 提供用户注销登录的服务 |
数据库 | 存储用户账户信息 |
缓存 | Redis存储用户的登录状态信息 |
用户登录状态的存储与校验:用户登录成功之后,生成AuthToken交给客户端保存。如果是浏览器,就保存在Cookie中。如果是手机App就保存在App本地缓存中。
对象 | 说明 |
---|---|
AuthToken | 直接使用UUID/GUID,如果有验证AuthToken合法性需求,可以将UserName 时间戳加密生成,服务端解密之后验证合法性。 |
登录信息 | 通常是将UserID,UserName缓存起来。 |
用户登录/登录校验:
用户登出:
跨域登录、登出:客户端存储AuthToken,服务器端通过Redis存储登录信息。由于客户端是将AuthToken存储在Cookie中的,但是Cookie是不能跨域的。
解决Cookie不能跨域的核心思路:
登录完成之后通过回调的方式,将AuthToken传递给主域名之外的站点,该站点自行将AuthToken保存在当前域下的Cookie中。
登出完成之后通过回调的方式,调用非主域名站点的登出页面,完成设置Cookie中的AuthToken过期的操作。
OAuth 2.0
OAuth 是行业的标准授权协议,用来授权第三方应用获取有限的权限。为第三方应用颁发一个有时效性的令牌 Token,使得第三方应用能够通过该令牌获取相关的资源。
应用案例:
应用网站接入了第三方登录的一般使用 OAuth 2.0 协议。例如:微信支付、支付宝支付。