@Lenciel

Oauth: Introduction and Tips

Terminology / Reference

  • Signed / Signature
    • 由一系列的HTTP request元素组成的一个字符串。

    这里说的HTTP request元素一般包括了Request Method & URL Query & Parameters, 并且这些元素用(consumer_secret & token_secret)组成的key进行了加密。In some cases this may be the key, plaintext, or may use simply the consumer_secret, for RSA encryption.

  • Consumer Secret
    • 由应用提供出来作为OAuth握手的保密的token
  • Consumer Key
    • 由应用随Consumer Secret一起提供,用来做OAuth的握手的key
  • Nonce / UID
    • 通常32个字符长度,由a-zA-Z0-9中的字符生成的一个独一无二的ID
  • OAuth Token
    • 由服务器或者是其他Endpoint发送的,用来作为Request或者Access的token
  • OAuth Token Secret
    • 作为特定token的响应被发送,用来进行 exchanges / refreshing.
  • Query
    • URL中的用 ? 符号隔开的一些键值对部分。键和值之间用 = 分隔,例如 ?query=looks&like=this
  • Parameter / Argument
    • 一般指Query中的键,比如 oauth_token="helloWorld"oauth_token 被称为一个 parameter 或者 argumenthelloWorld 则是它的值。
  • PLAINTEXT
    • 使用普通文本作为Signature的保存方式
  • HMAC-SHA1 1
    • Signature的保存方式,基于Secure Hash Algorithm(1),是加密的文本
  • RSA-SHA1 2
    • Signature的保存方式,基于Secure Hash Algorithm(1),由一对public/ private的key组成。
  • Service
    • 服务方指信息的提供者,在OAuth语境中,Facebook/Twitter/腾讯/新浪等就是一个个的Service
  • Signature Method
    • OAuth接受的加密算法,包括: PLAINTEXT, HMAC-SHA1和RSA-SHA1
  • Value
    • 键值对中的值
  • URL / URI
    • URL是URI的一种,你应该懂的吧

Signed Requests

本章描述的是OAuth 1.0

对request签名(Sign)是非常重要的,本章主要解释签名流程和各个参数的作用。从数据流上来说,签名的过程就是把应用所获取和所生成的信息放到一个地方去:可以是通过 OAuth 头,也可以是 Query 字符串。

Signature Base String

签名的基本组成有:request的 Method,request的 URL (如果是 OAuth Echo则是 credentials uri) 和 request的 Query String。没有加密前它看起来会是下面这样 (例子来自 twitter):

1
POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521
Signing Key

上面的 signature base 字符串会被加密。加密时用到的key就是 signing key , 是由OAuth Consumer SecretToken Secret& 字符连接起来组成的:

1
kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE

Note: 如果是使用RSA或者xAuth, signing key 可能只有 Consumer Secret 部分外加一个可以省略的 & 。 更多相关信息可以从mashape-oauth/lib/oauth.js的 233行和 238行了解。


Signature编码

有了 signature base stringsignature key 之后,就需要从这两个字符串中提取信息完成编码。编码的方式共有三种:PLAINTEXT, HMAC, 或者是 RSA。

PLAINTEXT

没有任何编码,直接传 Signature Key

HMAC-SHA1

这种编码方式下,二进制格式的key被用来更新base,然后被编码成 Base64 放到signature string里面:

1
tnnArxj06cWHq44gCs1OSKk/jLY=
RSA-SHA1

这种更复杂但是更安全的方式是根据Signature Base来生成一对private keypublic key进行加密。

然后在服务端,会用key来验证编码后的 oauth_signature 字段。

Note: mashape-oauth/tests/oauth.js的第74行说明了如何使用生成的private key来对signature base进行编码。

OAuth请求头

OAuth请求头包括了oauth_signatureoauth_signature_method 等参数及值。这些oauth_* 参数一般会用名字和其他复杂的规则排序,相互之间用 , 或者是空格分隔。下面是一个取得Twitter的Request Token的例子:

1
2
3
4
5
6
7
8
9
10
11
12
POST /oauth/request_token HTTP/1.1
User-Agent: themattharris' HTTP Client
Host: api.twitter.com
Accept: */*
Authorization:
        OAuth oauth_callback="http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F",
              oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
              oauth_nonce="ea9ec8429b68d6b77cd5600adbbb0456",
              oauth_signature="F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D",
              oauth_signature_method="HMAC-SHA1",
              oauth_timestamp="1318467427",
              oauth_version="1.0"

oauth_callback 是整个认证过程完毕后的回调地址,默认情况下服务方会提供一个 oauth_callback_confirmed token。

下面是一个response的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HTTP/1.1 200 OK
Date: Thu, 13 Oct 2011 00:57:06 GMT
Status: 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 146
Pragma: no-cache
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Cache-Control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
Vary: Accept-Encoding
Server: tfe

oauth_token=NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0&
oauth_token_secret=veNRnAWe6inFuo8o2u8SLLZLjolYDmDP7SzL0YfYI&
oauth_callback_confirmed=true

可以看到, 200 response 以及 oauth_token, oauth_token_secretoauth_callback_confirmed 参数表示这次OAuth请求是成功的。接下来你就可以用 oauth_token_secret 来生成你的签名作为 access token 然后使用 oauth_token参数发送出去进行认证。

一般来说, oauth_token 发送的格式是 ?oauth_token=[token] ,在认证的endpoint收到之后会进行一次 3-Legged OAuth 1.0a 并返回 oauth_tokenoauth_verifier。返回的参数也会被用到 Access Token request3中去。

OAuth 1.0a (one-legged)

一般被叫成 two-legged 的OAuth其实是只有一步的。

  1. 应用发送一个 signed request 到服务提供商,request里包括:
    • oauth_token Empty String
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
  2. 服务提供商验证后,提供相应的资源供应用访问。
  3. 应用请求可以访问的资源。

这种最简单的方式当然也是安全是漏洞最多的方式。通常如果你都已经想到要用OAuth了,就不该考虑这么简陋的方式了。


Note: Google 要求请求里面要带一个不是oauth开头的参数叫 xoauth_requester_id4,这个要求在OAuth2里面过期了。


OAuth 1.0a (two-legged)

真正的two-legged是1.0a版本的OAuth。

  1. 应用发送一个 signed request到服务提供商请求一个 Request Token,request里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version 可选
  2. 服务提供商返回 Request Token:
    • oauth_token
    • oauth_token_secret
    • … 其他额外的参数
  3. 应用再次发送signed request来用Request TokenAccess Token,请求中包括:
    • oauth_token Request Token
    • oauth_consumer_key
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version
  4. 服务提供商返回 Access TokenToken Secret,整个payload的参数和第二步一样主要是oauth_tokenoauth_token_secret
  5. 应用使用oauth_tokenoauth_token_secret 来访问被权限保护的资源。

这里我们可以看到安全性被增强了,而应用开发者不会有太多的工作,用户更是完全觉察不到。

OAuth 1.0a (three-legged)

最完备同时也是带来最多麻烦的一个版本,特别是引入了需要用户操作来确认的部分,增加了开发和交互上的复杂度。一开始推出的时候,让很多用户感到不知所措。

  1. 应用发送一个 signed request到服务提供商请求一个 Request Token,request里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
    • oauth_callback
  2. 服务提供商返回 Request Token:
    • oauth_token
    • oauth_token_secret
    • oauth_callback_confirmed
    • … Additional Parameters / Arguments
  3. 返回包含下面参数的url
    • oauth_token
  4. 弹出窗口访问返回的url,要求用户授权
  5. 用户授权
  6. 返回到应用中,并保存下面的参数:
    • oauth_token
    • oauth_verifier
  7. 应用发送signed request,用Request Token / VerifierAccess Token, 请求中包括:
    • oauth_token Request Token;
    • oauth_consumer_key
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version
    • oauth_verifier
  8. 服务提供商返回 Access TokenToken Secret,整个payload的参数和第二步一样主要是oauth_tokenoauth_token_secret
  9. 应用使用oauth_tokenoauth_token_secret 来访问被权限保护的资源。

Note:第6步 如果 oauth_verifier 没有被发送,那么就会认证失败。只有极少数的实现可以接受只发送oauth_token,这样的服务提供商被认为是不完整的实现了OAuth 1.0a 3-Legged。


OAuth 1.0a (Echo)

非主流的一种实现,但是确实是存在的:发明者是Twitter的Raffi。这种实现允许在首次发送的请求token里面多带两个header,这样可以通过代理的方式在代理服务商那里对原始服务商的用户进行认证。

  1. 应用发送一个 signed request到代理服务提供商,request里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
    • oauth_callback

    还包括额外的header:

    • X-Auth-Service-Provider
    • X-Verify-Credentials-Authorization
  2. 代理服务商拿到额外的header信息到原始服务商认证
  3. 代理服务商认证通过后,可以返回受限资源的url给应用。

OAuth 1.0a (xAuth)

xAuth是一种桌面程序或者手机程序(没有使用webview等控件不能完成完整流程的程序)使用的OAuth方式。它通过提供用户的emailpassword给服务器提供商来换取access token

这种方式返回的一般是具有只读性质的access token,并且这种token能操作的资源也是有限的。比如Twitter的DM(类似私信)就不能使用xAuth而必须用完整的three-legged流程获取token才能取到。

  1. 应用请求用户的Credentials
  2. 应用发送一个 signed request到服务提供商请求一个 Access Token,request里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
    • oauth_callback

    额外还包括:

    • x_auth_mode = client_auth
    • x_auth_username
    • x_auth_password
    • x_auth_permission 可选;5
  3. 服务提供商验证用户的Credentials之后返回Access Token
    • oauth_token
    • oauth_token_secret
  4. 应用使用Access Token来访问被权限保护的资源。

OAuth 2 (two-legged)

目前为止最容易解释的一种流程,也被称为 Client Credentials 认证流程6,也有一种 Resource Owner Password 流程属于这类。

Client Credentials

  1. 应用发送请求给服务提供商:
  2. grant_type = client_credentials

如果不是用的 Authorization header: - client_id - client_secret 2. 应用服务商返回 Access Token等信息: - access_token - expires_in - token_type

Resource Owner Password

基本上就是 OAuth 1.0a Echo 流程,但是去掉了签名等复杂的部分。

  1. 应用向resource owner(一般就是用户)请求credentials
    • username
    • password
  2. 应用向服务提供商发送request,请求内容为:

    • grant_type = password
    • username
    • password

    其格式如下:

     grant_type=password&username=my_username&password=my_password
    

    如果不是用的 Authorization header, 下面的也需要被放到request里面:

    • client_id
    • client_secret

    整个加起来回是:

     grant_type=password&username=my_username&password=my_password&client_id=random_string&client_secret=random_secret
    
  3. 应用服务商返回 Access Token等信息:

    • access_token
    • expires_in
    • token_type

OAuth 2 (three-legged)

同样去掉了很多复杂的步骤。

  1. 应用把用户引导到认证页面:
    • client_id
    • redirect_uri
    • response_type7
    • state 可选; 防止CSRF8
    • scope 可选; 你可以获取的资源范围

    一个例子(为了可读性没有进行Encode):

    https://oauth_service/login/oauth/authorize?client_id=3MVG9lKcPoNINVB&redirect_uri=http://localhost/oauth/code_callback&scope=user
    
  2. 用户登录到服务中,批准应用申请权限。
  3. 服务提供商把用户重定向到 redirect_url 并带上:
    • code
    • state
  4. 应用使用 code 用来请求 Access Token:
    • client_id
    • client_secret
    • code
    • redirect_uri 可选;9
    • grant_type = "authorization_code" [^7]
  5. 如果 client_idclient_secret 正确,服务提供商调用回调到 redirect_url 返回 access_token等信息:
    • access_token
    • expires_in
    • refresh_token
  6. 应用保存 access_token 并使用。
    • 一般来说保存到session或者是cookie里,然后放在 Authorization: [Bearer] access_token header里面用,其中[Bearer]Header Authorization Bearer Name,如Bearer, OAuth, MAC等。

有趣的事实: 有些RFC里面的规定,比如scope的分隔符用空格等,根本没有人遵守。所以开发者根本不知道API会在下个版本变成什么样子。


OAuth 2 (refresh token)

在OAuth2中,access_token一般是有有效期的。一个过期的token被使用时,服务器会返回一个token过期的错误,并带上refresh_token。应用使用refresh token获取新的access_token会比前面描述的流程简单得多。

  1. 发送请求到服务提供商的Refresh Token URI:
    • grant_type = "refresh_token"
    • scope 可选; 更新时不能指定之前没有的scope
    • refresh_token
    • client_id
    • client_secret
  2. 服务提供商验证参数后返回:
    • access_token
    • issued_at

Tips & Tricks

生成Access Token和Refresh Key

最好使用uuid,也就是固定长度的随机字符组成的字符串。

例子

1
2
var OAuth = require('mashape-oauth').OAuth,
    access_token = OAuth.nonce(/* Length, Default 32 */);

References

  1. Authorizing with OAuth - Flickr Documentation
  2. OAuth on Bitbucket - Bitbucket Documentation
  3. OAuth Documentation - Twitter Documentation
  4. OAuth Extended Flows
  5. 2-Legged OAuth - OAuth-PHP
  6. OAuth for Consumer Requests
  7. OAuth Example - term.ie
  8. OAuth 1.0 Guide - Heuniverse
  9. OAuth 1.0a Diagram
  10. OAuth Wiki
  11. 2-Legged OAuth 1.0 & 2.0 - DZone
  12. OAuth & OAuth2 - Google Documentation
  13. What is 2-legged OAuth? - Nerdbank
  14. List of Service Providers - Wikipedia
  15. OAuth Echo - mobypicture
  16. OAuth Echo - Twitter
  17. Advanced API - Vimeo Developer();
  18. About xAuth - Twitter xAuth Documentation
  19. Implementing Sign-in - Twitter Sign-in Documentation
  20. RFC6749 - IETF
  21. Web Application Flow - Github OAuth2
  22. OAuth2 Quickstart - Salesforce
  23. Authentication Mechanisms - Geoloqi
  24. Understanding Web Server OAuth Flow - Salesforce
  25. CSRF & OAuth2 - Springsource
  26. OAuth v2-31 - IETF
  27. Resource Owner Flow - Hybris

tmux: Introduction and Tips

tmux screenshot 1

简介

tmux ,其实就是 terminal multiplexer 的简称。使用tmux你可以把多个任务同时运行起来,使用不同的 tmux 窗口来查看它们。你也可以 detach 一个 session,也就是让一个窗口的活动,比如编译这种耗时你又不希望断开的活动,放到后台去运行。如果你使用过 screendetach 一个 session 应该非常熟悉。其实初用 tmux 的时候,它很大程度上就像一个 GNU-Session 外加很多窗口管理的功能。而且由于 tmux 使用了 client-server 架构,我们可以在一个总控的地方去操作所有的窗口和pannel,甚至可以在一个窗口里面切换不同的 session

tmux的安装

用你的Linux package manager或者如果你和我一样在OSX可以用brew。另外,iTerm2集成了tmux的支持,它也是很多Mac上的程序员最爱的Terminal。

创建一个具名的Session

由于使用 tmux 可以在一个电脑上创建多个 session ,为了更好的管理它们我们一般可以使用名字来辨识这些 session 。 比如下面的命令可以创建一个叫 basicsession

1
$ tmux -new -s basic

回车之后就会进入一个新的 session 里面。可以看到具名的sessionterminal和正常打开iTerm2大致相同,没有特别之处。 这个时候我们敲exit就会回到原来的terminal中去。

Detaching 和 Attaching

使用tmux一大好处就是我们可以启动terminal,运行一个任务在后台,然后detach这个session。如果在一般的session里面工作,一旦我们关闭了窗口,跑在里面的所有程序都会被退出。 但是如果是使用了detach,我们可以再attach回去。下面演示一个例子。

在创建的具名session “basic”里面运行top,然后使用Ctrl-b + ddetach这个session

首先来学习一下Ctrl+b这样的Command Prefix。因为tmux是一个terminal管家,我们需要有一个办法告诉tmux我们敲击的是需要tmux处理的命令还是传给terminal的。如果定义了Ctrl-b为命令前缀,就是说我们一定要先敲这个前缀,然后执行一个命令,比如d,表示我们要detach。要记住前缀输入之后要松开手,不要在不松手的情况下发命令给tmux

由于这个前缀是可以自定义的,所以后面我们记为Prefix而不再用Ctrl-b

然后我们可以使用下面的命令对session进行listattachdelete

1
2
3
$ tmux ls
0: 1 windows (created Thu Sep 27 10:16:16 2012) [121x22]
basic: 1 windows (created Thu Sep 27 14:32:50 2012) [122x22]

可以看到目前有两个存活的session,一个是刚刚创建的basic

1
$ tmux kill-session -t 0

杀掉我们不需要的那个

1
2
$ tmux ls
basic: 1 windows (created Thu Sep 27 14:32:50 2012) [122x22]

再次attach的时候可以不带-t,因为只有basic这个session还活着。

1
$ tmux attach

后面我们可以看到在session之间进行切换还有更多更方便的办法。

窗口

很多时候我们都需要打开窗口运行多个任务。这种情况比较适用于tmux的窗口概念:用起来和现代操作系统里面的tab类似。

新建一个窗口很容易

1
tmux new -s windows -n shell

-s是对session进行命名的,-n是用来对窗口进行命名的。

  • 在当前的session里面新建一个窗口: Prefix+C
  • 要给窗口命名:Prefix+
  • 在已有的窗口间跳转: Prefix+n/Prefix+p
  • 窗口较多的时候跳转: Prefix+序列号
  • 要关掉窗口: exit或者Prefix+&
  • 要搜索窗口:Prefix+f或者 Prefix+w

分栏

  • 竖分: Prefix+%
  • 横分: Prefix+"
  • 在分栏中切换: Prefix+o
  • 在分栏中切换: Prefix+方向键
  • 在不同的布局间切换: Prefix+space
  • 关闭: Prefix+x

命令行模式

Prefix+:

取得所有的快捷键

Prefix+?

二、配置tmux

首先在系统设置里面把CapsLock这枚废材按键map成ctrl。然后是把Prefix配置成ctrl+a而不是ctrl+b,这样主要是为了按起来方便顺手。

另外可以让窗口的序列号从1开始分配,这样初始窗口不会是0,那个你需要手伸很远才能按到的键。

1
set -g base-index 1

同理分栏的序列号也可以从1开始:

1
setw -g pane-base-index 1

另外一般需要把发送命令的延迟设置为没延迟

1
set -sg escape-time 1

完整的配置文件在这里

(tmux.conf) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# zsh is kinda tight
set-option -g default-shell $SHELL

# copy and paster
set-option -g default-command "reattach-to-user-namespace -l zsh"

# look good
set -g default-terminal "screen-256color"

# act like GNU screen
set -g prefix C-a
unbind C-b

set -sg escape-time 1
set -g base-index 1
setw -g pane-base-index 1

bind r source-file ~/.tmux.conf \; display "Reloaded!"

bind - split-window -v
bind | split-window -h

# act like vim
setw -g mode-keys vi
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
bind -r C-h select-window -t :-
bind -r C-l select-window -t :+
unbind [
bind ` copy-mode
unbind p
bind p paste-buffer
bind -t vi-copy v begin-selection
bind -t vi-copy y copy-selection

# after copying to a tmux buffer, hit y again to copy to clipboard
bind y run "tmux save-buffer - | reattach-to-user-namespace pbcopy"

# resize pane
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# enable mouse
setw -g mode-mouse on
set -g mouse-select-pane on
set -g mouse-resize-pane on
set -g mouse-select-window on


setw -g window-status-fg cyan
setw -g window-status-bg default
setw -g window-status-attr dim
setw -g window-status-current-fg white
setw -g window-status-current-bg red
setw -g window-status-current-att bright

set -g status-interval 60
set -g status-justify centre
setw -g monitor-activity on
set -g visual-activity on

set -g terminal-overrides "xterm*:XT:smcup@:rmcup@:kUPS=\eOA:kDN5=\eOB:kLFT5=\eOD:kRIT5=\eOC"
set -g history-limit 30000
setw -g alternate-screen on
set -s escape-time 50

set-window-option -g window-status-current-format "#[fg=colour235, bg=colour27]⮀#[fg=colour255, bg=colour27] #I ⮁ #W #[fg=colour27, bg=colour235]⮀"
source "/Users/lenciel/Library/Python/2.7/lib/python/site-packages/powerline/bindings/tmux/powerline.conf"