OAuth是一个关于授权的开放网络标准。目前发行到第二版本。
1、应用场景
第三方开发了一个微信公众号,但是需要读取用户的微信信息如身份信息,地址性别出生日期等,在自己的网站上展示。
只有第三方在获得了用户授权,微信才会同意向第三方提供这些信息。
传统的方法是用户把用户密码等关键信息告诉第三方,第三方读取微信的信息。
缺点:
1、第三方为了后面的业务,会保存用户的密码,不安全。
2、微信得部署一个密码登陆,但是密码登陆目前也不能百分百确认身份
3、第三方相当于拥有了用户在微信上的全部权力,但用户没办法限制第三方获得授权的范围和有效期
4、用户只有修改密码才能取回第三方的权力,但是会导致其他第三方授权也同时失效
5、第三方的服务和程序一旦被破解,就会导致用户密码泄漏。
2、名词定义
1、Third-party application 第三方应用程序 = 第三方公众号
2、HTTP service HTTP服务提供商=微信
3、Resource Owner 资源拥有者 = 用户
4、User Agent 用户代理 = 浏览器
5、Authorization server 认证服务器 = 服务提供商,即例子中的微信,专门用来处理认证授权的服务器
6、Rescource server 资源服务器 = 服务提供商存放用户生成的资源的服务器,可以和Authorization Server是同一台服务器,也可以分开。
3、OAuth的思路
OAuth在客户端和服务提供商之间,设置一个授权层。“第三方”不能直接登陆“微信”,只能通过登陆授权层,以此将用户和“第三方”区分开来。“第三方”登陆授权层说用到的令牌token,与用户的密码不一样。用户可以在登陆的时候,指定授权层令牌token的权限范围和时效。
4、运行流程
图来源于网络:
流程解释
A、用户打开客户端后,第三方要求用户给予授权 B、第三方获得了用户的授权 C、第三方使用上一步的授权,向认证服务器申请令牌 D、认证服务器对第三方认证完成后,确认无误,返回令牌 E、第三方使用令牌,向资源服务器获取用户资源 F、资源服务器确认令牌无误,返回第三方需要的资源
5、授权模式
1、授权码模式(authorization code)
2、简化模式(implicit)
3、密码模式(resource owner password credentials)
3、客户端模式(client credentials)
6、授权码模式
授权码模式是功能最完整流程最严密的授权模式,本处只讨论这个。他是通过第三方的后台服务器与服务厅上的认证服务器进行互动。
图片源于网络:
流程解释:
A、用户访问第三方客户端,第三方将用户导向认证服务器
B、用户选择是否给予第三方授权
C、假设用户授权,认证服务器将用户导向第三方事先指定的“重定向URI”,同时附上一个授权码code
D、第三方通过授权码,结合之前指定的“重定向URI”发送到认证服务器申请令牌,这一步用户不可见
E、认真服务器校验授权码和“重定向URI”,确认无误,返回第三方客户端访问令牌(access token)和更新令牌(refresh token)
步骤:
1、A中客户端申请认证的URI,包含以下参数:
response_type:代表授权类型,此处为“code”,即授权码模式的授权码
client_id:第三方客户端的id,一般是服务提供方针对第三方客户端app的一个id,由服务提供方给的
redirect_uri:重定向的URI,认证服务器认证完后重定向到我们的URI,一般也是需要在服务提供方填写
scope:申请的权限范围。
state:客户端状态值,任意值,认证服务器原文返回。
//向微信服务器发送认证代码
public String getOauthPageUrl(String redirectUrl, OauthScope scope, String state) {
BeanUtil.requireNonNull(redirectUrl, "redirectUrl is null");
BeanUtil.requireNonNull(scope, "redirectUrl is null");
String userState = StringUtil.isNullOrEmpty(state)?"STATE":state;
String url = StringUtil.urlEncode(redirectUrl);
StringBuilder stringBuilder =
new StringBuilder("https://open.weixin.qq.com/connect/oauth2/authorize?");
// 此处的appid其实就是第三方客户端在微信公众平台的的client_id
stringBuilder.append("appid=")
.append(this.config.getAppId())
// 重定向到我们页面的URI,一般有域名的绑定,微信公众平台就需要绑定域名,才能跳转
.append("&redirect_uri=")
.append(url)
// response_type=code不变,scope分为snsapi_base和snsapi_userinfo,两个获取的信息
// 数量不一样,一个可以静默授权微信直接跳转,一个需要用户在认证页点同意才会成功授权。
.append("&response_type=code&scope=")
.append(scope.toString())
// 针对第三方应用当前认证的状态码,用于给第三方客户端区别认证场景。
.append("&state=")
.append(userState)
// 微信公众平台自带的一个结尾
.append("#wechat_redirect");
return stringBuilder.toString();
}
2、C步骤中,服务器回应客户端的URI中,包含这些参数:
code:授权码,有时限,一般10分钟,且只能使用一次。这个code生成时候与传入的client_id和重定向URI是绑定的。
state:第三方客户端传过来的state原文回传。
public class VerifyController extends BaseController { // 此为重定向URI的处理方法,接收code、state和refer,其中refer是自身逻辑用的 public static void verify(String code, String state, String refer) { SysLogger.info(“In patient verify.code=%s, state=%s, refer:%s”, code, state, refer); } }
3、D步骤中,客户端向认证服务器申请令牌token,包含一下参数:
grant_type:标识授权模式,这里固定是authorization_code
code:上一步认证服务器返回的授权码
redirect_uri:重定向的uri且和A步骤的保持一致
client_id:客户端的id,此处为appid
public class VerifyController extends BaseController {
// 此为重定向URI的处理方法,接收code、state和refer,其中refer是自身逻辑用的
public static void verify(String code, String state, String refer) {
SysLogger.info("In patient verify.code=%s, state=%s, refer:%s", code, state, refer);
}
}
4、E步骤中,认证服务器返回的响应,包含一下参数:
access_token:访问令牌
token_type:令牌类型,可以是bearer或者mac
expires_in:过期时间,秒为单位
refresh_token:更新令牌,用于刷新access_token
scope:权限范围
{
OauthGetTokenResponse getTokenResp = oauthAPI.getToken(code);
String openId = getTokenResp.getOpenid();
String unionId = getTokenResp.getUnionid();
String accessToken = getTokenResp.getAccessToken();
SysLogger.info("get wxdata.openId:%s,uniondId:%s,accessToken:%s",
openId, unionId, accessToken);
}
// 此处除了上面的基本信息外,还附带了微信帐号在微信的一个openid和微信关联应用中的unionId
注:加入是一个强关联的应用,需要把上面的信息都保存,并且与用户的id有关,可以存放于数据库中或者redis中,通过id-tokenDTO的方式关联,每次需要从资源服务器中获取用户资源时,先校验是否是在expires_id里面,若不是则通过refresh_token请求新的token然后刷新token,若无过期则直接使用access_token来请求资源。
本例子由于只是登陆的时候简单的获取用户信息,没有对access_token进行存储,获得基本信息和openid后则跟自己数据库中用户信息绑定,只起到一个简单的登陆作用。但其实更好的办法还是存储access_token,首先微信服务器对于access_token的获取接口调用是有频率限制的,假设10w的请求,一天活跃用户到达30000,每天访问3次每次都重新调用,一下子就满了。
5、令牌的更新,包含一下参数:
grant_type:标识授权的木虱,此处应该是固定值:refresh_token
refresh_token:更新令牌,通过E步骤中获取到的refresh_token
scope:申请授权的范围,不可以超过上一次的范围,忽略则表示跟上一次一样。
返回的参数和上面的是一样的
7、简化模式
如图:
流程解释:
A、客户端将用户导向认证服务器
B、用户决定时候授权给第三方客户端
C、假设用户授权,认真服务器将用户向导到指定的URI,并在URI的HASH部分包含了访问令牌
D、浏览器向资源服务器发出请求,其中不包括上一步的hash值
E、资源服务器返回一个页面,其中包含的代码可以获取hash值中的令牌
F、浏览器执行上一步网页中的代码脚本,提取出令牌
G、浏览器将令牌返回客户端
8、密码模式
如图:
流程解释:
A、用户向客户端提供用户名和密码
B、客户端将用户名和密码发给认证服务器,然后请求令牌
C、认证服务器确认无误后,向客户端发送令牌
步骤
1、B步骤中客户端发送的http请求包含这些参数:
grant_type:授权类型,此模式下固定为“password”
username:用户帐号
password:用户密码
scope:申请的授权范围
注意:
这种和应用场景中传统的方法还是有区别的
1、第三方客户端不需要存储用户的帐号和密码,防止第三方泄漏和避免了用户更换密码导致授权失效
2、实际上是通过模拟的一次用户登陆,获取随后用到的access_token。
3、同样存在请求过程中泄密的问题,得确保用户输入账号密码的发送到客户端的安全,以及客户端向认证服务器发送用户帐号密码的过程信息安全。
9、客户端模式
如图:
流程:
A、客户端向认证服务器进行身份认证,并要求一个访问令牌
B、认证服务器确认无误后,向客户端提供访问令牌。
步骤:
1、在A步骤中,客户端向认证服务器发送的请求,包含这些参数:
grant_type:授权类型,此处固定为 client_credentials
scope:授权范围
注意:
这种正常第三方都跟服务提供方有合作关系,或者在服务提供方存有记录等,通过客户端内部直接获取授权。信任度高。