A. Spring security OAuth2 深入解析
话不多说,先上图:
分析一波:
其实不管微信或者QQ大体上都是使用这种OAuth2的基本流程:
OAuth2 在服务提供者上可分为两类:
注:这两者有时候可能存在同一个应用程序中(即SOA架构)。在Spring OAuth中可以简便的将其分配到两个应用中(即微服务),而且可多个资源获取服务共享一个授权认证服务。
主要的操作:
分析一波:
1) 第一步操作 :
注:其中 client_id和 client_secret都是授权服务器发送给第三方应用的,如:微信等一系列授权,在其平台上注册,获取其appid和secret同样道理(个人理解为账号密码)。
既然是账号秘密,总不能以get请求,也太不安全了。因此,OAuth2要求该请求必须是POST请求,同时,还必须时HTTPS服务,以此保证获取到的安全凭证(Access Token)的安全性。
2) 第二步操作 :
3) 第三步操作 :
主要的操作:
spring OAuth2中,我们配置一个授权认证服务,我们最主要有以下三点:
spring中有三个配置与这三点一一对应:
除了上面说到的 client_id和 client_secret,还需要一些服务附带一些授权认证参数。
1). Grant Type
其实OAuth2不仅提供授权码(code)这种格式授权方式,还提供几个其他类型。其中用Grant Type代表当前授权的类型。 Grant Type包括:
2). scope
其实授权赋予第三方用户可以在资源服务器获取资源,经常就是调取Api请求附带令牌,然而调取api有增删查改等功能,而scopes的值就是all(全部权限),read,write等权限。就是第三方访问资源的一个权限,访问范围。
3). accessTokenValiditySeconds
还可以设置accessTokenValiditySeconds属性来设置Access Token的存活时间。
AccessToken的存在意义:
1).
提供了对AccessToken的相关操作创建、刷新、获取。
2). DefaultTokenServices
竟然可以操作AccessToken,那么OAuth2就默认为我们提供了一个默认的DefaultTokenServices。包含了一些有用实现,可以使用它来修改令牌的格式和令牌的存储等,但是生成的token是随机数。
3). TokenStore
创建AccessToken完之后,除了发放给第三方,肯定还得保存起来,才可以使用。因此,TokenStore为我们完成这一操作,将令牌(AccessToken)保存或持久化。
TokenStore也有一个默认的实现类InMemoryTokenStore,从名字就知道是通过保存到内存进而实现保存Access Token。
TokenStore的实现有多种类型,可以根据业务需求更改Access Token的保存类型:
4). JWT Token
想使用jwt令牌,需要在授权服务中配置JwtTokenStore。之前说了,jwt将一些信息数据编码后存放在令牌,那么其实在传输的时候是很不安全的,所以Spring OAuth2提供了JwtAccessTokenConverter来怼令牌进行编码和解码。适用JwtAccessTokenConverter可以自定义秘签(SigningKey)。SigningKey用处就是在授权认证服务器生成进行签名编码,在资源获取服务器根据SigningKey解码校验。
授权认证是使用AuthorizationEndpoint这个端点来进行控制,一般使用 来进行配置。
1).端点(endpoints)的相关属性配置:
2).端点(endpoints)的授权url:
要授权认证,肯定得由url请求,才可以传输。因此OAuth2提供了配置授权端点的URL。
,还是这个配置对象进行配置,其中由一个pathMapping()方法进行配置授权端点URL路径,默认提供了两个参数defaultPath和customPath:
pathMapping的defaultPath有:
注:pathMapping的两个参数都将以 "/" 字符为开始的字符串
实际上我们上面说到的端点,其实可以看成Controller,用于返回不同端点的响应内容。
授权服务的错误信息是使用标准的Spring MVC来进行处理的,也就是 @ExceptionHandler 注解的端点方法,我们可以提供一个 对象。最好的方式是改变响应的内容而不是直接进行渲染。
资源服务器,其实就是存放一些受令牌保护的资源,只有令牌并且有效正确才能获取到资源。 内部是通过Spring OAuth2的Spring Security Authentication filter 的过滤链来进行保护。
我们可以继承,来使用 进行相关配置。
ResourceServerTokenServices 是组成授权服务的另一半。
1).若是资源服务器和授权服务在同一个应用,可以使用DefaultTokenServices
2).若是分离的。ResourceServerTokenServices必须知道令牌的如何解码。
ResourceServerTokenServices解析令牌的方法:
注:授权认证服务需要把/oauth/check_toke暴露出来,并且附带上权限访问。
(~ ̄▽ ̄)~未完待续... ...
B. JWT token封装以及自动刷新方案建议
什么是JWT
pom.xml
JWTUtil.java
用户登录操作
在前后分离场景下,越来越多的项目使用jwt token作为接口的安全机制,但存在jwt过期后,用户无法直接感知,假如在用户操作页面期间,突然提示登录,则体验很不友好,所以就有了token自动刷新需求;
方案:前端控制检测token,无感知刷新
用户登录成功的时候,一次性给他两个Token,分别为AccessToken和RefreshToken
AccessToken有效期较短,比如1天或者5天,用于正常请求
RefreshToken有效期可以设置长一些,例如10天、20天,作为刷新AccessToken的凭证
刷新方案:当AccessToken即将过期的时候,例如提前30分钟,客户端利用RefreshToken请求指定的API获取新的AccessToken并更新本地存储中的AccessToken
核心逻辑
1、登录成功后,jwt生成AccessToken; UUID生成RefreshToken并存储在服务端redis中,设置过期时间
2、接口返回3个字段AccessToken/RefreshToken/访问令牌过期时间戳
3、由于RefreshToken存储在服务端redis中,假如这个RefreshToken也过期,则提示重新登录;
老王的疑问:RefreshToken有效期那么长,和直接将AccessToken的有效期延长有什么区别
答:RefreshToken不像AccessToken那样在大多数请求中都被使用,主要是本地检测accessToken快过期的时候才使用,
一般本地存储的时候,也不叫refreshToken,前端可以取个别名,混淆代码让攻击者不能直接识别这个就是刷新令牌
缺点:前端每次请求需要判断token距离过期时间
优点:后端压力小,代码逻辑改动不大
刷新token方法未实现。
C. 请教JWT 的 Token 本地储存和前端请求问题
token可以存localStorage里面,至于你说的前端请求问题完全不知道是什么意思。
D. JWT生成token及过期处理方案
## 业务场景
在前后分离场景下,越来越多的项目使用token作为接口的安全机制,APP端或者WEB端(使用VUE、REACTJS等构建)使用token与后端接口交互,以达到安全的目的。本文结合stackoverflow以及本身项目实践,试图总结出一个通用的,可落地的方案。
## 基本思路
- 单个token
1. token(A)过期设置为15分钟
2. 前端发起请求,后端验证token(A)是否过期;如果过期,前端发起刷新token请求,后端设置已再次授权标记为true,请求成功
3. 前端发起请求,后端验证再次授权标记,如果已经再次授权,则拒绝刷新token的请求,请求成功
4. 如果前端每隔72小时,必须重新登录,后端检查用户最后一次登录日期,如超过72小时,则拒绝刷新token的请求,请求失败
- 授权token加上刷新token
用户仅登录一次,用户改变密码,则废除token,重新登录
## 1.0实现
1.登录成功,返回access\_token和refresh\_token,客户端缓存此两种token;
2.使用access_token请求接口资源,成功则调用成功;如果token超时,客户端
携带refresh\_token调用中间件接口获取新的access\_token;
3.中间件接受刷新token的请求后,检查refresh_token是否过期。
如过期,拒绝刷新,客户端收到该状态后,跳转到登录页;
如未过期,生成新的access\_token和refresh\_token并返回给客户端(如有可能,让旧的refresh\_token失效),客户端携带新的access\_token重新调用上面的资源接口。
4.客户端退出登录或修改密码后,调用中间件注销旧的token(使access\_token和refresh\_token失效),同时清空客户端的access\_token和refresh\_toke。
后端表
id user\_id client\_id client\_secret refresh\_token expire\_in create\_date del_flag
## 2.0实现
场景: access\_token访问资源 refresh\_token授权访问 设置固定时间X必须重新登录
1.登录成功,后台jwt生成access\_token(jwt有效期30分钟)和refresh\_token(jwt有效期15天),并缓存到redis(hash-key为token,sub-key为手机号,value为设备唯一编号(根据手机号码,可以人工废除全部token,也可以根据sub-key,废除部分设备的token。),设置过期时间为1个月,保证最终所有token都能删除),返回后,客户端缓存此两种token;
2.使用access\_token请求接口资源,校验成功且redis中存在该access\_token(未废除)则调用成功;如果token超时,中间件删除access\_token(废除);客户端再次携带refresh\_token调用中间件接口获取新的access_token;
3.中间件接受刷新token的请求后,检查refresh_token是否过期。
如过期,拒绝刷新,删除refresh_token(废除); 客户端收到该状态后,跳转到登录页;
如未过期,检查缓存中是否有refresh\_token(是否被废除),如果有,则生成新的access\_token并返回给客户端,客户端接着携带新的access_token重新调用上面的资源接口。
4.客户端退出登录或修改密码后,调用中间件注销旧的token(中间件删除access\_token和refresh\_token(废除)),同时清空客户端侧的access\_token和refresh\_toke。
5.如手机丢失,可以根据手机号人工废除指定用户设备关联的token。
6.以上3刷新access_token可以增加根据登录时间判断最长X时间必须重新登录,此时则拒绝刷新token。(拒绝的场景:失效,长时间未登录,频繁刷新)
2.0 变动
1.登录
2.登录拦截器
3.增加刷新access_token接口
4.退出登录
5.修改密码
## 3.0实现
场景:自动续期 长时间未使用需重新登录
1.登录成功,后台jwt生成access\_token(jwt有效期30分钟),并缓存到redis(hash-key为access\_token,sub-key为手机号,value为设备唯一编号(根据手机号码,可以人工废除全部token),设置access_token过期时间为7天,保证最终所有token都能删除),返回后,客户端缓存此token;
2.使用access\_token请求接口资源,校验成功且redis中存在该access\_token(未废除)则调用成功;如果token超时,中间件删除access\_token(废除),同时生成新的access\_token并返回。客户端收到新的access_token,
再次请求接口资源。
3.客户端退出登录或修改密码后,调用中间件注销旧的token(中间件删除access\_token(废除)),同时清空客户端侧的access\_token。
4.以上2 可以增加根据登录时间判断最长X时间必须重新登录,此时则拒绝刷新token。(拒绝的场景:长时间未登录,频繁刷新)
5.如手机丢失,可以根据手机号人工废除指定用户设备关联的token。
3.0 变动
1.登录
2.登录拦截器
3.退出登录
4.修改密码
1.3 场景:token过期重新登录 长时间未使用需重新登录
1.登录成功,后台jwt生成access\_token(jwt有效期7天),并缓存到redis,key为 "user\_id:access\_token",value为access\_token(根据用户id,可以人工废除指定用户全部token),设置缓存过期时间为7天,保证最终所有token都能删除,请求返回后,客户端缓存此access_token;
2.使用access\_token请求接口资源,校验成功且redis中存在该access\_token(未废除)则调用成功;如果token超时,中间件删除access\_token(废除),同时生成新的access\_token并返回。客户端收到新的access_token,
再次请求接口资源。
3.客户端退出登录或修改密码后,调用中间件注销旧的token(中间件删除access\_token(废除)),同时清空客户端侧的access\_token。
4.以上2 可以增加根据登录时间判断最长X时间必须重新登录,此时则拒绝刷新token。(拒绝的场景:长时间未登录,频繁刷新)
5.如手机丢失,可以根据手机号人工废除指定用户设备关联的token。
1.3 变动
1.登录
2.登录拦截器
3.退出登录
4.修改密码
# 解决方案
2.0 场景: access\_token访问资源 refresh\_token授权访问 设置固定时间X必须重新登录
1.登录成功,后台jwt生成access\_token(jwt有效期30分钟)和refresh\_token(jwt有效期15天),并缓
存到redis(hash-key为token,sub-key为手机号,value为设备唯一编号(根据手机号码,可以人工废除全
部token,也可以根据sub-key,废除部分设备的token。),设置过期时间为1个月,保证最终所有token都
能删除),返回后,客户端缓存此两种token;
2.使用access\_token请求接口资源,校验成功且redis中存在该access\_token(未废除)则调用成功;如果
token超时,中间件删除access\_token(废除);客户端再次携带refresh\_token调用中间件接口获取新的
access_token;
3.中间件接受刷新token的请求后,检查refresh_token是否过期。
如过期,拒绝刷新,删除refresh_token(废除); 客户端收到该状态后,跳转到登录页;
如未过期,检查缓存中是否有refresh\_token(是否被废除),如果有,则生成新的access\_token并返回给
客户端,客户端接着携带新的access_token重新调用上面的资源接口。
4.客户端退出登录或修改密码后,调用中间件注销旧的token(中间件删除access\_token和refresh\_token(
废除)),同时清空客户端侧的access\_token和refresh\_toke。
5.如手机丢失,可以根据手机号人工废除指定用户设备关联的token。
6.以上3刷新access_token可以增加根据登录时间判断最长X时间必须重新登录,此时则拒绝刷新token。(
拒绝的场景:失效,长时间未登录,频繁刷新)
2.0 变动
1.登录
2.登录拦截器
3.增加刷新access_token接口
4.退出登录
5.修改密码
3.0 场景:自动续期 长时间未使用需重新登录
1.登录成功,后台jwt生成access_token(jwt有效期30分钟),并缓存到redis(hash-key为
access_token,sub-key为手机号,value为设备唯一编号(根据手机号码,可以人工废除全部token,也可以
根据sub-key,废除部分设备的token。),设置access_token过期时间为1个月,保证最终所有token都能删
除),返回后,客户端缓存此token;
2.使用access\_token请求接口资源,校验成功且redis中存在该access\_token(未废除)则调用成功;如果
token超时,中间件删除access\_token(废除),同时生成新的access\_token并返回。客户端收到新的
access_token,
再次请求接口资源。
3.客户端退出登录或修改密码后,调用中间件注销旧的token(中间件删除access_token(废除)),同时清
空客户端侧的access_token。
4.以上2 可以增加根据登录时间判断最长X时间必须重新登录,此时则拒绝刷新token。(拒绝的场景:长
时间未登录,频繁刷新)
5.如手机丢失,可以根据手机号人工废除指定用户设备关联的token。
3.0 变动
1.登录
2.登录拦截器
3.退出登录
4.修改密码
4.0 场景:token过期重新登录 长时间未使用需重新登录
1.登录成功,后台jwt生成access_token(jwt有效期7天),并缓存到redis,key为
"user\_id:access\_token" + 用户id,value为access_token(根据用户id,可以人工废除指定用户全部
token),设置缓存过期时间为7天,保证最终所有token都能删除,请求返回后,客户端缓存此
access_token;
2.使用access\_token请求接口资源,校验成功且redis中存在该access\_token(未废除)则调用成功;如果
token超时,中间件删除access\_token(废除),同时生成新的access\_token并返回。客户端收到新的
access_token,
再次请求接口资源。
3.客户端退出登录或修改密码后,调用中间件注销旧的token(中间件删除access_token(废除)),同时清
空客户端侧的access_token。
4.以上2 可以增加根据登录时间判断最长X时间必须重新登录,此时则拒绝刷新token。(拒绝的场景:长
时间未登录,频繁刷新)
5.如手机丢失,可以根据手机号人工废除指定用户设备关联的token。
4.0 变动
1.登录
2.登录拦截器
3.退出登录
4.修改密码
## 最终实现
### 后端
1. 在登录接口中 如果校验账号密码成功 则根据用户id和用户类型创建jwt token(有效期设置为-1,即永不过期),得到A
2. 更新登录日期(当前时间new Date()即可)(业务上可选),得到B
3. 在redis中缓存key为ACCESS_TOKEN:userId:A(加上A是为了防止用户多个客户端登录 造成token覆盖),value为B的毫秒数(转换成字符串类型),过期时间为7天(7 * 24 * 60 * 60)
4. 在登录结果中返回json格式为{"result":"success","token": A}
5. 用户在接口请求header中携带token进行登录,后端在所有接口前置拦截器进行拦截,作用是解析token 拿到userId和用户类型(用户调用业务接口只需要传token即可),
如果解析失败(抛出SignatureException),则返回json(code = 0 ,info= Token验证不通过, errorCode = '1001');
此外如果解析成功,验证redis中key为ACCESS_TOKEN:userId:A 是否存在 如果不存在 则返回json(code = 0 ,info= 会话过期请重新登录, errorCode = '1002');
如果缓存key存在,则自动续7天超时时间(value不变),实现频繁登录用户免登陆。
6. 把userId和用户类型放入request参数中 接口方法中可以直接拿到登录用户信息
7. 如果是修改密码或退出登录 则废除access_tokens(删除key)
### 前端(VUE)
1. 用户登录成功,则把username存入cookie中,key为loginUser;把token存入cookie中,key为accessToken
把token存入Vuex全局状态中
2. 进入首页
E. 请教JWT 的 Token 本地储存和前端请求问题
点允许就可以了。不妨碍你什么。
F. 来,科普一下JWT
1. JSON Web Token是什么
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
2. 什么时候你应该用JSON Web Tokens
下列场景中使用JSON Web Token是很有用的:
Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
3. JSON Web Token的结构是什么样的
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
因此,一个典型的JWT看起来是这个样子的:
接下来,具体看一下每一部分:
Header
header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。
例如:
然后,用Base64对这个JSON编码就得到JWT的第一部分
Payload
Public claims : 可以随意定义。
下面是一个例子:
对payload进行Base64编码就得到JWT的第二部分
Signature
为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。
例如:
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
看一张官网的图就明白了:
4. JSON Web Tokens是如何工作的
在认证的时候,当用户用他们的凭证成功登录以后,一个JSON Web Token将会被返回。此后,token就是用户凭证了,你必须非常小心以防止出现安全问题。一般而言,你保存令牌的时候不应该超过你所需要它的时间。
无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中,用Bearer schema。
header应该看起来是这样的:
服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。
如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。
下面这张图显示了如何获取JWT以及使用它来访问APIs或者资源:
5. 基于Token的身份认证 与 基于服务器的身份认证
5.1. 基于服务器的身份认证
在讨论基于Token的身份认证是如何工作的以及它的好处之前,我们先来看一下以前我们是怎么做的:
HTTP协议是无状态的,也就是说,如果我们已经认证了一个用户,那么他下一次请求的时候,服务器不知道我是谁,我们必须再次认证
传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。
这种基于服务器的身份认证方式存在一些问题:
Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
CSRF : 用户很容易受到CSRF攻击。
5.2. JWT与Session的差异
相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。
Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
5.3. 基于Token的身份认证是如何工作的
基于Token的身份认证是无状态的,服务器或者Session中不会存储任何用户信息。
虽然这一实现可能会有所不同,但其主要流程如下:
注意:
5.4. 用Token的好处
无状态和可扩展性: Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。
安全: Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。即使在你的实现中将token存储到客户端的Cookie中,这个Cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话!
还有一点,token在一段时间以后会过期,这个时候用户需要重新登录。这有助于我们保持安全。还有一个概念叫token撤销,它允许我们根据相同的授权许可使特定的token甚至一组token无效。
5.5. JWT与OAuth的区别
写在最后:我为大家准备了一些适合于1-5年以上开发经验的java程序员面试涉及到的绝大部分面试题及答案做成了文档和学习笔记文件以及架构视频资料免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望可以帮助到大家。
G. jwt的token怎么生存的
引入依赖包加密解密方法。
在生产环境中,一般jwt会保存用户的名字和角色权限等信息。可以将token写到cookie里,每次前端访问后台时,可以在拦截器或者过滤器取到token,然后解密,先判断是否过期,过期就抛异常阻止其访问。然后取出信息保存到threadLocal里,方便以后调用这些信息,当后台访问完成后,从thredLocal删除此用户信息。
服务器认证以后,生成一个JSON格式的对象返回给客户端。之后客户端与服务端通信的时候,都要发回这个JSON对象。服务器完全根据这个对象认证用户身份。
H. 前端登陆实现
四种方式
Cookie 出现的原因: HTTP 协议是无状态的,每次请求都会建立一个新的链接,请求结束就会断开链接,优点就是可以节省链接资源,缺点就是无法保存用户状态。Cookie 的出现就是为了解决这个问题。
Cookie 是存储在浏览器中的,可以通过 Js 和 set-cookie 这个响应字段来进行设置。
cookie 的限制:
有了 cookie 之后,服务端就可以从客户端获取到信息,如果需要对信息进行验证,那么还需要 session
服务端在收到客户端的请求之后,会在服务器中开辟一片内存空间来存放 session
第一次登陆之后,下次再访问的时候就会携带这个 cookie,服务端就可以根据 sessionId 进行验证用户是否登陆(判断这个 sessionId 和服务端保存的 sessionId 是否一致,是否有这个 sessionId 的记录或者记录是否有效)
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。
Token 是 服务器 生成的一个字符串,作为客户端请求的一个令牌。第一次登陆之后,服务器会生成一个 Token 返回给客户端,客户端后续访问的时候,只需带上这个 Token 进行身份认证
缺点
JWT(Json Web Token)
服务端不需要存储 Token 那么服务端是怎么验证客户端传递过来的 Token 是否有效的呢?
答案:
Token 并不是杂乱无章的字符串,而是通过多种算法拼接而成的字符串
header 部分指定了这个 Token 所使用的签名算法
payload 部分表明了这个 JWT 的意图
signature 部分为 JWT 的签名,主要是为了让 JWT 不被随意的篡改
签名的部分有两个步骤
一:
二:
最后的 Token 计算如下:
单点登陆指的是公司会搭建一个公共的认证中心,公司里的所有产品的认证都可以在这个认证中心中完成,一个产品在认证中心认证之后,再去访问其他产品时就不需要再次认证
这个时候,由于 a.com 存在已登录的 Cookie 信息,所以服务器端直接认证成功。
这个时候由于认证中心存在之前登陆过的 cookie,所以不需要再输入账号密码,直接从第四步开始执行
目前我们已经完成了单点登录,在同一套认证中心的管理下,多个产品可以共享登录态。现在我们需要考虑退出了,即:在一个产品中退出了登录,怎么让其他的产品也都退出登录?
原理也不难,其实就是在携带 ticket 去请求认证中心的时候,再去请求一下认证中心的退出登陆的 api 即可
当某个产品 c.com 退出登陆时
sso 就是一个集中地验证系统。你项目内请求时,向 sso 发一个请求,他给你个 token 你扔到游览器缓存里,请求的时候放在请求头里带着。和其他验证接口一样。 他好就好在,一个账号在不同系统里都可以登录,因为不同项目可以共用这个 token。并且通过 sso 集中管理一些用户信息,你可以方便的拿用户信息。
以微信为例子
I. 鉴权必须了解的 5 个兄弟:cookie、session、token、jwt、单点登录
本文你将看到:
**“前端存储”**这就涉及到一发、一存、一带,发好办,登陆接口直接返回给前端,存储就需要前端想办法了。
前端的存储方式有很多。
有,cookie。cookie 也是前端存储的一种,但相比于 localStorage 等其他方式,借助 HTTP 头、浏览器能力,cookie 可以做到前端无感知。一般过程是这样的:
“配置:Domain / Path”
cookie 是要限制::“空间范围”::的,通过 Domain(域)/ Path(路径)两级。
“配置:Expires / Max-Age”
cookie 还可以限制::“时间范围”::,通过 Expires、Max-Age 中的一种。
“配置:Secure / HttpOnly”
cookie 可以限制::“使用方式”::。
**“HTTP 头对 cookie 的读写”**回过头来,HTTP 是如何写入和传递 cookie 及其配置的呢?HTTP 返回的一个 Set-Cookie 头用于向浏览器写入“一条(且只能是一条)”cookie,格式为 cookie 键值 + 配置键值。例如:
那我想一次多 set 几个 cookie 怎么办?多给几个 Set-Cookie 头(一次 HTTP 请求中允许重复)
HTTP 请求的 Cookie 头用于浏览器把符合当前“空间、时间、使用方式”配置的所有 cookie 一并发给服务端。因为由浏览器做了筛选判断,就不需要归还配置内容了,只要发送键值就可以。
**“前端对 cookie 的读写”**前端可以自己创建 cookie,如果服务端创建的 cookie 没加HttpOnly,那恭喜你也可以修改他给的 cookie。调用document.cookie可以创建、修改 cookie,和 HTTP 一样,一次document.cookie能且只能操作一个 cookie。
调用document.cookie也可以读到 cookie,也和 HTTP 一样,能读到所有的非HttpOnly cookie。
现在回想下,你刷卡的时候发生了什么?
这种操作,在前后端鉴权系统中,叫 session。典型的 session 登陆/验证流程:
**“Session 的存储方式”**显然,服务端只是给 cookie 一个 sessionId,而 session 的具体内容(可能包含用户信息、session 状态等),要自己存一下。存储的方式有几种:
“Session 的过期和销毁”**很简单,只要把存储的 session 数据销毁就可以。****“Session 的分布式问题”**通常服务端是集群,而用户请求过来会走一次负载均衡,不一定打到哪台机器上。那一旦用户后续接口请求到的机器和他登录请求的机器不一致,或者登录请求的机器宕机了,session 不就失效了吗?这个问题现在有几种解决方式。
但通常还是采用第一种方式,因为第二种相当于阉割了负载均衡,且仍没有解决“用户请求的机器宕机”的问题。**“node.js 下的 session 处理”**前面的图很清楚了,服务端要实现对 cookie 和 session 的存取,实现起来要做的事还是很多的。在npm中,已经有封装好的中间件,比如 express-session - npm,用法就不贴了。这是它种的 cookie:
express-session - npm 主要实现了:
session 的维护给服务端造成很大困扰,我们必须找地方存放它,又要考虑分布式的问题,甚至要单独为了它启用一套 Redis 集群。有没有更好的办法?
回过头来想想,一个登录场景,也不必往 session 存太多东西,那为什么不直接打包到 cookie 中呢?这样服务端不用存了,每次只要核验 cookie 带的“证件”有效性就可以了,也可以携带一些轻量的信息。这种方式通常被叫做 token。
token 的流程是这样的:
**“客户端 token 的存储方式” 在前面 cookie 说过,cookie 并不是客户端存储凭证的唯一方式。token 因为它的“无状态性”,有效期、使用限制都包在 token 内容里,对 cookie 的管理能力依赖较小,客户端存起来就显得更自由。但 web 应用的主流方式仍是放在 cookie 里,毕竟少操心。 “token 的过期”**那我们如何控制 token 的有效期呢?很简单,把“过期时间”和数据一起塞进去,验证时判断就好。
编码的方式丰俭由人。**“base64”**比如 node 端的 cookie-session - npm 库
默认配置下,当我给他一个 userid,他会存成这样:
这里的 eyJ1c2VyaWQiOiJhIn0=,就是 {"userid":"abb”} 的 base64 而已。 “防篡改”
是的。所以看情况,如果 token 涉及到敏感权限,就要想办法避免 token 被篡改。解决方案就是给 token 加签名,来识别 token 是否被篡改过。例如在 cookie-session - npm 库中,增加两项配置:
这样会多种一个 .sig cookie,里面的值就是 {"userid":"abb”} 和 iAmSecret通过加密算法计算出来的,常见的比如HMACSHA256 类 (System.Security.Cryptography) | Microsoft Docs。
好了,现在 cdd 虽然能伪造出eyJ1c2VyaWQiOiJhIn0=,但伪造不出 sig 的内容,因为他不知道 secret。**“JWT”**但上面的做法额外增加了 cookie 数量,数据本身也没有规范的格式,所以 JSON Web Token Introction - jwt.io 横空出世了。
它是一种成熟的 token 字符串生成方案,包含了我们前面提到的数据、签名。不如直接看一下一个 JWT token 长什么样:
这串东西是怎么生成的呢?看图:
类型、加密算法的选项,以及 JWT 标准数据字段,可以参考 RFC 7519 - JSON Web Token (JWT)node 上同样有相关的库实现:express-jwt - npm koa-jwt - npm
token,作为权限守护者,最重要的就是“安全”。业务接口用来鉴权的 token,我们称之为 access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但过短的有效期会造成 access token 经常过期,过期后怎么办呢?一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。另外一种办法是,再来一个 token,一个专门生成 access token 的 token,我们称为 refresh token。
有了 refresh token 后,几种情况的请求流程变成这样:
如果 refresh token 也过期了,就只能重新登录了。
session 和 token 都是边界很模糊的概念,就像前面说的,refresh token 也可能以 session 的形式组织维护。狭义上,我们通常认为 session 是“种在 cookie 上、数据存在服务端”的认证方案,token 是“客户端存哪都行、数据存在 token 里”的认证方案。对 session 和 token 的对比本质上是“客户端存 cookie / 存别地儿”、“服务端存数据 / 不存数据”的对比。**“客户端存 cookie / 存别地儿”**存 cookie 固然方便不操心,但问题也很明显:
存别的地方,可以解决没有 cookie 的场景;通过参数等方式手动带,可以避免 CSRF 攻击。 “服务端存数据 / 不存数据”
前面我们已经知道了,在同域下的客户端/服务端认证系统中,通过客户端携带凭证,维持一段时间内的登录状态。但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要“一次登录,全线通用”的能力,叫做“单点登录”。
简单的,如果业务系统都在同一主域名下,比如wenku..com tieba..com,就好办了。可以直接把 cookie domain 设置为主域名 .com,网络也就是这么干的。
比如滴滴这么潮的公司,同时拥有didichuxing.com xiaojukeji.com didiglobal.com等域名,种 cookie 是完全绕不开的。这要能实现“一次登录,全线通用”,才是真正的单点登录。这种场景下,我们需要独立的认证服务,通常被称为 SSO。 “一次“从 A 系统引发登录,到 B 系统不用登录”的完整流程”
**“完整版本:考虑浏览器的场景”**上面的过程看起来没问题,实际上很多 APP 等端上这样就够了。但在浏览器下不见得好用。看这里:
对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。这就需要也只能由 A 提供 A 域下存储凭证的能力。一般我们是这么做的:
图中我们通过颜色把浏览器当前所处的域名标记出来。注意图中灰底文字说明部分的变化。
谢谢大家哦
J. jwt中为啥用refresh_token去刷新access_token,直接把access_token的有效期设置长一点不行吗
access_token使用频率高,容易泄露,有效期风险就小。refresh_token使用频率低,泄露风险小。另外,不是必须要有两个token吧?