一、Oathkeeper简介
ORY Oathkeeper是一个认证与访问代理,用于作为反向代理或者访问权限决策点。
当作为反向代理时,客户端、oathkeeper以及内网服务之间的通信流程如图1所示。
图1 作为反向代理时的通信流程
当作为权限决策点时,客户端、API网关、Oathkeeper以及内网服务之间的通信流程如下如下。
图2 当作为访问决策点时的通信流程
当Oathkeeper接收到HTTP请求后,会首先对HTTP请求进行认证以及鉴权,如果全都通过,才会将HTTP请求转发到服务端。
图3 Oathkeeper主要运行流程
由上图可知,Oathkeeper主要完成对HTTP请求的认证、鉴权的任务。然而,在不与第三方认证、鉴权服务通信的前提下, oathkeeper的只有有限的认证、鉴权能力,详细如下:
能力范围 | ||
认证 | 鉴权 | 更改 |
1.noop(全部认证通过) 2.unauthorized(全部拒绝认证) 3.Annymous(不带Authorization头部的请求可以认证通过,如果数据的访问者没有定义,则被设置为“匿名”)
| 1.allow(权限认证时全部通过) 2.deny(权限认证时全部失败) | 尚不清楚应用场景 |
因此,如果想要提供更全面、更完善的安全机制,只能在系统中继续搭建其他服务。在这个博客中,oathkeeper会和go-oauth2相配合,搭建安全防护系统,保护内网的一个nginx服务器。
二、 Oathkeeper的安装
详见Oathkeeper安装,由于网络原因,有些依赖包可能无法下载,可以通过设置代理或者手动下载完成,注意的是手动下载时需要将数据包安装到GOPATH下的指定路径。
三、 系统构建
3.1 基于go-oauth2完成系统认证、授权代码
核心代码如下:
/*
Step1:创建admin,zhanglei两个用户,并存储在内存数据库中
*/
clientStore := store.NewClientStore()
root_id, root_secret := "admin", "admin_haha"
root_index := root_id
root_client_info := models.Client{ID: root_id, Secret: root_secret, Domain: "http://ip_of_web:80/"}
clientStore.Set(root_index, &root_client_info)
user_id,user_secret := "zhanglei", "zhanglei_haha"
user_index := user_id
user_info := models.Client{ID: user_id, Secret: user_secret, Domain: "http://ip_of_web:80/"}
clientStore.Set(user_index, &user_info)
manager.MapClientStorage(clientStore)
/*
搭建登录服务,用来给用户生成short live token(2h内有效)
*/
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
//need:grant_typ(password),client_id,password
if r.Method == "POST" {
/*
当客户端POST的client存在且与password对应时,则认证服务器产生一个token.
否则,返回失败
*/
err := srv.HandleTokenRequest(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
outputHTML(w, r, "static/login.html")
})
http.HandleFunc("/vaild_token", func(w http.ResponseWriter, r *http.Request) {
//need:grant_typ(password),client_id,password
/*
提供HTTP的认证服务
*/
log.Println("receive a vaild token request !\n")
ctx := r.Context()
r.ParseForm()
access_token := r.Form.Get("token")
log.Println("access token:", access_token)
if access_token == "" {
http.Error(w, "The request don't have any access token", http.StatusBadRequest)
return
}
/*
查看该token是否被认证过,如果没有,则返回错误
*/
ti, err := srv.Manager.LoadAccessToken(ctx, access_token)
if err != nil {
http.Error(w, "Found token error", http.StatusForbidden)
return
}
response_map := make(map[string]interface{})
/*
active = true表示认证成功
active、username将会被放入鉴权信息的头部
*/
response_map["active"] = true
response_map["username"] = ti.GetUserID()
response_json, err := json.Marshal(response_map)
_ = err
response_string := string(response_json)
io.WriteString(w, response_string)
})
http.HandleFunc("/access_engery", func(w http.ResponseWriter, r *http.Request) {
/*
从HTTP头部信息中得到用户信息
*/
clientID, require_resource, require_action, err := get_client_info(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
/*
如果有相应权限,则需要返回给oathkeeper 200状态码
*/
if clientID == "admin" && require_resource == "web_server" && require_action == "read" {
io.WriteString(w, clientID+" is accessible to "+require_resource)
} else {
http.Error(w, clientID+" is not accessible to "+require_resource, http.StatusBadRequest)
}
return
})
3.2 Oathkeeper配置
oathkeeper的配置如下:
[
{
"id": "allow-anonymous-with-header-mutator",
"version": "v0.36.0-beta.4",
"upstream": {
"url": "http://ip_of_webserver:80/"
},
"match": {
"url": "<https|http>://ip_of_oathkeeper:4455/",
"methods": [
"GET"
]
},
"authenticators": [
{
"handler": "oauth2_introspection"
}
],
"authorizer": {
"handler": "remote",
"config": {
"headers": {
"clientID": "{{ print .Extra.username }}",
"require_resource": "web_server",
"action": "read"
}
}
},
"mutators": [
{
"handler": "noop"
}
]
},
{
"id": "user_login",
"version": "v0.36.0-beta.4",
"upstream": {
"url": "http://ip_of_go_oath2:9096"
},
"match": {
"url": "<https|http>://ip_of_oathkeeper:4455/login",
"methods": [
"GET","POST"
]
},
"authenticators": [
{
"handler": "noop"
}
],
"authorizer": {
"handler": "allow"
},
"mutators": [
{
"handler": "noop"
}
]
}
]
serve:
proxy:
port: 4455 # run the proxy at port 4455
api:
port: 4456 # run the api at port 4456
access_rules:
repositories:
- file:///home/zhanglei/go_project/src/github.com/ory/oathkeeper/demo/rules.json
authenticators:
noop:
enabled: true
unauthorized:
enabled: true
oauth2_introspection:
enabled: true
config:
introspection_url: http://ip_of_go_oath2:9096/vaild_token
mutators:
noop:
enabled: true
authorizers:
allow:
enabled: true
remote:
enabled: true
config:
remote: http://ip_of_go_oath2:9096/access_engery
这个配置的意思是,通过配置访问authenticators.oauth2_introspection.config.introspection_url对用户进行认证,然后通过访问
authorizers.remote.config.remote来进行远程鉴权。