Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
512fc8f8
Commit
512fc8f8
authored
Jul 05, 2018
by
BaiJiangJie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Update] 优化登录失败次数限制的逻辑,并添加系统安全设置选项
parent
ff9b1a88
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
109 additions
and
39 deletions
+109
-39
forms.py
apps/common/forms.py
+20
-6
security_setting.html
apps/common/templates/common/security_setting.html
+2
-2
django.mo
apps/i18n/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/i18n/zh/LC_MESSAGES/django.po
+35
-17
settings.py
apps/jumpserver/settings.py
+2
-0
api.py
apps/users/api.py
+15
-5
login.html
apps/users/templates/users/login.html
+1
-1
utils.py
apps/users/utils.py
+26
-0
login.py
apps/users/views/login.py
+8
-8
No files found.
apps/common/forms.py
View file @
512fc8f8
...
...
@@ -170,7 +170,7 @@ class TerminalSettingForm(BaseForm):
class
SecuritySettingForm
(
BaseForm
):
# MFA
全局设置
# MFA
global setting
SECURITY_MFA_AUTH
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"MFA Secondary certification"
),
...
...
@@ -179,12 +179,26 @@ class SecuritySettingForm(BaseForm):
'authentication (valid for all users, including administrators)'
)
)
# 最小长度
# limit login count
SECURITY_LOGIN_LIMIT_COUNT
=
forms
.
IntegerField
(
initial
=
3
,
min_value
=
3
,
label
=
_
(
"Limit the number of login failures"
)
)
# limit login time
SECURITY_LOGIN_LIMIT_TIME
=
forms
.
IntegerField
(
initial
=
30
,
min_value
=
5
,
label
=
_
(
"No logon interval"
),
help_text
=
_
(
"Tip :(unit/minute) if the user has failed to log in for a limited "
"number of times, no login is allowed during this time interval."
)
)
# min length
SECURITY_PASSWORD_MIN_LENGTH
=
forms
.
IntegerField
(
initial
=
6
,
label
=
_
(
"Password minimum length"
),
min_value
=
6
)
#
大写字母
#
upper case
SECURITY_PASSWORD_UPPER_CASE
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
...
...
@@ -193,21 +207,21 @@ class SecuritySettingForm(BaseForm):
'After opening, the user password changes '
'and resets must contain uppercase letters'
)
)
#
小写字母
#
lower case
SECURITY_PASSWORD_LOWER_CASE
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain lowercase letters"
),
help_text
=
_
(
'After opening, the user password changes '
'and resets must contain lowercase letters'
)
)
#
数字
#
number
SECURITY_PASSWORD_NUMBER
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain numeric characters"
),
help_text
=
_
(
'After opening, the user password changes '
'and resets must contain numeric characters'
)
)
#
特殊字符
#
special char
SECURITY_PASSWORD_SPECIAL_CHAR
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain special characters"
),
...
...
apps/common/templates/common/security_setting.html
View file @
512fc8f8
...
...
@@ -39,9 +39,9 @@
{% endif %}
{% csrf_token %}
<h3>
{% trans "
MFA setting
" %}
</h3>
<h3>
{% trans "
User login settings
" %}
</h3>
{% for field in form %}
{% if forloop.counter ==
2
%}
{% if forloop.counter ==
4
%}
<div
class=
"hr-line-dashed"
></div>
<h3>
{% trans "Password check rule" %}
</h3>
{% endif %}
...
...
apps/i18n/zh/LC_MESSAGES/django.mo
View file @
512fc8f8
No preview for this file type
apps/i18n/zh/LC_MESSAGES/django.po
View file @
512fc8f8
...
...
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-0
4 16:46
+0800\n"
"POT-Creation-Date: 2018-07-0
5 14:58
+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
...
...
@@ -1433,45 +1433,60 @@ msgid ""
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:184
#: common/forms.py:186
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
#: common/forms.py:193
msgid "No logon interval"
msgstr "禁止登录时间间隔"
#: common/forms.py:195
msgid ""
"Tip :(unit/minute) if the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval."
msgstr ""
"提示:(单位 / 分钟)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
#: common/forms.py:201
msgid "Password minimum length"
msgstr "密码最小长度 "
#: common/forms.py:
191
#: common/forms.py:
208
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: common/forms.py:
193
#: common/forms.py:
210
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:
199
#: common/forms.py:
216
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: common/forms.py:2
00
#: common/forms.py:2
17
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:2
06
#: common/forms.py:2
23
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: common/forms.py:2
07
#: common/forms.py:2
24
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:2
13
#: common/forms.py:2
30
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: common/forms.py:2
14
#: common/forms.py:2
31
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
...
...
@@ -1532,8 +1547,8 @@ msgid "Security setting"
msgstr "安全设置"
#: common/templates/common/security_setting.html:42
msgid "
MFA setting
"
msgstr "
MFA
设置"
msgid "
User login settings
"
msgstr "
用户登录
设置"
#: common/templates/common/security_setting.html:46
msgid "Password check rule"
...
...
@@ -2307,6 +2322,10 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api.py:210 users/templates/users/login.html:50
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: users/authentication.py:56
msgid "Invalid signature header. No credentials provided."
msgstr ""
...
...
@@ -2649,10 +2668,6 @@ msgstr "忘记密码"
msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
#: users/templates/users/login.html:50
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: users/templates/users/login.html:53
msgid "Captcha invalid"
msgstr "验证码错误"
...
...
@@ -3049,7 +3064,7 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:7
4
#: users/views/login.py:7
5
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
...
...
@@ -3149,3 +3164,6 @@ msgstr "MFA 解绑成功"
#: users/views/user.py:555
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
#~ msgid "MFA setting"
#~ msgstr "MFA 设置"
apps/jumpserver/settings.py
View file @
512fc8f8
...
...
@@ -405,6 +405,8 @@ TERMINAL_REPLAY_STORAGE = {
DEFAULT_PASSWORD_MIN_LENGTH
=
6
DEFAULT_LOGIN_LIMIT_COUNT
=
3
DEFAULT_LOGIN_LIMIT_TIME
=
30
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3
=
{
...
...
apps/users/api.py
View file @
512fc8f8
...
...
@@ -3,6 +3,7 @@ import uuid
from
django.core.cache
import
cache
from
django.urls
import
reverse
from
django.utils.translation
import
ugettext
as
_
from
rest_framework
import
generics
from
rest_framework.permissions
import
AllowAny
,
IsAuthenticated
...
...
@@ -17,7 +18,8 @@ from .tasks import write_login_log_async
from
.models
import
User
,
UserGroup
,
LoginLog
from
.permissions
import
IsSuperUser
,
IsValidUser
,
IsCurrentUserOrReadOnly
,
\
IsSuperUserOrAppUser
from
.utils
import
check_user_valid
,
generate_token
,
get_login_ip
,
check_otp_code
from
.utils
import
check_user_valid
,
generate_token
,
get_login_ip
,
\
check_otp_code
,
set_user_login_failed_count_to_cache
,
is_block_login
from
common.mixins
import
IDInFilterMixin
from
common.utils
import
get_logger
...
...
@@ -149,7 +151,6 @@ class UserOtpAuthApi(APIView):
return
Response
({
'msg'
:
'请先进行用户名和密码验证'
},
status
=
401
)
if
not
check_otp_code
(
user
.
otp_secret_key
,
otp_code
):
# Write login failed log
data
=
{
'username'
:
user
.
username
,
'mfa'
:
int
(
user
.
otp_enabled
),
...
...
@@ -159,7 +160,6 @@ class UserOtpAuthApi(APIView):
self
.
write_login_log
(
request
,
data
)
return
Response
({
'msg'
:
'MFA认证失败'
},
status
=
401
)
# Write login success log
data
=
{
'username'
:
user
.
username
,
'mfa'
:
int
(
user
.
otp_enabled
),
...
...
@@ -196,12 +196,21 @@ class UserOtpAuthApi(APIView):
class
UserAuthApi
(
APIView
):
permission_classes
=
(
AllowAny
,)
serializer_class
=
UserSerializer
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
def
post
(
self
,
request
):
user
,
msg
=
self
.
check_user_valid
(
request
)
username
=
request
.
data
.
get
(
'username'
)
ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
if
not
ip
:
ip
=
get_login_ip
(
request
)
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
if
is_block_login
(
key_limit
):
msg
=
_
(
"Log in frequently and try again later"
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
if
not
user
:
# Write login failed log
data
=
{
'username'
:
request
.
data
.
get
(
'username'
,
''
),
'mfa'
:
LoginLog
.
MFA_UNKNOWN
,
...
...
@@ -209,10 +218,11 @@ class UserAuthApi(APIView):
'status'
:
False
}
self
.
write_login_log
(
request
,
data
)
set_user_login_failed_count_to_cache
(
key_limit
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
if
not
user
.
otp_enabled
:
# Write login success log
data
=
{
'username'
:
user
.
username
,
'mfa'
:
int
(
user
.
otp_enabled
),
...
...
apps/users/templates/users/login.html
View file @
512fc8f8
...
...
@@ -46,7 +46,7 @@
<form
class=
"m-t"
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
{% if
login_limit
%}
{% if
block_login
%}
<p
class=
"red-fonts"
>
{% trans 'Log in frequently and try again later' %}
</p>
{% elif form.errors %}
{% if 'captcha' in form.errors %}
...
...
apps/users/utils.py
View file @
512fc8f8
...
...
@@ -332,3 +332,29 @@ def check_password_rules(password):
match_obj
=
re
.
match
(
pattern
,
password
)
return
bool
(
match_obj
)
def
set_user_login_failed_count_to_cache
(
key_limit
):
count
=
cache
.
get
(
key_limit
)
count
=
count
+
1
if
count
else
1
setting_limit_time
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_LOGIN_LIMIT_TIME'
)
.
first
()
limit_time
=
setting_limit_time
.
cleaned_value
if
setting_limit_time
\
else
settings
.
DEFAULT_LOGIN_LIMIT_TIME
cache
.
set
(
key_limit
,
count
,
int
(
limit_time
)
*
60
)
def
is_block_login
(
key_limit
):
count
=
cache
.
get
(
key_limit
)
setting_limit_count
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_LOGIN_LIMIT_COUNT'
)
.
first
()
limit_count
=
setting_limit_count
.
cleaned_value
if
setting_limit_count
\
else
settings
.
DEFAULT_LOGIN_LIMIT_COUNT
if
count
and
count
>=
limit_count
:
return
True
apps/users/views/login.py
View file @
512fc8f8
...
...
@@ -27,7 +27,8 @@ from common.models import Setting
from
..models
import
User
,
LoginLog
from
..utils
import
send_reset_password_mail
,
check_otp_code
,
get_login_ip
,
\
redirect_user_first_login_or_index
,
get_user_or_tmp_user
,
\
set_tmp_user_to_cache
,
get_password_check_rules
,
check_password_rules
set_tmp_user_to_cache
,
get_password_check_rules
,
check_password_rules
,
\
is_block_login
,
set_user_login_failed_count_to_cache
from
..tasks
import
write_login_log_async
from
..
import
forms
...
...
@@ -63,9 +64,9 @@ class UserLoginView(FormView):
# limit login authentication
ip
=
get_login_ip
(
request
)
username
=
self
.
request
.
POST
.
get
(
'username'
)
count
=
cache
.
get
(
self
.
key_prefix_limit
.
format
(
ip
,
username
)
)
if
count
and
count
>=
3
:
return
self
.
render_to_response
(
self
.
get_context_data
(
login_limit
=
True
))
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
if
is_block_login
(
key_limit
)
:
return
self
.
render_to_response
(
self
.
get_context_data
(
block_login
=
True
))
return
super
()
.
post
(
request
,
*
args
,
**
kwargs
)
...
...
@@ -87,13 +88,12 @@ class UserLoginView(FormView):
}
self
.
write_login_log
(
data
)
# limit user login failed
times
# limit user login failed
count
ip
=
get_login_ip
(
self
.
request
)
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
count
=
cache
.
get
(
key_limit
)
count
=
count
+
1
if
count
else
1
cache
.
set
(
key_limit
,
count
,
1800
)
set_user_login_failed_count_to_cache
(
key_limit
)
# show captcha
cache
.
set
(
self
.
key_prefix_captcha
.
format
(
ip
),
1
,
3600
)
old_form
=
form
form
=
self
.
form_class_captcha
(
data
=
form
.
data
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment