## 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...
现在我们通过 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_basic_user_file 文件中。我的意思是,这个校验的工作,让我来吧,Nginx 你去搞别的去!
这样做的好处是, 我可以方便的管理我的账号体系(这里所谓的账号体系,是一个夸张的说法,你一个小小的 blog ,还账号体系...)
emmm... 不管怎样,这看起来是件有趣的事情。
Nginx 自是能考虑到这些,已然提供了一个语法 auth_request , 意思是,你如果要访问这里,可以,得先通过我的验证才行。
auth_request uri | off; # off 表示关闭上文的验证,就是我这里我说了算,上级不要说话了
# 该语法支持的上下文为:
http, server, location
那么我们要怎么搞呢? 别急, 我先整张图:
通过上述步骤, 我们成功通过和 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;
我现在又觉得, 搞啥账号体系,我一个小小的 blog , 太耗时间了!
于是你想到了用验证码来搞好了。
那自然而然的想到了微信,想到利用公众号, 来搞个自动获取验证码呗。整个流程如下:
下面再来看下更详细的流程图:
这张图就很详细了,这里主要说几个注意的点:
贴下具体的配置:
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;
}
关于 测试账号 的东西,我也了解的不多,因为我只对接了一个 。
登录微信公众号后, [开发] - [基本配置] - [服务器配置] ,配置服务器地址、令牌、消息加解密密钥(optional)
提交时, 微信会先发一个验证请求,看看你的 server 是不是好的:
GET /handle?signature=xxx&echostr=123455×tamp=123456&nonce=1234
验证部分的文档, 在 这里 。主要步骤是:
# 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>
<ToUserName><![CDATA[toUser]]></ToUserName>
<Encrypt>
<![CDATA[QC1jqzkTmPbaCkcB7ruVHe6k=...]]>
</Encrypt>
</xml>
你需要用你的 EncodingAESKey 先解密才行, 这里 是微信的文档, 不再详述。至此, 你就可以愉快的通过微信公众号,来玩转你的 blog 了。是不是很有趣? 可怜我在五一的美好假期里,熬了一个通宵搞这东西...
请参考 proxy_cache_path
请参考 这里
开始通过 POST /login 进行重定向时, 总是用 POST 方法调用 Location 的地址,导致 Method Not Allowed. 此时使用的 http code 是 307,后来用 302 , 最后改为 303 搞定
对于使用哪种变量作为 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 。
,