使用Django为Nightingale实现Jira伪统一认证

  由于我司内部系统均使用 Jira 的统一认证,而在用的监控平台 Nightingale 并不支持 Jira 认证的接口,因此就有了本文。(还有其他诸多不支持 Jira 认证的平台也可通过相同思路实现 Jira 统一认证)

思路

  众所周知,用户认证的过程就是将用户的认证信息发往服务端进行核对数据库用户名和密码的相关信息是否正确的过程。
  Jira 有用于用户认证的 Restful 接口,可以通过 POST 请求对用户进行鉴权。而 Nightingale 作为前后端分离的项目,各个模块都很好的进行了解耦,拥有独立的认证接口,因此,我们要做的仅仅是将用户认证模块的请求拦截后转向到 Jira 等统一认证平台进行认证再返回即可。将用户的认证请求拦截的这个过程我们可以通过 Nginx 去实现。

实现

  我们先来抓一下 Nightingale 的认证接口。通过浏览器控制台将登陆过程中的请求抓下来得到类似这样的一个请求:

1
2
3
4
5
url: http://www.iots.vip/api/rdb/auth/login  
method: POST
header: content-type: application/json
body: {username: "alliot", password: "alliot", is_ldap: 0}
response: {"err": ""} # err 为空则成功登陆并带上一个 cookie

  因此,我们需要实现这样一个接口:接口拿到用户鉴权信息去到 Jira 认证接口进行认证,认证成功后模拟登陆拿到 Nightingale 认证的 Cookie 并响应给浏览器即可。
  因为需要综合进内部的 Django 项目中,所以这里是通过 Django 来实现的(当然你也可以使用 FastApi、Flask 之类更轻量的框架去实现)代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# views.py
# by: Alliot
# date: 2021-05-13


from Crypto.Cipher import AES
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
import requests
import base64
import json
import logging

JIRA_BASIC_URL = 'http://www.iots.vip' # Jira地址
JIRA_LOGIN_URL = '/rest/auth/1/session'
JIRA_LOGIN_OUT_URL = '/rest/auth/1/session'
JIRA_LOGIN_WHO_URL = '/rest/api/2/myself'
JIRA_GET_USER = '/rest/api/2/user'

N9E_HOST = 'http://127.0.0.1:8000'
# n9e token 在个人设置中创建
N9E_TOKEN = 'alliot_4578492a3280'


def login(request):
"""
拦截的请求入口
:param request:
:return:
"""
data = request.body
data = json.loads(data)

if request.method == 'POST':
login_username = data['username']
login_password = data['password']
use_ldap = data['is_ldap']
# 判断是否勾选使用LDAP登陆,是则使用n9e原生认证接口,否则使用JIRA认证接口
if use_ldap:
data['is_ldap'] = 0
return n9e_login(login_username, login_password)
else:
jira_user_data = {
'username': login_username,
'password': login_password}
headers = {'content-type': 'application/json'}
status_code = ""
try:
result = requests.post(
JIRA_BASIC_URL +
JIRA_LOGIN_URL,
json=jira_user_data,
headers=headers)
cookies = result.cookies
result_user = requests.get(
JIRA_BASIC_URL + JIRA_GET_USER,
params=jira_user_data,
cookies=cookies)
status_code = result_user.status_code
except Exception as e:
print(e)
print("Auth Failed")
if status_code == 200:
return n9e_login(login_username, login_password)

return HttpResponse(content=json.dumps({
"err": "登陆失败,请检查jira用户名/密码,其他问题请联系Alliot"
}))


def n9e_login(username, password):
"""
n9e原生登陆接口,直接响应结果
:param username:
:param password:
:return:
"""
post_data = json.dumps({
'username': username,
'password': password,
'is_ldap': 0
})
session = requests.Session()
r = session.post(f'{N9E_HOST}/api/rdb/auth/login', data=post_data)
cookie_dict = requests.utils.dict_from_cookiejar(r.cookies)
from pprint import pprint
response = HttpResponse(content=json.dumps({
'err': ''
}))
response.set_cookie('ecmc-sid', cookie_dict['ecmc-sid'])
pprint(response)
# print('-'*10)
# pprint(r.text)
if r.text == '{"err":"登陆失败,请检查用户名/密码"}':
print(n9e_register(username, password))
return HttpResponse(content=json.dumps({
"err": "这是你第一次登陆本系统,正在同步账号信息,请重新登陆一次"
}))
else:
return response


def n9e_reset_password(username, password):
return HttpResponse(content=json.dumps({
"err": "jira密码与n9e密码不一致,请重置n9e密码"
}))


def n9e_register(username, password):
payload = json.dumps({
"type": 0,
"username": username,
"password": password,
"dispname": username,
"is_root": 0,
"active_begin": None,
"active_end": None,
"is_tenant_admin": 0
})
headers = {
'content-type': 'application/json',
'X-User-Token': N9E_TOKEN
}
r = requests.request("POST", f'{N9E_HOST}/api/rdb/users', headers=headers, data=payload)
if '已存在' in r.text:
n9e_reset_password(username, password)
else:
return r.text
1
2
3
4
5
6
7
8
# urls.py: 
from django.contrib import admin
from django.urls import path
from app.views import *

urlpatterns = [
path('api/rdb/auth/login', login),
]

修改 Nightingale 的 nginx 配置将请求拦截到我们的接口,配置如下:

1
2
3
4
5
6
7
8
9
10
11
# n9e.conf

server {
...

# 拦截n9e认证登陆接口
location /api/rdb/auth/login {
proxy_pass http://localhost:8081/api/rdb/auth/login; # 代理到我们写的接口
}
...
}

  这样,用户登陆 Nightingale 默认是通过 Jira 来进行用户鉴权的,当 Jira 用户鉴权通过,则会去用账号密码模拟登陆 Nightingale,当认证成功(表示 Nightingale 中存在该用户,并且账号密码正确),则返回模拟登陆得到的 Cookies 返回给用户。当 Nightingale 不存在该用户时,则会调用 Nightingale 接口创建用户(账号密码与 Jira 一致);当用户勾选 ldap 登陆后,则会直接走 Nightingale 来登陆。

结语

  这个伪统一认证实际上只是通过 Jira 验证了用户信息,避免了新用户创建账号的烦恼,权限控制等依然在 Nightingale 中完成,同时也不影响现有的 Nightingale 用户。