技术漫谈 | OAuth 2.0 In Action


原创作者:万里

前言
最近公司项目需要使用OAuth 2.0对接客户的用户系统,而我以前只是知道有OAuth2.0这个协议,但是没有接触过,所以乘这个机会把OAuth 2.0相关知识梳理一遍,顺便使用Java语言开发一个Demo实现对接Gitlab.这里感谢同事钟亮和吴鹏对我工作上帮助和支持。
在接触一项新东西之前肯定要对他进行一个了解. 网上关于OAuth2.0的资料非常的多,这里只做简要叙述.首先要找到什么是OAuth 2.0。开放授权(OAuth 2.0)是一个授权协议,它允许软件应用代表(而不是充当)资源拥有者去访问资源拥有者的资源。应用向资源拥有者请求授权, 然后取得令牌(token), 并用他来访问资源。这一切都不需要去充当资源拥有者的身份, 因为令牌明确表示了被授予的访问权限。现如今越来越多的应用使用OAuth 2.0来授权,比如常见的使用QQ登录。

OAuth 2.0授权类型有以下几种:

  1. Authorization Code (授权码模式)
  2. Implicit (简化模式)
  3. Password (密码模式)
  4. Client Credentials (客户端模式)
  5. Device Code (设备代码)
  6. Refresh Token (刷新令牌)

OAuth是一个复杂的安全协议, 他需要不同的组件互相通信。但从根本上来说,OAuth事务中的两个重要步骤是颁发令牌和使用令牌。令牌表示授予客户端的访问权限, 他在OAuth2.0的各个部分都起到了核心作用。尽管每个步骤的细节会因多种因素而异, 但是一个规范的OAuth事务包含以下事件:

1.资源拥有者向客户端表示他希望客户端代表他执行一些任务

2.客户端在授权服务器上向资源拥有者请求授权

3.资源拥有者许可客户端的授权请求

4.客户端接收到来的授权服务器令牌

5.客户端向受保护资源出示令牌

授权码模式(Authorization Code)
授权码模式(Authorization Code)是功能最完善,流程最严密的授权模式。他的特点就是通过客户端的后台服务器,与“服务提供商”的认证服务器进行互动。其他的模式基本上都是对授权码模式的简化和定制。翻看OAuth2.0的RFC(Request for Comments 请求意见稿)时看到他的流程如下。要看懂他就要知道其中的一些专有名词:

1. Resource Owner:资源拥有者,这里指客户的用户

2. User-Agent:用户代理, 一般指的是浏览器

3. Client:客户端, 这里指的是我们需要做对接的系统,比如下面的demo程序

4. Authorization Server:授权服务, 这里指提供授权服务的系统,比如gitlab

+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
Note: The lines illustrating steps (A), (B), and (C) are broken into
two parts as they pass through the user-agent.
Figure 3: Authorization Code Flow

下面我将以实战的方式来进一步阐述他的流程,开发之前的一些准备工作是要有一个Gitlab账号,在这里( https://gitlab.com/profile/applications
)注册一个需要开发这个程序的相关信息,Name是在Gitlab中用于标识客户端的名字,Redirect URI是客户端的一个回调地址。注册成功后我们会获得Client ID(or Application), Secret。

❖ 1.用户使用浏览器代理用户的所有行为,Client需要授权时浏览器去授权服务器获取验证码,响应代码如下:

private static final String CLIENT_ID = "37c111426318a880b3eba318c77b455835364485e8e51292fc017ace9a31fb291"; 
private static final String GITLAB_REDIRECT_URL = "https://gitlab.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=YOUR_UNIQUE_STATE_HASH";
private static final String REDIRECT_URL = "http://127.0.0.1/gitlab/callback";
@GetMapping("login")
public void login(HttpServletResponse response) throws IOException {
response.sendRedirect(String.format(GITLAB_REDIRECT_URL, CLIENT_ID, REDIRECT_URL));

}
我使用的是Web客户端,因此采用HTTP重定向的方式将用户代理重定向到授权服务器的授权端点,客户端的HTTP响应如下

HTTP/1.1 302
Location: https://gitlab.com/oauth/authorize?client_id=37c111426318a880b3eba318c77b455835364485e8e51292fc017ace9a31fb291&redirect_uri=http://127.0.0.1/gitlab/callback&response_type=code&state=YOUR_UNIQUE_STATE_HASH
Content-Length: 0
Date: Mon, 26 Aug 2019 08:45:54 GMT

❖ 2.浏览器根据Location请求授权服务器,当授权成功后用户代理将在次重定向回到客户端的 redirect_uri http://127.0.0.1/gitlab/callback
, 客户端获取到code再去请求授权服务器获取AccessToken, 代码如下:

//完整路径 /gitlab/callback
@GetMapping("callback")
public String callback(@RequestParam String code, HttpServletRequest request, HttpServletResponse response) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
Map map = new HashMap<>(8);
map.put("client_id", CLIENT_ID);
map.put("client_secret", CLIENT_SECRET);
map.put("code", code);
map.put("grant_type", "authorization_code");
map.put("redirect_uri", REDIRECT_URL);
map.put("scope", "api");
HttpEntity entity = new HttpEntity<>(JSON.toJSONString(map), headers);
//获取AccessToken
GitlabToken token = new RestTemplate().postForObject(GITLAB_TOKEN, entity, GitlabToken.class);
LOGGER.info("accesstoken is {}", token);
//获取用户信息
String userJson = new RestTemplate().getForObject("https://gitlab.com/api/v4/user?access_token={token}",String.class, token.getAccessToken());
LOGGER.info("UserJson: {}", userJson);
return userJson;

}
HTTP响应:

HTTP/1.1 302 Found
Server: nginx
Date: Mon, 26 Aug 2019 09:04:44 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 201
Cache-Control: no-cache
Location: http://127.0.0.1/gitlab/callback?code=bb846237d8a320ccd9b5bec3fcdc7a69bbe90adedf97b0157f7cef8e23883941&state=YOUR_UNIQUE_STATE_HASH

❖ 3.使用OAuth 2.0的最终目的就是获取到AccessToken, 当AccessToken获取成功就能代表用户获取其他信息比如用户详情.

相关代码我已放在 Github( https://github.com/wanlinus/gitlabauth
)上共享。

原文链接: https://mp.weixin.qq.com/s/bwr1s_UHIRkZvzIKW-WlOw

关于睿云智合
深圳睿云智合科技有限公司成立于2012年,总部位于深圳,并分别在成都、深圳设立了研发中心,北京、上海设立了分支机构,核心骨干人员全部为来自金融、科技行业知名企业资深业务专家、技术专家。早期专注于为中国金融保险等大型企业提供创新技术、电子商务、CRM等领域专业咨询服务。
自2016年始,在率先将容器技术引进到中国保险行业客户后,公司组建了专业的容器技术产品研发和实施服务团队,旨在帮助中国金融行业客户将容器创新技术应用于企业信息技术支持业务发展的基础能力改善与提升,成为中国金融保险行业容器技术服务领导品牌。
此外,凭借多年来在呼叫中心领域的业务经验与技术积累,睿云智合率先在业界推出基于开源软交换平台FreeSwitch的微服务架构多媒体数字化业务平台,将语音、视频、webchat、微信、微博等多种客户接触渠道集成,实现客户统一接入、精准识别、智能路由的CRM策略,并以容器化治理来支持平台的全应用生命周期管理,显著提升了数字化业务处理的灵活、高效、弹性、稳定等特性,为帮助传统企业向“以客户为中心”的数字化业务转型提供完美的一站式整体解决方案。