tft每日頭條

 > 生活

 > 登錄和鑒權

登錄和鑒權

生活 更新时间:2025-04-21 23:01:41

## Basic Auth

Nginx 的 basic auth , 基于 ngx_http_auth_basic_module 模塊。該模塊是 builtin 模塊,就是你安裝 Nginx 的時候,它就一起裝好了。

Basic Auth ,是一種簡單的鑒權方式,不怎麼安全。一般的 Basic Auth header 長這樣:

Authorization: Basic $(base64_encode(username:password))

客戶端通過對 username 和 password 進行 base64 加密, 放入 header 中。服務端獲取 header 中的 Authorization , 然後 decode 鑒權。

在 nginx 中,通過 auth_basic 和 auth_basic_user_file 進行配置,該語法支持的上下文有

http, server, location, limit_except

具體配置如下:

server { listen 8088; location / { auth_basic "closed site"; auth_basic_user_file conf/htpasswd; root /xxx/html; index index.html index.htm; } error_page 404 /404.html; access_log logs/blog.access.log; }

關于 auth_basic 後面的字符串,要看各大浏覽器是不是給面子了。我用 chrome 就不會顯示這個,用 edge 會顯示。

auth_basic_user_file 是用來存儲賬号密碼的。官方支持使用 "HTTP Basic Authentication" 協議來驗證用戶名和密碼。

emmm...

  • 支持通過 crypt() 方法加密: 通過 htpasswd 或者 openssl passwd命令
  • apache 基于 MD5 的 apr1 加密方式
  • 基于 RFC 2307 的實現

現在我們通過 htpasswd 生成一個:

# 直接在控制帶輸出 htpasswd -nbd iyuhp admin # 輸出到文本 htpasswd -bdc passwd iyuhp admin # 再次追加到文本 htpasswd -nbd iyuhp admin | tee -a passwd

這個時候,優雅的重啟一波 nginx:

sudo nginx -s reload

然後訪問一波, 發現已經會彈出一個用來登錄的彈窗了。

如果通過命令行訪問,可以直接通過 username:password@url訪問:

curl 'localhost:8088' --header 'Authorization: Basic aXl1aHA6YWRtaW4=' // or curl iyuhp:admin@localhost:8088


Auth Request

可能我有一個後端程序,所以我完全可以把這些 "繁重" 的用戶密碼管理,交給後台就好了。

哦,我并不是說搞個程序來生成賬号密碼,然後寫到 auth_basic_user_file 文件中。我的意思是,這個校驗的工作,讓我來吧,Nginx 你去搞别的去!

這樣做的好處是, 我可以方便的管理我的賬号體系(這裡所謂的賬号體系,是一個誇張的說法,你一個小小的 blog ,還賬号體系...)

emmm... 不管怎樣,這看起來是件有趣的事情。

Nginx 自是能考慮到這些,已然提供了一個語法 auth_request , 意思是,你如果要訪問這裡,可以,得先通過我的驗證才行。

auth_request uri | off; # off 表示關閉上文的驗證,就是我這裡我說了算,上級不要說話了 # 該語法支持的上下文為: http, server, location

那麼我們要怎麼搞呢? 别急, 我先整張圖:

登錄和鑒權(給Blog加上鑒權)1

  1. client 向 Nginx 請求資源, 因為配置了 auth_request , Nginx 會先先 /auth 請求權限
  2. /auth 将請求轉發給 Backend
  3. Backend 懵了, 你啥都沒有,請求啥? 麻溜的返回 401 。注意 , 這裡必須返回 401 (403 應該也可以,沒有試... ),返回之前, 需要添加 http header:Header().Add("WWW-Authenticate", "Basic realm=\"up to you\"")
  4. 第四步, 第五步,第六步,則是将最終的結果反饋給 client。 client 收到 Basic Auth , 彈出彈窗,讓你填用戶名密碼。
  5. 于是 client 這次攜帶 basic auth 再次請求 Nginx , 同樣, 該請求最終被 Backend 處理。 Backend 一看,呦,原來是哥們啊, 得, 給你個 http code 200 吧。
  6. location /auth 一看, http code 200 , 趕緊緩存起來。 proxy_cache_valid 200 2h; 的意思就是對 http code 為 200 的請求,緩存 2h 。
  7. location / 一看,行, 你小子通過 auth 驗證了, 這次的資源請求就放行吧。

通過上述步驟, 我們成功通過和 Backend 交互,并獲得了指定資源的訪問權。

關于 auth_request 的更多内容請看 這裡 。 附上一份配置:

location / { auth_request /auth; root /xxx/html } location /auth { proxy_pass http://localhost:1234/auth; # 處理 auth 的後端 URL # 關閉不必要的數據傳遞 proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; # proxy cache 設置 proxy_cache auth_cache; proxy_cache_valid 200 2h; proxy_cache_key $host$request_uri$cookie_session; }

使用 proxy_cache 前,需要先定義下 proxy_cache_path , 該語法的上下文是 http ,也就說,你需要在 http 層先定義該值, 比如:

proxy_cache_path path levels=1:2 keys_zone=one:10m;

  • path : 指定 cache 緩存的位置
  • levels[1] : 指定緩存的層級, 上面的配置可能最終長這樣: path/a/bc/xxxbca
  • keys_zone : 相當于這個 cache 的 alias , 使用時, 就用這個名稱。 10m 即緩存的大小。

WeChat

我現在又覺得, 搞啥賬号體系,我一個小小的 blog , 太耗時間了!

于是你想到了用驗證碼來搞好了。

那自然而然的想到了微信,想到利用公衆号, 來搞個自動獲取驗證碼呗。整個流程如下:

登錄和鑒權(給Blog加上鑒權)2

  1. client 發起一個需要鑒權的資源請求
  2. Nginx 通過 auth_request 配置,轉發給 Server
  3. Server 告訴 Nginx , 需要重定向到 login 界面獲取驗證碼,然後再提交
  4. Nginx 告訴 client ,同時 client 展示 login 界面
  5. client 通過掃碼關注公衆号,輸入預設指令(如: yzm 等) ,獲取驗證碼
  6. 微信把獲取驗證碼的請求轉發給 server , server 生成并存儲該驗證碼(設置驗證碼失效時間等),同時發送給微信
  7. client 獲取驗證碼, 填寫提交,server 驗證通過, 告知 Nginx 放行

下面再來看下更詳細的流程圖:

登錄和鑒權(給Blog加上鑒權)3

這張圖就很詳細了,這裡主要說幾個注意的點:

  • 3 - 4 步, 這裡,返回 401 後,需要在 Nginx 中定義 error_page:error_page 401 =200 /login;# 所以也可以這麼做, 返回 403error_page 401, 403 =200 /login;這樣, 你返回 401 或者 403 http code 後, Nginx 就幫你重定向到了 /login
  • 4 - 5 步,這裡, 你需要把原始的 URI 交給 server 記錄下來,用于登錄成功後的跳轉:location /login { proxy_pass http://localhost:1234/login; proxy_set_header X-Original-URI $request_uri; # 原始的 URI 請求, 交由 server 記錄}​
  • 9 - 10 步, server 端需要記錄下自己生成的 code ,之後驗證 code 需要
  • 12 - 14 步, 這裡 client 請求的,其實是另一個 URI (如 POST /login), 這個 URI 是可以公開訪問,不需要鑒權的
  • 15 步, 這裡要注意, 鑒權成功後,要設置 header ,添加 Location URI , 告訴浏覽器要跳轉。這裡有個坑 [2] ,返回的 http code 需要注意。
  • 16 - 22 , 事實上就是前面介紹的兩個部分的内容,不再贅述

貼下具體的配置:

location / { auth_request /auth; error_page 401 =200 /login; root /xxx/html; } location /auth { proxy_pass http://localhost:1234/auth; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; proxy_cache auth_cache; proxy_cache_valid 200 2h; # proxy key, 參見 reference [3] proxy_cache_key $host$request_uri$cookie_session; # 如果有 session 丢失的問題, 可以參考這條配置 # proxy_cookie_path ~*^/.* /; } location /login { proxy_pass http://localhost:1234/login; proxy_set_header X-Original-URI $request_uri; }

WeChat 對接

關于 測試賬号 的東西,我也了解的不多,因為我隻對接了一個 。

登錄微信公衆号後, [開發] - [基本配置] - [服務器配置] ,配置服務器地址、令牌、消息加解密密鑰(optional)

  • URL 需要是一個公網 ip 或者域名
  • Token 你可以随意填寫,隻要你 server 端驗證的 Token 和此處填寫一緻, 就好了。

提交時, 微信會先發一個驗證請求,看看你的 server 是不是好的:

GET /handle?signature=xxx&echostr=123455×tamp=123456&nonce=1234

驗證部分的文檔, 在 這裡 。主要步驟是:

  • token, timestamp, nonce 字典排序并拼接成一個字符串
  • sha1 加密
  • 與 signature 比較是否一緻

# golang 部分代碼 ary := []string(token, timestamp, nonce) sort.Strings(ary) encryptor := sha1.New() io.WriteString(encryptor, strings.Join(ary,"")) mySignature := fmt.Sprintf("%x", encryptor.Sum(nil)) // compare

文檔說的是, 如果一緻,就把 URI 中 echostr 原樣返回。

嗯, 我返回了, 你特麼說我驗證失敗, Token 有問題啥的???

為啥, 因為我返回了一個 string 類型的字符串! F**k , 這導緻微信接收的時候, 在原值上多了一對雙引号。

哎, 我最終返回了一個 int 類型的 echostr ,雖然我不知道這個 echostr 是不是會存在 str ...


這一步完成後, 當你通過公衆号發送消息時, 微信就會把這個消息轉發給你的 server 。它的格式如下:

<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>


這就是在 server 端定義幾個結構體,解析下就好了:

type CdataString struct { Value string `xml:",cdata"` } ​ type MsgXml struct { XMLName xml.Name `xml:"xml"` ToUserName CdataString `xml:"ToUserName"` FromUserName CdataString `xml:"FromUserName"` CreateTime int64 `xml:"CreateTime"` MsgType CdataString `xml:"MsgType"` Content CdataString `xml:"Content"` MsgId int64 `xml:"MsgId,omitempty"` }

需要注意三個點:

  • 你的 xml 的 root tag 需要是 xml
  • 幾乎所有的字段,都需要用 cdata 格式包裹
  • 如果你在上面選擇了對消息加密,則給你的數據包,可能是長這樣的:

<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <Encrypt> <![CDATA[QC1jqzkTmPbaCkcB7ruVHe6k=...]]> </Encrypt> </xml>

你需要用你的 EncodingAESKey 先解密才行, 這裡 是微信的文檔, 不再詳述。至此, 你就可以愉快的通過微信公衆号,來玩轉你的 blog 了。是不是很有趣? 可憐我在五一的美好假期裡,熬了一個通宵搞這東西...

Referenct

[1]

請參考 proxy_cache_path

[2]

請參考 這裡

  • 301 :http 1.0 ,永久移除, POST --> GET
  • 302 : http 1.0 ,暫時移除, POST -> POST (我用的 chrome 會如此)
  • 303 : see other , POST -> GET
  • 307 : 臨時重定向, POST -> POST
  • 308 : 永久重定向, POST -> POST

開始通過 POST /login 進行重定向時, 總是用 POST 方法調用 Location 的地址,導緻 Method Not Allowed. 此時使用的 http code 是 307,後來用 302 , 最後改為 303 搞定

[3]

對于使用哪種變量作為 proxy_cache 的 key ,我想了很久。

最開始用一堆諸如 $host$remote_addr$request_uri , 後來發現一個問題,對于同一個局域網下的用戶, 隻要 $request_uri 一緻, 那這個 cache 就是公用的, 這明顯不科學啊!

後來 google 了一把, 決定用 session 作為 proxy_cache 的 key 。這要求你在鑒權的時候,在 cookie 中寫入你的 session,然後通過 $cookie_session 獲取即可。

當然,你可以往 cookie 裡寫 anything else ,如 wtf ,然後你就可以用 $cookie_wtf 作為你的 proxy key 了, 完美。

所以, 這裡的 cookie_xxx , 值得是 client 的端 cookie 中的一個 key 。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2025 - www.tftnews.com All Rights Reserved