基于Keycloak的Grafana SSO身份认证过程剖析

2021-09-17 10:29:40 浏览数 (2)

Keycloak是一款主流的IAM(Identity and Access Management 的缩写,即“身份识别与访问管理”)开源实现,它具有单点登录、强大的认证管理、基于策略的集中式授权和审计、动态授权、企业可管理性等功能。

结合我们项目内的过往使用经验,本篇文章,将介绍:

1、一键式Keycloak安装;

2、配置Grafana,使接入Keycloak进行单点登录;

3、分析Grafana SSO登录过程,从而帮助用户更理解OAuth2的交互过程;

下图就是最终的过程图示:

1、一键式Keycloak安装

基于项目需要,我们在使用Keycloak时,需要外接企业微信的认证方式,鉴于Keycloak已有的Social Login并不支持企业微信,我们对此作出了扩展, https://github.com/qugeppl/keycloak-social-wecom ,并基于官方的12.0.4镜像做了无感扩展,可直接以容器方式启动:

代码语言:javascript复制
docker run -it --name keycloak-wecom -p 80:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin qugeppl/keycloak-social-wecom:12.0.4.1

2、配置Keycloak

2.1 Grafana的OAuth接入

这里我们重点以Grafana的通用OAuth2接入能力https://grafana.com/docs/grafana/latest/auth/generic-oauth/

可以看到,对Grafana来说,天生支持了OAuth协议的sso过程,只需要添加一些配置(其他非必填,我们先只关注基础的部分)

代码语言:javascript复制
[auth.generic_oauth]
enabled = true
client_id = YOUR_APP_CLIENT_ID
client_secret = YOUR_APP_CLIENT_SECRET
scopes =
empty_scopes = false
auth_url =
token_url =
api_url =
root_url =

Grafana或者其他任意需要SSO登录的组件,其实都是Keycloak(或者其他IAM)里的一个Client,所以需要去Keycloak创建出这个client,并且拿到对应的信息;

2.2 Keycloak配置

  1. 访问

上述步骤安装后的Keycloak ip,例如可以是http://localhost, 会自动打开如下页面,从管理员界面进行登录,账号密码如步骤1中的环境变量所设置 admin/admin

2. 创建新域Test (Master是顶级域,一般不建议使用)

3.在Test域创建Client,命名为grafana

补充其他必须信息,保存

拿到client secret

2.3 填充Grafana配置信息

几个url信息从哪里得到?参照Keycloak官方文档的指引, https://www.keycloak.org/docs/latest/server_admin/index.html

发现有endpoint现成提供

代码语言:javascript复制
<root>/auth/realms/{realm-name}/.well-known/openid-configuration

like:
http://localhost/auth/realms/Test/.well-known/openid-configuration

访问得到的结果在这里啦

代码语言:javascript复制
{
    "issuer":"http://local/auth/realms/Test",
    "authorization_endpoint":"http://localhost/auth/realms/Test/protocol/openid-connect/auth",
    "token_endpoint":"http://localhost/auth/realms/Test/protocol/openid-connect/token",
    "introspection_endpoint":"http://localhost/auth/realms/Test/protocol/openid-connect/token/introspect",
    "userinfo_endpoint":"http://localhost/auth/realms/Test/protocol/openid-connect/userinfo",
    "end_session_endpoint":"http://localhost/auth/realms/Test/protocol/openid-connect/logout",
    "jwks_uri":"http://localhost/auth/realms/Test/protocol/openid-connect/certs",
    "check_session_iframe":"http://localhost/auth/realms/Test/protocol/openid-connect/login-status-iframe.html",
    "grant_types_supported":[
        "authorization_code",
        "implicit",
        "refresh_token",
        "password",
        "client_credentials"
    ],
    ...省略了其他无关信息
}

所以,这里基本的配置都有了

代码语言:javascript复制
[auth.generic_oauth]
enabled = true
name = Test
allow_sign_up = true
client_id = grafana
client_secret = ******  #这个在keycloak->client id=grafana->credential找到
scopes = openid email   #这里要注意,openid这个是一定要有的,email的话,表示登录成功后,可以授权拿到用户的email信息
auth_url = http://localhost/auth/realms/Test/protocol/openid-connect/auth  #第一步登录
token_url = http://localhost/auth/realms/Test/protocol/openid-connect/token #第二步获取token
api_url = http://localhost/auth/realms/Test/protocol/openid-connect/userinfo #第三部获取user信息

root_url = http://grafanaip:port #这里是grafana的地址一定要注意,grafana 认证成功并且存入grafana session后,会将用户redirect这个地址,就是grafana首页
role_attribute_path = role #这里是读取用户Atrribute里的某个字段来解析用户登录到Grafana的角色
  

上面的role_attribute_path 可以更近一步在官方文档中找到说明:

https://grafana.com/docs/grafana/latest/auth/generic-oauth/#role-mapping

对应的keycloak里grafana client需要配置在userinfo时候,返回用户的role属性

用户的属性信息里role是哪里来的呢,其实可以配置给用户登录的时使用的IDP Mapper,如下图的意思是,用户登录后,自动给用户匹配被硬编码的“Admin”字符串

当然既然都是管理员,那可不可以不这么麻烦,直接在grafana配置不就好了么,于是参照grafana文档及测试,得到了如下配置,节省了keycloak的用户角色配置

代码语言:javascript复制
role_attribute_path ='True'&&'Admin'

3. Grafana SSO登录过程分析

按照上述的步骤,你的grafana配置好后,已经能够使用keycloak进行登录了(需要在Keycloak创建用户):

当然你需要在Keycloak的Test域下创建一个用户,或者直接使用企业微信扫码登录(这里需要企业应用的一些信息)

一般情况下,第三方软件以oauth2接入keycloak时,都需要提供三个url

代码语言:javascript复制
auth_url = http://localhost/auth/realms/PulseLine/protocol/openid-connect/auth  #第一步登录
token_url = http://localhost/auth/realms/PulseLine/protocol/openid-connect/token #第二步获取token
api_url = http://localhost/auth/realms/PulseLine/protocol/openid-connect/userinfo #第三部获取user信息

(第三方软件外部用)auth_url:浏览器访问时,第三方软件(Grafana)靠此url使用户浏览器转向Keycloak的认证登录界面,用户在Keycloak登录成功后,keycloak会生成一个针对该用户的code(这个code只能使用一次,用于换取token),并返回给浏览器,并指定下一跳的url.(这个url是用户指定的第三方软件中能够处理code的url,即callback)

(第三方软件内部用)token_url:浏览器按照上一步第三方软件转向地址,携带code进行访问,第三方软件内部收到请求后使用token_url以及code,和keycloak通信交换出这个用户的access_token。

(第三方软件内部用)api_url:第三方软件内部使用api_url,以及上一步的access_token与keycloak通信,获取这个用户的详细信息后,内部注册用户并存储session,最后将浏览器重定向到第三方软件首页(因为已经存储了第三方软件的session,所以直接保持登录态)

第一个请求authenticate,请求keycloak登录授权

代码语言:javascript复制
http://localhost/auth/realms/Test/login-actions/authenticate?session_code=TqvBA9opgD0HDGZIgKIo3bRHa6k9kCmB_pBu1ISuOGE&execution=3b27c26c-883e-47f1-b999-c6f003f7f4ec&client_id=grafana&tab_id=FymTJ3TfBXA
​
#此时用户输入账号密码,点击登录,认证成功后

#该请求的responseHeader看出,keycloak登录成功,客户端可以转向grafana了,并给予了keycloak的code
http://localhost:3000/login/generic_oauth?state=dFgc4PlQAA_7f8b6D6g-N4lWn8fWI-EnEVpcy1sJ-o8=&session_state=c78927fc-37bd-4d00-8cc8-c23c8f5d1989&code=9d4a337c-83f2-4940-a079-4185020c883a.c78927fc-37bd-4d00-8cc8-c23c8f5d1989.27cb44d1-7d6b-46a9-966b-cdaa49a7f9f5

第二个请求则为第一个请求中keycloak让客户端登录keycloak登录成功后转向

代码语言:javascript复制
http://localhost:3000/login/generic_oauth?state=dFgc4PlQAA_7f8b6D6g-N4lWn8fWI-EnEVpcy1sJ-o8=&session_state=c78927fc-37bd-4d00-8cc8-c23c8f5d1989&code=9d4a337c-83f2-4940-a079-4185020c883a.c78927fc-37bd-4d00-8cc8-c23c8f5d1989.27cb44d1-7d6b-46a9-966b-cdaa49a7f9f5
​
#这里grafana会拿到code,并和keycloak通信,用code换回accesstoken

#有了accesstoken后,遂向keycloak,发起api_url的请求,获取用户身份

#此时存入自己管理的用户session

#然后返回
#response header里,设置浏览器的grafana cookie,说明grafana已经将keycloak的code验证登录后,生成了该客户端的grafana_session
Set-Cookie: oauth_state=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax
Set-Cookie: grafana_session=d08672d2f9a27fa5d760d8a44bb8fc73; Path=/; Max-Age=2595600; HttpOnly; SameSite=Lax
Set-Cookie: redirect_to=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax
​
Location: /

第三个请求为第二个请求的客户端强制转向,即Grafana里配置的root_url

代码语言:javascript复制
http://localhost:3000/
​
#请求头
Cookie: grafana_session=d08672d2f9a27fa5d760d8a44bb8fc73

0 人点赞