Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
loginservice
Overview
Overview
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
lanmw
loginservice
Commits
d36b5cd7
Commit
d36b5cd7
authored
Aug 18, 2026
by
xieshaohua
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/product-v2' into product-v2-hnrcb
parents
94e075e9
f6532a64
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
145 additions
and
2 deletions
+145
-2
LoginManagement.java
src/main/java/com/keymobile/sso/api/LoginManagement.java
+69
-0
ConcurrentSessionExpiredStrategy.java
.../keymobile/sso/conf/ConcurrentSessionExpiredStrategy.java
+35
-0
SsoSecurityConfig.java
src/main/java/com/keymobile/sso/conf/SsoSecurityConfig.java
+13
-0
ApiError.java
src/main/java/com/keymobile/sso/exception/ApiError.java
+2
-2
LoginAttemptService.java
.../java/com/keymobile/sso/security/LoginAttemptService.java
+26
-0
No files found.
src/main/java/com/keymobile/sso/api/LoginManagement.java
View file @
d36b5cd7
package
com
.
keymobile
.
sso
.
api
;
import
com.keymobile.sso.logging.LogConstants
;
import
com.keymobile.sso.logging.LogManager
;
import
com.keymobile.sso.security.LoginAttemptService
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletResponse
;
import
jakarta.servlet.http.HttpSession
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.security.core.Authentication
;
import
org.springframework.security.core.context.SecurityContextHolder
;
import
org.springframework.security.core.userdetails.UserDetails
;
import
org.springframework.util.Assert
;
import
org.springframework.web.bind.annotation.*
;
import
java.util.ArrayList
;
...
...
@@ -16,6 +24,14 @@ import java.util.Map;
@RequestMapping
(
value
=
"/"
)
public
class
LoginManagement
{
private
static
final
String
HINT_AUTHENTICATION_IS_NULL
=
"Authentication is null."
;
@Value
(
"${self.login.admin-user:root}"
)
private
String
adminUser
;
@Autowired
private
LoginAttemptService
loginAttemptService
;
@RequestMapping
(
value
=
"/sessionInfo"
,
method
=
{
RequestMethod
.
POST
,
RequestMethod
.
GET
})
public
@ResponseBody
Map
<
String
,
Object
>
verifyLogin
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
UserDetails
userDetails
=
(
UserDetails
)
SecurityContextHolder
.
getContext
().
getAuthentication
().
getPrincipal
();
...
...
@@ -45,4 +61,57 @@ public class LoginManagement {
return
session
.
getAttribute
(
Constants
.
Session_Lang
).
toString
();
}
@RequestMapping
(
value
=
"/lockedUsers"
,
method
=
RequestMethod
.
GET
)
public
@ResponseBody
Map
<
String
,
Object
>
lockedUsers
(
HttpServletResponse
response
)
{
boolean
isAdmin
=
getCurrentUserName
().
equals
(
adminUser
);
Map
<
String
,
Object
>
rs
=
new
HashMap
<>();
if
(!
isAdmin
)
{
response
.
setStatus
(
HttpStatus
.
FORBIDDEN
.
value
());
rs
.
put
(
"status"
,
HttpStatus
.
FORBIDDEN
.
value
());
rs
.
put
(
"message"
,
"Admin role required"
);
rs
.
put
(
"cnMessage"
,
"需要管理员权限"
);
return
rs
;
}
rs
.
put
(
"status"
,
HttpStatus
.
OK
.
value
());
rs
.
put
(
"users"
,
loginAttemptService
.
getLockedUsers
());
return
rs
;
}
@RequestMapping
(
value
=
"/unlock"
,
method
=
RequestMethod
.
POST
)
public
@ResponseBody
Map
<
String
,
Object
>
unlock
(
HttpServletResponse
response
,
@RequestParam
(
value
=
"username"
,
required
=
true
)
String
username
)
{
String
currentUser
=
getCurrentUserName
();
boolean
isAdmin
=
getCurrentUserName
().
equals
(
adminUser
);
Map
<
String
,
Object
>
rs
=
new
HashMap
<>();
if
(!
isAdmin
)
{
LogManager
.
logWarning
(
LogConstants
.
CTX_AUDIT
,
"Unauthorized unlock attempt by "
+
currentUser
+
" for user: "
+
username
);
response
.
setStatus
(
HttpStatus
.
FORBIDDEN
.
value
());
rs
.
put
(
"status"
,
HttpStatus
.
FORBIDDEN
.
value
());
rs
.
put
(
"message"
,
"Admin role required"
);
rs
.
put
(
"cnMessage"
,
"需要管理员权限"
);
return
rs
;
}
boolean
wasLocked
=
loginAttemptService
.
unlock
(
username
);
LogManager
.
logWarning
(
LogConstants
.
CTX_AUDIT
,
"Admin "
+
currentUser
+
" unlocked account: "
+
username
);
rs
.
put
(
"status"
,
HttpStatus
.
OK
.
value
());
rs
.
put
(
"username"
,
username
);
rs
.
put
(
"wasLocked"
,
wasLocked
);
return
rs
;
}
private
String
getCurrentUserName
()
{
Authentication
authentication
=
SecurityContextHolder
.
getContext
().
getAuthentication
();
Assert
.
notNull
(
authentication
,
HINT_AUTHENTICATION_IS_NULL
);
Object
principal
=
authentication
.
getPrincipal
();
Assert
.
notNull
(
principal
,
"Principal is null."
);
if
(
principal
.
toString
().
equals
(
"anonymousUser"
))
{
return
null
;
}
else
{
UserDetails
userDetails
=
(
UserDetails
)
principal
;
String
userNameWithIdAttached
=
userDetails
.
getUsername
();
return
userNameWithIdAttached
.
split
(
":"
)[
0
];
}
}
}
src/main/java/com/keymobile/sso/conf/ConcurrentSessionExpiredStrategy.java
0 → 100644
View file @
d36b5cd7
package
com
.
keymobile
.
sso
.
conf
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.keymobile.sso.exception.ApiError
;
import
com.keymobile.sso.logging.LogConstants
;
import
com.keymobile.sso.logging.LogManager
;
import
jakarta.servlet.http.HttpServletResponse
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.MediaType
;
import
org.springframework.security.web.session.SessionInformationExpiredEvent
;
import
org.springframework.security.web.session.SessionInformationExpiredStrategy
;
import
org.springframework.stereotype.Component
;
import
java.io.IOException
;
@Component
public
class
ConcurrentSessionExpiredStrategy
implements
SessionInformationExpiredStrategy
{
private
static
final
String
MESSAGE
=
"Session expired: this account signed in from another location"
;
private
static
final
String
CN_MESSAGE
=
"该账号已在其他地方登录,您已被迫下线"
;
private
final
ObjectMapper
objectMapper
=
new
ObjectMapper
();
@Override
public
void
onExpiredSessionDetected
(
SessionInformationExpiredEvent
event
)
throws
IOException
{
HttpServletResponse
response
=
event
.
getResponse
();
LogManager
.
logInfo
(
LogConstants
.
CTX_AUDIT
,
MESSAGE
);
ApiError
apiError
=
new
ApiError
(
HttpStatus
.
UNAUTHORIZED
,
MESSAGE
,
CN_MESSAGE
,
null
);
response
.
setStatus
(
HttpServletResponse
.
SC_UNAUTHORIZED
);
response
.
setContentType
(
MediaType
.
APPLICATION_JSON_VALUE
);
response
.
setCharacterEncoding
(
"UTF-8"
);
response
.
getWriter
().
write
(
objectMapper
.
writeValueAsString
(
apiError
));
}
}
src/main/java/com/keymobile/sso/conf/SsoSecurityConfig.java
View file @
d36b5cd7
...
...
@@ -2,6 +2,7 @@ package com.keymobile.sso.conf;
import
com.keymobile.sso.security.LoginAttemptFilter
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.security.config.annotation.web.builders.HttpSecurity
;
...
...
@@ -15,6 +16,9 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
@EnableWebSecurity
public
class
SsoSecurityConfig
{
@Value
(
"${self.concurrent-login-constraint.enabled:false}"
)
private
boolean
isConstraintEnabled
;
@Autowired
private
RESTAuthenticationEntryPoint
authenticationEntryPoint
;
@Autowired
...
...
@@ -25,6 +29,8 @@ public class SsoSecurityConfig {
private
RESTLogoutSuccessHandler
logoutSuccessHandler
;
@Autowired
private
LoginAttemptFilter
loginAttemptFilter
;
@Autowired
private
ConcurrentSessionExpiredStrategy
concurrentSessionExpiredStrategy
;
@Bean
public
PasswordEncoder
passwordEncoder
()
{
...
...
@@ -62,6 +68,13 @@ public class SsoSecurityConfig {
logout
.
logoutSuccessHandler
(
logoutSuccessHandler
);
});
http
.
addFilterBefore
(
loginAttemptFilter
,
UsernamePasswordAuthenticationFilter
.
class
);
if
(
isConstraintEnabled
)
{
http
.
sessionManagement
((
sessionManagement
)
->
{
sessionManagement
.
maximumSessions
(
1
)
.
maxSessionsPreventsLogin
(
false
)
.
expiredSessionStrategy
(
concurrentSessionExpiredStrategy
);
});
}
return
http
.
build
();
}
...
...
src/main/java/com/keymobile/sso/exception/ApiError.java
View file @
d36b5cd7
...
...
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import
org.springframework.http.HttpStatus
;
@JsonTypeInfo
(
include
=
JsonTypeInfo
.
As
.
WRAPPER_OBJECT
,
use
=
JsonTypeInfo
.
Id
.
NAME
,
property
=
"error"
,
visible
=
true
)
class
ApiError
{
public
class
ApiError
{
private
HttpStatus
status
;
private
Long
timestamp
;
...
...
@@ -31,7 +31,7 @@ class ApiError {
this
.
message
=
message
;
}
ApiError
(
HttpStatus
status
,
String
message
,
String
cnMessage
,
Throwable
ex
)
{
public
ApiError
(
HttpStatus
status
,
String
message
,
String
cnMessage
,
Throwable
ex
)
{
this
();
this
.
status
=
status
;
this
.
message
=
message
;
...
...
src/main/java/com/keymobile/sso/security/LoginAttemptService.java
View file @
d36b5cd7
...
...
@@ -5,6 +5,8 @@ import org.springframework.stereotype.Service;
import
java.time.LocalDateTime
;
import
java.time.temporal.ChronoUnit
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
...
...
@@ -31,6 +33,30 @@ public class LoginAttemptService {
attempts
.
remove
(
key
);
}
public
boolean
unlock
(
String
key
)
{
LoginAttempt
attempt
=
attempts
.
get
(
key
);
if
(
attempt
==
null
)
{
return
false
;
}
attempts
.
remove
(
key
);
return
attempt
.
isLocked
();
}
public
List
<
String
>
getLockedUsers
()
{
List
<
String
>
lockedUsers
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
String
,
LoginAttempt
>
entry
:
attempts
.
entrySet
())
{
LoginAttempt
attempt
=
entry
.
getValue
();
if
(
attempt
.
isLocked
())
{
if
(
attempt
.
getLockTime
().
plus
(
lockoutDurationMinutes
,
ChronoUnit
.
MINUTES
).
isBefore
(
LocalDateTime
.
now
()))
{
attempts
.
remove
(
entry
.
getKey
());
}
else
{
lockedUsers
.
add
(
entry
.
getKey
());
}
}
}
return
lockedUsers
;
}
public
boolean
isBlocked
(
String
key
)
{
LoginAttempt
attempt
=
attempts
.
get
(
key
);
if
(
attempt
==
null
)
{
...
...
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