淺談對OAuth理解與運用

1.什么是oauth?

2.什么時候用oauth吼鳞?

3.oauth用在哪里革砸?

4.oauth怎么用氮墨?

5.一個小demo

6.demo解析


1.什么是oauth?

oauth協(xié)議為用戶資源的授權(quán)提供了一個安全的栗弟、開放而又建議的標準污筷。oauth的授權(quán)不會是第三方初級到用戶的賬號信息(如用戶名與密碼),及第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權(quán)乍赫,因此oauth是安全的瓣蛀。oauth是Open Authorization的簡寫


2.什么時候用oauth?

為了理解OAuth的適用場合雷厂,讓我舉一個假設(shè)的例子惋增。
有一個"云沖印"的網(wǎng)站,可以將用戶儲存在Google的照片改鲫,沖印出來诈皿。用戶為了使用該服務(wù),必須讓"云沖印"讀取自己儲存在Google上的照片钩杰。
問題是只有得到用戶的授權(quán)纫塌,Google才會同意"云沖印"讀取這些照片。那么讲弄,"云沖印"怎樣獲得用戶的授權(quán)呢措左?

傳統(tǒng)方法是,用戶將自己的Google用戶名和密碼避除,告訴"云沖印"怎披,后者就可以讀取用戶的照片了。這樣的做法有以下幾個嚴重的缺點瓶摆。

(1)"云沖印"為了后續(xù)的服務(wù)凉逛,會保存用戶的密碼,這樣很不安全群井。
(2)Google不得不部署密碼登錄状飞,而我們知道,單純的密碼登錄并不安全。
(3)"云沖印"擁有了獲取用戶儲存在Google所有資料的權(quán)力诬辈,用戶沒法限制"云沖印"獲得授權(quán)的范圍和有效期酵使。
(4)用戶只有修改密碼,才能收回賦予"云沖印"的權(quán)力焙糟。但是這樣做口渔,會使得其他所有獲得用戶授權(quán)的第三方應(yīng)用程序全部失效。
(5)只要有一個第三方應(yīng)用程序被破解穿撮,就會導致用戶密碼泄漏缺脉,以及所有被密碼保護的數(shù)據(jù)泄漏。

OAuth就是為了解決上面這些問題而誕生的悦穿。


3.oauth用在哪里攻礼?


4.oauth怎么用?

OAuth在"客戶端"與"服務(wù)提供商"之間咧党,設(shè)置了一個授權(quán)層(authorization layer)秘蛔。"客戶端"不能直接登錄"服務(wù)提供商",只能登錄授權(quán)層傍衡,以此將用戶與客戶端區(qū)分開來。"客戶端"登錄授權(quán)層所用的令牌(token)负蠕,與用戶的密碼不同蛙埂。用戶可以在登錄的時候,指定授權(quán)層令牌的權(quán)限范圍和有效期遮糖。

"客戶端"登錄授權(quán)層以后绣的,"服務(wù)提供商"根據(jù)令牌的權(quán)限范圍和有效期,向"客戶端"開放用戶儲存的資料欲账。


oauth.png

(A)用戶打開客戶端以后屡江,客戶端要求用戶給予授權(quán)。
(B)用戶同意給予客戶端授權(quán)赛不。
(C)客戶端使用上一步獲得的授權(quán)惩嘉,向認證服務(wù)器申請令牌。
(D)認證服務(wù)器對客戶端進行認證以后踢故,確認無誤文黎,同意發(fā)放令牌。
(E)客戶端使用令牌殿较,向資源服務(wù)器申請獲取資源耸峭。
(F)資源服務(wù)器確認令牌無誤,同意向客戶端開放資源淋纲。

客戶端的授權(quán)模式:
客戶端必須得到用戶的授權(quán)(authorization grant)劳闹,才能獲得令牌(access token)。OAuth 2.0定義了四種授權(quán)方式。

  • 授權(quán)碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

5.一個小demo

  '''
  import base64
  import random
  import time
  import json
  import hmac
  from datetime import datetime, timedelta
  from flask import Flask, request, redirect,             make_response

  app = Flask(__name__)

  users = {
      'lisir': ['123456']
  }
  redirect_uri = 'http://localhost:8888/client/passport'
  client_id = '666666'
  # 添加用戶名到users字典中
  users[client_id] = []
  auth_code = {}
  # 存放重定向uri
  oauth_redirect_uri = []
  #過期時間
  timeout = 3600 * 2

  #新版本token生成器
  def get_token(data):
      '''獲取token'''
      data = data.copy()
      if "salt" not in data:
          #隨機生成的一個實數(shù)本涕,在[0,1)范圍內(nèi)
          data['salt'] = random.random()
      if "expires" not in data:
          #設(shè)置過期時間
          data['expires'] = time.time() + timeout
      payload = json.dumps(data).encode('utf8')
      #生成簽名
      sig = _get_signature(payload)
      return encode_token_bytes(payload+sig)


  def get_code(uri, user_id):
      '''授權(quán)碼生成器'''
      code = random.randint(0, 10000)
      auth_code[code] = [uri, user_id]
      return code


  def verify_token(token):
      '''token驗證'''
     decode_token = decode_token_bytes(token)
      payload = decode_token[:-16]
      sig = decode_token[-16:]
      #生成簽名
      expected_sig = _get_signature(payload)
      if sig != expected_sig:
          print('token驗證失敗')
          return {}
      data = json.loads(payload.decode('utf8'))
      print(data)
      if data.get('expires') >= time.time():
          return data
      return 0

  #使用hmac為消息生成簽名
  def _get_signature(value):
      a = hmac.new(b'secret1234656', value).digest()
      return a

  #base64編碼
  def encode_token_bytes(data):
      b = base64.urlsafe_b64encode(data)
      print(b)
      return b

  def decode_token_bytes(data):
      return base64.urlsafe_b64decode(data)


  @app.route('/index', methods=['POST', 'GET'])
  def index():
      print(request.headers)
      return "hello"

  @app.route('/login', methods=['GET', 'POST'])
  def login():
      uid, pw =       base64.b64decode(request.headers['Authorization'].split('     ')[-1]).split(':')
      if users.get(uid)[0] == pw:
          return get_token(dict(user=uid, pw=pw))
       else:
          return 'error'


  @app.route('/oauth', methods=['GET', 'POST'])
  def oauth():
#處理表單登錄业汰,同時設(shè)置cookie
if request.method == "POST" and request.form['user']:
    u = request.form['user']
    p = request.form['pw']

    if "".join(users.get(u)) == p and oauth_redirect_uri:
        uri = oauth_redirect_uri[0] + '?code=%s' % get_code(oauth_redirect_uri[0], u)
        print(uri)
        expire_date = datetime.now() + timedelta(minutes=1)
        #設(shè)置cookie
        #make_response接受字符串或錯誤碼會返回一個response對象
        resp = make_response(redirect(uri))
        resp.set_cookie('login', '_'.join([u, p]), expires=expire_date)
        return resp

#驗證授權(quán)碼,發(fā)放token
if request.args.get('code'):
    auth_info = auth_code.get(int(request.args.get('code')))
    if auth_info[0] == request.args.get('redirect_uri'):
        #可以在授權(quán)碼的auth_code中存儲用戶名偏友,曾進token中
        return get_token(dict(client_id=request.args.get('client_id'), user_id=auth_info[1]))

#如果登錄用戶有Cookie蔬胯,則直接驗證成功,否則需要填寫登錄表單
if request.args.get('redirect_uri'):
    oauth_redirect_uri.append(request.args.get('redirect_uri'))
    print(oauth_redirect_uri)
    if request.cookies.get('login'):
        u, p = request.cookies.get('login').split('_')
        if users.get(u)[0] == p:
            uri = oauth_redirect_uri[0] + '?code=%s' % get_code(oauth_redirect_uri[0], u)
            print(u, p)
            print(uri)
            return redirect(uri)

    return '''
        <form action='/oauth' method='POST'>
        <p><input type=text name=user>
        <p><input type=text name=pw>
        <p><input type=submit value=Login>
    '''



  @app.route('/client/login', methods=['POST', 'GET'])
  def client_login():
      uri = '/oauth?response_type=code&client_id=%s&redirect_uri=%s' %     (client_id, redirect_uri)
    return redirect(uri)


@app.route('/client/passport', methods=['POST', 'GET'])
  def client_passport():
      code = request.args.get('code')
      uri = 'http://localhost:8888/oauth?grant_type=authorization_code&code=%s&redirect_uri=%      s&client_id=%s' % (code, redirect_uri, client_id )
      print('------------')
      print(code)
      print(uri)
      return redirect(uri)
      # return code

  #資源服務(wù)器
  @app.route('/test1', methods=['GET', 'POST'])
  def test():
      token = request.args.get('token')
      print(token)
      ret = verify_token(token)
      if ret:
         return json.dumps(ret)
      else:
          return 'error'



  if __name__ == '__main__':
      app.run(debug=True, port=8888)

  '''

6.demo解析


  對生成token和驗證token簡單的簡單說明位他,
生成token時:
生成token函數(shù)里的參數(shù)是一個字典氛濒,字典里有user_id 和授權(quán)碼,在此字典中加上一個隨機數(shù)salt和過期時間expires鹅髓,然后使用hmac生成簽名舞竿;生成簽名后將簽名與字典拼接,使用b64進行編碼

生成簽名:
生成簽名函數(shù)中窿冯,對傳來的數(shù)據(jù)骗奖,在加上密匙,然后生成簽名醒串,

驗證token:
首先對token進行base64解碼执桌,然后進行切割成原來的,字典與簽名芜赌,然后將字典部分進行生成簽名仰挣,生成后的簽名與之前切割出來的簽名進行對比驗證;驗證通過后缠沈,從切割中出來的字典里獲取過期時間膘壶,與當前時間對比,判斷是否過期

簡單舉例:
生成token
      將數(shù)據(jù)A進行生成簽名為B洲愤,將A和B拼接后編碼颓芭,
驗證token
      將token進行解碼,切割成A和B柬赐,將A進行生成簽名為C 亡问,將B和C進行對比驗證,驗證成功后從A中獲取過期時間與當前時間對比判斷是否過期躺率,

授權(quán)碼:
  生成:  隨機生成一個數(shù)字(0玛界,10000)為鍵,uri和user_id為值保存為字典
  驗證:從字典中獲取出uri悼吱,與之前oauth_redirect_uri保存的uri對比慎框,驗證。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末后添,一起剝皮案震驚了整個濱河市笨枯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖馅精,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件严嗜,死亡現(xiàn)場離奇詭異,居然都是意外死亡洲敢,警方通過查閱死者的電腦和手機漫玄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來压彭,“玉大人睦优,你說我怎么就攤上這事∽巢唬” “怎么了汗盘?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長询一。 經(jīng)常有香客問我隐孽,道長,這世上最難降的妖魔是什么健蕊? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任菱阵,我火速辦了婚禮,結(jié)果婚禮上缩功,老公的妹妹穿的比我還像新娘送粱。我一直安慰自己,他們只是感情好掂之,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脆丁,像睡著了一般世舰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上槽卫,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天跟压,我揣著相機與錄音,去河邊找鬼歼培。 笑死震蒋,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的躲庄。 我是一名探鬼主播查剖,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼噪窘!你這毒婦竟也來了笋庄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎直砂,沒想到半個月后菌仁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡静暂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年济丘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽蛀。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡摹迷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辱士,到底是詐尸還是另有隱情泪掀,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布颂碘,位于F島的核電站异赫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏头岔。R本人自食惡果不足惜塔拳,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峡竣。 院中可真熱鬧靠抑,春花似錦、人聲如沸适掰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽类浪。三九已至载城,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間费就,已是汗流浹背诉瓦。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留力细,地道東北人睬澡。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像眠蚂,于是被迫代替她去往敵國和親煞聪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容

  • OAuth是一個關(guān)于授權(quán)(authorization)的開放網(wǎng)絡(luò)標準河狐,在全世界得到廣泛應(yīng)用米绕,目前的版本是2.0版瑟捣。...
    謝謝寫閱讀 747評論 0 1
  • OAuth是一個關(guān)于授權(quán)(authorization)的開放網(wǎng)絡(luò)標準,在全世界得到廣泛應(yīng)用栅干,目前的版本是2.0版迈套。...
    常曉曉閱讀 778評論 0 0
  • http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html 作者:...
    fu文彬閱讀 347評論 0 0
  • OAuth是一個關(guān)于授權(quán)(authorization)的開放網(wǎng)絡(luò)標準,在全世界得到廣泛應(yīng)用碱鳞,目前的版本是2.0版桑李。...
    獵人1987閱讀 262評論 0 0
  • 最近發(fā)現(xiàn)公司里有很多人都不理解Oauth,而且目前國內(nèi)大部分的oauth實現(xiàn)也都是基于標準oauth2.0的改版窿给,...
    monkey01閱讀 942評論 0 5