在授权登录时,OAuth2 用得比较广泛。本文详细描述了不同客户端用OAuth2标准获取用户授权的方式。
必须
保密。对原生App或者单页js应用,不能用Secret。此类应用最好别生成secret。下面对具有Web Server的web应用,web单页应用或基于浏览器的App,原生应用分别进行详述。
因为有服务器,且服务器非公开,所以是大多数的服务场景,且比较安全。
https://authorization-server.com/auth?response_type=code&
client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos&state=1234zyx
弹出提示,说明哪个应用希望访问用户的哪些内容。如果用户同意访问,则重定向到第三方服务:
https://example-app.com/cb?code=AUTH_CODE_HERE&state=1234zyx
第三方应用应该比较state是否和发出的请求一致。避免被欺骗,换成其他AUTH_CODE_HERE
POST https://api.authorization-server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
服务端返回:
{
"access_token":"RsT5OjbzRn430zqMLgV3Ia",
"expires_in":3600
}
或者遇到错误时:
{
"error":"invalid_request"
}
浏览器会加载全部代码,所以不能存储密钥和客户端密码。可以采用授权码类似,但每次请求动态生成密码,即PKCE扩展。 老的标准是用“implict”模式,直接给客户端返回token,这有安全问题。现在推荐用PKCE模式。
5d2309e5bb73b864f989753887fe52f79ce5270395e25862da6940d5
MChCW5vD-3h03HMGFZYskOSTir7II_MMTb8a9rJNhnI
可以用 example-app.com/pkce 生成密码和hashhttps://authorization-server.com/auth?response_type=code&
client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos&state=1234zyx&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
https://example-app.com/cb?code=AUTH_CODE_HERE&state=1234zyx
POST https://api.authorization-server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
code_verifier=CODE_VERIFIER
原生应用和基于浏览器应用差不多,都不能保证密钥的安全。所以也采用授权码加PKCE扩展。
创建一个“登录”按钮,点击后唤起授权App或者Web page。原生app可以创建自定义的模式scheme如:”example-app://”
假设安装了facebook app,采用如下url进入:
fbauth2://authorize?response_type=code&client_id=CLIENT_ID
&redirect_uri=REDIRECT_URI&scope=email&state=1234zyx
如果服务器支持PKCE,(自己提供的服务,应该支持PKCE)则应该带上相关参数:
如果资源方没有独立App,则应该唤起一个独立浏览器来进行登录授权。不能直接用webview,这样无法保证是资源方提供的服务,容易被钓鱼。 iOS 9以后,可以用”SafariViewController”来打开嵌入式浏览器。它与独立浏览器共享cookie,也可以看到地址栏。还能阻止app偷窥以及修改浏览器内容,所以可以认为安全。
https://facebook.com/dialog/oauth?response_type=code&client_id=CLIENT_ID
&redirect_uri=REDIRECT_URI&scope=email&state=1234zyx
如果服务支持PKCE,则应该带上相关参数。
用户点击“同意授权(Approve)”后,将被重定向到应用服务:
fb00000000://authorize?code=AUTHORIZATION_CODE&state=1234zyx
应用服务首先应该校验state,再用code去获取访问token。
与web server获取访问token基本相同,但不再带密钥secret,如果服务支持PKCE 则带上相关参数如code_verifie,如下:
POST https://api.authorization-server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
code_verifier=VERIFIER_STRING
授权服务校验并返回访问token。如果支持PKCE,则服务应该知道code是挑战生成,需要比较code_verifier运算后与挑战所带的hash是否一致。这样可以支持一些不支持秘钥secret的客户端。
密码方式应该是同一家单位的不同服务。如微信桌面版和移动版。
POST https://api.authorization-server.com/token
grant_type=password&
username=USERNAME&
password=PASSWORD&
client_id=CLIENT_ID
没有secret字段,因为假设大多数这种场景是移动或桌面应用。
某些情况下,应用需要授权访问服务提供者的服务,但不是代表用户,而是应用自身。如获取客户端访问统计数据。可以在后端用POST方法如下:
POST https://api.authorization-server.com/token
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
服务提供方校验后,可以像其他方式一样返回token。
拿到访问token后,就可以访问资源了,如下:
curl -H "Authorization: Bearer RsT5OjbzRn430zqMLgV3Ia" \
https://api.authorization-server.com/1/me
一定要用https协议,并且不要忽略无效证书。Https是唯一能够保护请求被拦截和修改的方式。
原生app或者浏览器app为何不能直接放密钥?因为app是完全控制在用户手中的。可以用各种手段进行脱壳,反编译,调试,监听。 密钥可以直接在客户端采用strings等命令列出来。 就算进行混淆,和服务端通信时也可以通过 https proxy如charles proxy监听到。