OAuth2 认证的基本流程
在一个单位中,可能是存在多个不同的应用,比如学校会有财务的系统会有学生工作的系统,还有图书馆的系统等等,如果每个系统都用独立的账号认证体系,会给用户带来很大困扰,也给管理带来很大不便。所以需要设计一种统一登录的解决方案。比如我登陆了百度账号,进贴吧时发现已经登录了,进糯米发现也自动登录了。常见的有两种情况,一种是 SSO(单点登录)效果是一次输入密码多个网站可以识别在线状态;还有一种是多平台登录,效果是可以用一个账号(比如 QQ 账号)登录多个不同的网站。
SSO 与多平台登录
SSO 一般用于同一单位的多个站点的登陆状态保持,技术上一般参考 CAS 协议;多平台登录一般是 Oauth 体系的协议,有多种认证模式但是不具备会话管理和状态保持。
不过从本质上讲,我觉得两者都是通过可信的第三方进行身份验证,如果说同一单位的多个子系统共同只围绕一个第三方账户(可以称为认证中心)进行多平台登录验证,那么在第三方平台登录后再访问其他网站,效果和统一登录是差不多的。此外,Oauth2 还有个好处就是可以实现跨平台的登录管理,因为他的认证过程不依赖于 session 和 cookie,比如对于移动端设备,以及在前后端分离后这种登录认证方式也可以起到很大作用。
这篇文章里我就着结合之前项目中整合过的 OAUTH2 来讲一讲这种登录认证的过程。项目是基于 Shiro+ALTU 实现,参考方案 mkk/oauth2-shiro - 码云 - 开源中国 。
oauth2 的基本概念
在 Oauth 中至少是有用户,应用服务器,认证服务器这几个角色在交互。OAuth 的作用就是让” 客户端” 安全可控地获取” 用户” 的授权,与” 应用服务器” 进行互动。
OAuth2 的基本流程
用户通过浏览器访问一个应用,比如我要上慕课网学习。
- 网站要求我登录,我选择使用 QQ 登录,这里的 QQ 登录就是那个认证服务器。
- 这个时候慕课提供的 QQ 登录链接会把我带到 QQ 登录页面
- 在 QQ 的登录页面完成登录后,选择授权,也就是允许慕课网获取我的资料。
- 这个时候我们看到浏览器经过几次跳转后返回慕课网,这个时候我们已经完成了登录。
重点在于几次跳转的过程中,慕课网和 QQ 登录的服务之间还有过几次交互。
- 我们选择了授权的时候 QQ 登录服务器会根据慕课跳转到 QQ 时候给出的重定向链接返回给慕课网一个 code,这个 code 代表 QQ 的登录服务器认可慕课网这个应用服务器的这个请求是合法的予以放行.
- 慕课这个时候就会用这个 code 再次向 QQ 登录服务发起请求服务令牌(token)。
- 拿到这个令牌之后,接下来慕课需要用户的一些基本信息时就可以通过在向 QQ 服务提交的请求头里带上这个令牌,令牌验证通过就可以拿到用户资源。
这一部分的操作是应用服务器和验证服务器之间的交互,这个过程对用户是透明的。这个过程中慕课网是不需要知道用户的账号密码也可以完成对用户身份的认证,这个 token 就可以用来标识用户资源。
官方的运行流程图是这样的:
OAuth 的几种认证模式
上述讲的是 OAuth2 中支持的授权码(CODE)方式的认证流程,也是其支持的四种认证方式里最复杂的,其他的三种种包括:
- 简化模式(implicit),(在 redirect_uri 的 Hash 传递 token; Auth 客户端运行在浏览器中,如 JS,Flash)
- 密码模式(resource owner password credentials),将用户名,密码传过去,直接获取 token;
- 客户端模式(client credentials),无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源;
详细的 OAuth2 资料参考理解 OAuth 2.0 | 阮一峰的网络日志
分别适用不同场景,复杂度也比授权码模式要低,所以这里就只说说授权码模式的具体过程。
CODE 方式认证实例
假设现在有一个应用服务器跑在我本机 8000 端口,认证服务器在 8090 端口。在需要用户登录时候把用户带到以下的一个 URL.
http://localhost:8090/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http://localhost:8000/login&state=09876999 |
- response_type:表示授权类型,就是上面讲的那四种类型,这里用的是 code 方式。
- client_id:表示客户端的 ID,代表哪个应用请求验证
- redirect_uri:表示重定向 URI,验证以后的回调地址,一般用来接收返回的 code,以及做下一步处理。
- scope:表示申请的权限范围,
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。作为安全校验。
下面是验证服务器接受这个请求的控制器关键代码:
@RequestMapping("authorize") |
首先拿到这个请求以后根据请求的参数将其封装成一个 OAuthAuthxRequest
, 基本就是把请求过来的参数,方法绑定便于使用。这是由 oltu 提供的 OAuthRequest
的进一步封装。
然后判断这个请求的授权的类型是否是 code,也就是判断下请求参数的 response_type
是否为 code,可以看到目前制作了两种类型的授权。
然后根据对应的授权类型,构造对应的方法处理器。下面是 handle 的实现接口:
public void handle() throws OAuthSystemException, ServletException, IOException { |
如果以上步骤都通过的话,认证服务器会转向这个会调地址,带上发放的 Code 码,类似如下:
http://localhost:8000/login?code=bca654ab6133ab3cbc55bb751da93b1c&state=09876999 |
可以看到带回了返回的参数,以及原样返回的状态码。
应用服务器这时候拿到返回的 code 去换 token, 发起如下的一个请求:
localhost:8090/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=bca654ab6133ab3cbc55bb751da93b1c&redirect_uri=http://localhost:8000/login&scope=read%20write&state=09876999 |
与之前请求类似只是多了一个 code 字段,去验证客户端的合法性。
验证服务器会在收到 code 以后去查找是否有支持这种 code 的处理器,如果有则发放 token。
for (OAuthTokenHandler handler : handlers) { |
初始化支持的 handler
private void initialHandlers() { |
验证通过后应用服务器会接受到包含 token 的一个 json 数据:
{ |
这个 token 是有一定的有效期的,在服务端会缓存这个 token 以便下一次查询,应用客户端也应该保留这个 token,访问受限资源时候需要带上这个 token 去验证身份。
比如请求一个 API 如下:
curl -i -X GET \ |
资源服务器上使用 shiro 做安全验证,配置 OAuth2 对应的 realms 即可:
<property name="realms"> |
在这个 reamls 中根据 token 去查到用户信息,再去分发对应的资源。
自此便完成了整个 oauth2 的流程。
这个流程中认证服务系统需要配置三张数据表:
- client_details 表中存放注册的客户端数据。如回调地址,授权类型,是否信任,权限信息等
- code 中存放发放给客户端应用的 code,使用后失效,以保证安全性
- access_token 中存放用户信息、客户端和 token 的对应关系。
项目是基于 Shiro+ALTU 实现,参考方案 mkk/oauth2-shiro - 码云 - 开源中国 ,更详细的内容,可以去读读 Shengzhao Li
开源的代码
总结
本文简单介绍了几种统一认证的解决方案,然后详细介绍了 OAuth2 的认证流程,并结合实例详细介绍了 CODE 授权的流程。尽管 OAuth2 被广泛用于多平台登录解决方案,我觉得在设置 cookie、session 共享之后也可以被应用于单点登录的解决方案。
作者: K_Biao 来源:慕课网