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
2311025b
Commit
2311025b
authored
Apr 30, 2026
by
lanmw
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
飞机维修厂现场集成AD域单点登录
parent
5b71f719
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
945 additions
and
2 deletions
+945
-2
pom.xml
pom.xml
+6
-0
SsoApplication.java
src/main/java/com/keymobile/sso/SsoApplication.java
+2
-1
ADApi.java
src/main/java/com/keymobile/sso/api/ADApi.java
+248
-0
OpenAPIConfig.java
src/main/java/com/keymobile/sso/conf/OpenAPIConfig.java
+19
-0
RESTAuthenticationEntryPoint.java
.../com/keymobile/sso/conf/RESTAuthenticationEntryPoint.java
+1
-1
SsoSecurityConfig.java
src/main/java/com/keymobile/sso/conf/SsoSecurityConfig.java
+1
-0
LdapException.java
src/main/java/com/keymobile/sso/exception/LdapException.java
+21
-0
LdapInfoRepository.java
...ava/com/keymobile/sso/persistence/LdapInfoRepository.java
+8
-0
LdapWhiteListRepository.java
...om/keymobile/sso/persistence/LdapWhiteListRepository.java
+10
-0
LdapInfo.java
...in/java/com/keymobile/sso/persistence/model/LdapInfo.java
+80
-0
LdapWhiteList.java
...va/com/keymobile/sso/persistence/model/LdapWhiteList.java
+31
-0
ADService.java
src/main/java/com/keymobile/sso/service/ADService.java
+36
-0
AuthRemoteService.java
...ain/java/com/keymobile/sso/service/AuthRemoteService.java
+21
-0
ADServiceImpl.java
...in/java/com/keymobile/sso/service/impl/ADServiceImpl.java
+368
-0
AES.java
src/main/java/com/keymobile/sso/util/AES.java
+93
-0
No files found.
pom.xml
View file @
2311025b
...
@@ -21,6 +21,12 @@
...
@@ -21,6 +21,12 @@
</properties>
</properties>
<dependencies>
<dependencies>
<dependency>
<groupId>
org.springdoc
</groupId>
<artifactId>
springdoc-openapi-starter-webmvc-ui
</artifactId>
<version>
2.0.2
</version>
</dependency>
<dependency>
<dependency>
<groupId>
com.keymobile.authservice
</groupId>
<groupId>
com.keymobile.authservice
</groupId>
<artifactId>
common
</artifactId>
<artifactId>
common
</artifactId>
...
...
src/main/java/com/keymobile/sso/SsoApplication.java
View file @
2311025b
...
@@ -4,10 +4,11 @@ import com.keymobile.authservice.component.SecurityConfig;
...
@@ -4,10 +4,11 @@ import com.keymobile.authservice.component.SecurityConfig;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.cloud.client.discovery.EnableDiscoveryClient
;
import
org.springframework.cloud.client.discovery.EnableDiscoveryClient
;
import
org.springframework.cloud.openfeign.EnableFeignClients
;
import
org.springframework.context.annotation.ComponentScan
;
import
org.springframework.context.annotation.ComponentScan
;
import
org.springframework.context.annotation.FilterType
;
import
org.springframework.context.annotation.FilterType
;
import
org.springframework.context.annotation.PropertySource
;
import
org.springframework.context.annotation.PropertySource
;
@EnableFeignClients
@SpringBootApplication
@SpringBootApplication
@EnableDiscoveryClient
@EnableDiscoveryClient
@ComponentScan
(
basePackages
=
{
"com.keymobile.sso"
,
@ComponentScan
(
basePackages
=
{
"com.keymobile.sso"
,
...
...
src/main/java/com/keymobile/sso/api/ADApi.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
api
;
import
com.keymobile.sso.persistence.model.LdapInfo
;
import
com.keymobile.sso.persistence.model.LdapWhiteList
;
import
com.keymobile.sso.service.ADService
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
jakarta.servlet.http.HttpServletRequest
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.*
;
import
javax.naming.AuthenticationException
;
import
javax.naming.Context
;
import
javax.naming.NamingEnumeration
;
import
javax.naming.directory.Attribute
;
import
javax.naming.directory.Attributes
;
import
javax.naming.directory.SearchControls
;
import
javax.naming.directory.SearchResult
;
import
javax.naming.ldap.InitialLdapContext
;
import
javax.naming.ldap.LdapContext
;
import
java.util.*
;
@Tag
(
name
=
"AD域相关"
,
description
=
"AD域相关"
)
@RestController
@RequestMapping
(
value
=
"/adApi"
)
public
class
ADApi
{
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
ADApi
.
class
);
private
static
final
String
DEFAULT_TIME_OUT
=
"5000"
;
@Autowired
private
ADService
adService
;
@Operation
(
summary
=
"保存ldap基本信息"
)
@PostMapping
(
value
=
"/saveLdapInfo"
)
public
LdapInfo
saveLdapInfo
(
@RequestBody
LdapInfo
ldapInfo
)
{
return
adService
.
saveLdapInfo
(
ldapInfo
);
}
@Operation
(
summary
=
"获取ldap基本信息"
)
@GetMapping
(
value
=
"/getLdapInfo"
)
public
LdapInfo
getLdapInfo
()
{
return
adService
.
getLdapInfo
();
}
@Operation
(
summary
=
"删除ldap基本信息"
)
@DeleteMapping
(
value
=
"/deleteLdapInfo"
)
public
void
deleteLdapInfo
()
{
adService
.
deleteLdapInfo
();
}
@Operation
(
summary
=
"ad域账号登录"
,
description
=
"ad域账号登录"
)
@PostMapping
(
value
=
"/login"
)
public
String
login
(
HttpServletRequest
request
,
@RequestParam
(
value
=
"username"
)
String
username
,
@RequestParam
(
value
=
"password"
)
String
password
)
{
return
adService
.
login
(
request
,
username
,
password
);
}
@Operation
(
summary
=
"同步ad域账号"
)
@PostMapping
(
value
=
"/synUser"
)
public
void
synUser
()
{
adService
.
syncUser
();
}
@Operation
(
summary
=
"测试ad账号连接"
,
description
=
"测试ad账号连接"
)
@PostMapping
(
value
=
"/connect"
)
public
void
connect
(
@RequestParam
(
value
=
"host"
)
String
host
,
@RequestParam
(
value
=
"port"
)
String
port
,
@RequestParam
(
value
=
"username"
)
String
username
,
@RequestParam
(
value
=
"password"
)
String
password
)
{
logger
.
info
(
"connect start. host:{},port:{},username:{},password:{}"
,
host
,
port
,
username
,
password
);
try
{
LdapContext
ctx
=
null
;
Hashtable
<
String
,
String
>
hashEnv
=
new
Hashtable
<>();
//AD的用户名
hashEnv
.
put
(
Context
.
SECURITY_PRINCIPAL
,
username
);
//AD的密码
hashEnv
.
put
(
Context
.
SECURITY_CREDENTIALS
,
password
);
// LDAP工厂类
hashEnv
.
put
(
Context
.
INITIAL_CONTEXT_FACTORY
,
"com.sun.jndi.ldap.LdapCtxFactory"
);
hashEnv
.
put
(
"com.sun.jndi.ldap.connect.timeout"
,
DEFAULT_TIME_OUT
);
// 默认端口389
hashEnv
.
put
(
Context
.
PROVIDER_URL
,
"ldap://"
+
host
+
":"
+
port
);
try
{
// 初始化上下文
ctx
=
new
InitialLdapContext
(
hashEnv
,
null
);
logger
.
info
(
"{}身份验证成功!"
,
username
);
}
catch
(
AuthenticationException
e
)
{
logger
.
error
(
"身份验证失败!"
,
e
);
}
catch
(
javax
.
naming
.
CommunicationException
e
)
{
logger
.
error
(
"AD域连接失败!"
,
e
);
}
catch
(
Exception
e
)
{
logger
.
error
(
"身份验证未知异常!"
,
e
);
}
finally
{
if
(
null
!=
ctx
)
{
try
{
ctx
.
close
();
}
catch
(
Exception
e
)
{
logger
.
error
(
"上下文关闭错误!"
,
e
);
}
}
}
}
catch
(
Exception
e
)
{
logger
.
error
(
"未知异常!"
,
e
);
}
}
@Operation
(
summary
=
"连接查询用户"
,
description
=
"连接查询用户"
)
@PostMapping
(
value
=
"/connectAndSearchUser"
)
public
Object
connectAndSearchUser
(
@RequestParam
(
value
=
"host"
)
String
host
,
@RequestParam
(
value
=
"port"
)
String
port
,
@RequestParam
(
value
=
"username"
)
String
username
,
@RequestParam
(
value
=
"dn"
)
String
dn
,
@RequestParam
(
value
=
"password"
)
String
password
,
@RequestParam
(
value
=
"searchuser"
)
String
searchuser
)
{
logger
.
info
(
"connect start. host:{},port:{},username:{},password:{},dn:{}"
,
host
,
port
,
username
,
password
,
dn
);
List
<
Map
<
String
,
String
>>
users
=
new
ArrayList
<>();
try
{
String
initialContextFactory
=
"com.sun.jndi.ldap.LdapCtxFactory"
;
String
providerUrl
=
"ldap://"
+
host
+
":"
+
port
;
String
filterPrefix
=
"(&(objectCategory=Person)(sAMAccountName="
;
String
filterSuffix
=
"))"
;
Hashtable
<
String
,
String
>
env
=
new
Hashtable
<
String
,
String
>();
env
.
put
(
Context
.
INITIAL_CONTEXT_FACTORY
,
initialContextFactory
);
env
.
put
(
Context
.
PROVIDER_URL
,
providerUrl
);
env
.
put
(
Context
.
SECURITY_PRINCIPAL
,
username
);
env
.
put
(
Context
.
SECURITY_CREDENTIALS
,
password
);
LdapContext
context
=
new
InitialLdapContext
(
env
,
null
);
logger
.
info
(
"{}身份验证成功!"
,
username
);
//baseName 可以通过Softerra LDAP Browser查看
//AD-指定搜索范围为个人,并且用户名为客户端输入的用户名称
String
filter
=
filterPrefix
+
searchuser
+
filterSuffix
;
SearchControls
controls
=
new
SearchControls
();
controls
.
setSearchScope
(
SearchControls
.
SUBTREE_SCOPE
);
//AD-返回用户的名称、别名及部门
controls
.
setReturningAttributes
(
new
String
[]{
"sAMAccountName"
,
"cn"
,
"department"
});
NamingEnumeration
<
SearchResult
>
answer
=
context
.
search
(
dn
,
filter
,
controls
);
while
(
answer
.
hasMore
())
{
SearchResult
result
=
answer
.
next
();
Map
<
String
,
String
>
user
=
new
HashMap
<>();
Attributes
attrs
=
result
.
getAttributes
();
Attribute
attr
=
attrs
.
get
(
"sAMAccountName"
);
String
name
=
attr
==
null
||
attr
.
get
()
==
null
?
null
:
attr
.
get
().
toString
();
user
.
put
(
"sAMAccountName"
,
name
);
attr
=
attrs
.
get
(
"cn"
);
String
alias
=
attr
==
null
||
attr
.
get
()
==
null
?
null
:
attr
.
get
().
toString
();
user
.
put
(
"cn"
,
alias
);
attr
=
attrs
.
get
(
"department"
);
String
dept
=
attr
==
null
||
attr
.
get
()
==
null
?
null
:
attr
.
get
().
toString
();
user
.
put
(
"department"
,
dept
);
users
.
add
(
user
);
}
}
catch
(
Exception
e
)
{
logger
.
error
(
"查询用户报错!"
,
e
);
}
return
users
;
}
@Operation
(
summary
=
"连接查询用户"
,
description
=
"连接查询用户"
)
@PostMapping
(
value
=
"/searchAllUser"
)
public
void
searchAllUser
(
@RequestParam
(
value
=
"host"
)
String
host
,
@RequestParam
(
value
=
"port"
)
String
port
,
@RequestParam
(
value
=
"dn"
)
String
dn
,
@RequestParam
(
value
=
"username"
)
String
username
,
@RequestParam
(
value
=
"password"
)
String
password
)
{
logger
.
info
(
"searchAllUser start. host:{},port:{},username:{},password:{},dn:{}"
,
host
,
port
,
username
,
password
,
dn
);
try
{
Hashtable
<
String
,
String
>
hashEnv
=
new
Hashtable
<>();
String
LDAP_URL
=
"ldap://"
+
host
+
":"
+
port
;
hashEnv
.
put
(
Context
.
INITIAL_CONTEXT_FACTORY
,
"com.sun.jndi.ldap.LdapCtxFactory"
);
hashEnv
.
put
(
Context
.
PROVIDER_URL
,
LDAP_URL
);
hashEnv
.
put
(
Context
.
SECURITY_AUTHENTICATION
,
"simple"
);
hashEnv
.
put
(
Context
.
SECURITY_PRINCIPAL
,
username
);
hashEnv
.
put
(
Context
.
SECURITY_CREDENTIALS
,
password
);
logger
.
info
(
"env setting"
);
LdapContext
ctx
=
null
;
try
{
// 初始化上下文
ctx
=
new
InitialLdapContext
(
hashEnv
,
null
);
logger
.
info
(
"{}身份验证成功!"
,
username
);
SearchControls
searchControls
=
new
SearchControls
();
searchControls
.
setReturningAttributes
(
null
);
searchControls
.
setSearchScope
(
SearchControls
.
SUBTREE_SCOPE
);
String
userSearchFilter
=
"(&(objectCategory=person)(objectClass=user))"
;
NamingEnumeration
<
SearchResult
>
answer
=
ctx
.
search
(
dn
,
userSearchFilter
,
searchControls
);
List
<
Map
<
String
,
Object
>>
lm
=
new
ArrayList
<>();
while
(
answer
.
hasMore
())
{
SearchResult
result
=
answer
.
next
();
NamingEnumeration
<?
extends
Attribute
>
attrs
=
result
.
getAttributes
().
getAll
();
Map
<
String
,
Object
>
map
=
new
HashMap
<>();
while
(
attrs
.
hasMore
())
{
Attribute
attr
=
attrs
.
next
();
map
.
put
(
attr
.
getID
(),
attr
.
get
());
lm
.
add
(
map
);
}
}
logger
.
info
(
"获取到的用户属性为:"
+
lm
);
}
catch
(
AuthenticationException
e
)
{
logger
.
error
(
"身份验证失败!"
,
e
);
}
catch
(
javax
.
naming
.
CommunicationException
e
)
{
logger
.
error
(
"AD域连接失败!"
,
e
);
}
catch
(
Exception
e
)
{
logger
.
error
(
"身份验证未知异常!"
,
e
);
}
finally
{
if
(
null
!=
ctx
)
{
try
{
ctx
.
close
();
}
catch
(
Exception
e
)
{
logger
.
error
(
"上下文关闭错误!"
,
e
);
}
}
}
}
catch
(
Exception
e
)
{
logger
.
error
(
"未知异常!"
,
e
);
}
}
@Operation
(
summary
=
"保存ldap白名单"
)
@PostMapping
(
value
=
"/saveWhiteList"
)
public
LdapWhiteList
saveWhiteList
(
@RequestBody
LdapWhiteList
whiteList
)
{
return
adService
.
saveWhiteList
(
whiteList
);
}
@Operation
(
summary
=
"删除ldap白名单"
)
@DeleteMapping
(
value
=
"/deleteWhiteList"
)
public
void
deleteWhiteList
(
@RequestParam
(
required
=
false
)
String
username
)
{
adService
.
deleteWhiteList
(
username
);
}
@Operation
(
summary
=
"获取ldap白名单"
)
@GetMapping
(
value
=
"/listWhiteList"
)
public
List
<
LdapWhiteList
>
listWhiteList
()
{
return
adService
.
listWhiteList
();
}
}
src/main/java/com/keymobile/sso/conf/OpenAPIConfig.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
conf
;
import
io.swagger.v3.oas.models.OpenAPI
;
import
io.swagger.v3.oas.models.info.Info
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
@Configuration
public
class
OpenAPIConfig
{
@Bean
public
OpenAPI
openAPI
()
{
Info
info
=
new
Info
()
.
title
(
"sso API文档"
)
.
description
(
"sso API文档"
);
return
new
OpenAPI
().
info
(
info
);
}
}
src/main/java/com/keymobile/sso/conf/RESTAuthenticationEntryPoint.java
View file @
2311025b
...
@@ -14,7 +14,7 @@ public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
...
@@ -14,7 +14,7 @@ public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
@Override
public
void
commence
(
HttpServletRequest
request
,
jakarta
.
servlet
.
http
.
HttpServletResponse
response
,
AuthenticationException
authException
)
public
void
commence
(
HttpServletRequest
request
,
jakarta
.
servlet
.
http
.
HttpServletResponse
response
,
AuthenticationException
authException
)
throws
IOException
{
throws
IOException
{
response
.
sendError
(
HttpServletResponse
.
SC_UNAUTHORIZED
);
response
.
sendError
(
HttpServletResponse
.
SC_UNAUTHORIZED
);
}
}
}
}
src/main/java/com/keymobile/sso/conf/SsoSecurityConfig.java
View file @
2311025b
...
@@ -38,6 +38,7 @@ public class SsoSecurityConfig {
...
@@ -38,6 +38,7 @@ public class SsoSecurityConfig {
@Bean
@Bean
protected
SecurityFilterChain
securityFilterChain
(
HttpSecurity
http
)
throws
Exception
{
protected
SecurityFilterChain
securityFilterChain
(
HttpSecurity
http
)
throws
Exception
{
http
.
authorizeHttpRequests
((
request
)
->
{
http
.
authorizeHttpRequests
((
request
)
->
{
request
.
requestMatchers
(
"/adApi/**"
).
permitAll
();
request
.
anyRequest
().
authenticated
();
request
.
anyRequest
().
authenticated
();
});
});
http
.
csrf
((
httpSecurityCsrfConfigurer
)
->
{
http
.
csrf
((
httpSecurityCsrfConfigurer
)
->
{
...
...
src/main/java/com/keymobile/sso/exception/LdapException.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
exception
;
/**
* @author xiesh
* @version 1.0.0
* @date 2024/11/21
* @desc
*/
public
class
LdapException
extends
Exception
{
private
static
final
long
serialVersionUID
=
1L
;
public
LdapException
(
String
errorMsg
)
{
super
(
errorMsg
);
}
public
LdapException
(
String
errorMsg
,
Throwable
cause
)
{
super
(
errorMsg
,
cause
);
}
}
src/main/java/com/keymobile/sso/persistence/LdapInfoRepository.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
persistence
;
import
com.keymobile.sso.persistence.model.LdapInfo
;
import
jakarta.transaction.Transactional
;
import
org.springframework.data.repository.CrudRepository
;
@Transactional
public
interface
LdapInfoRepository
extends
CrudRepository
<
LdapInfo
,
String
>
{
}
src/main/java/com/keymobile/sso/persistence/LdapWhiteListRepository.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
persistence
;
import
com.keymobile.sso.persistence.model.LdapWhiteList
;
import
jakarta.transaction.Transactional
;
import
org.springframework.data.repository.CrudRepository
;
@Transactional
public
interface
LdapWhiteListRepository
extends
CrudRepository
<
LdapWhiteList
,
String
>
{
}
src/main/java/com/keymobile/sso/persistence/model/LdapInfo.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
persistence
.
model
;
import
jakarta.persistence.Column
;
import
jakarta.persistence.Entity
;
import
jakarta.persistence.Id
;
import
jakarta.persistence.Table
;
/**
* @author xiesh
* @version 1.0.0
* @date 2024/4/26
* @desc
*/
@Entity
@Table
(
name
=
"sso_ldap_info"
)
public
class
LdapInfo
{
@Id
private
String
id
;
@Column
(
name
=
"HOST"
,
nullable
=
false
)
private
String
host
;
@Column
(
name
=
"PORT"
,
nullable
=
false
)
private
String
port
;
@Column
(
name
=
"USER_NAME"
,
nullable
=
false
)
private
String
username
;
@Column
(
name
=
"PASSWORD"
,
nullable
=
false
)
private
String
password
;
@Column
(
name
=
"DN"
,
nullable
=
false
)
private
String
dn
;
public
String
getId
()
{
return
id
;
}
public
void
setId
(
String
id
)
{
this
.
id
=
id
;
}
public
String
getHost
()
{
return
host
;
}
public
void
setHost
(
String
host
)
{
this
.
host
=
host
;
}
public
String
getPort
()
{
return
port
;
}
public
void
setPort
(
String
port
)
{
this
.
port
=
port
;
}
public
String
getUsername
()
{
return
username
;
}
public
void
setUsername
(
String
username
)
{
this
.
username
=
username
;
}
public
String
getPassword
()
{
return
password
;
}
public
void
setPassword
(
String
password
)
{
this
.
password
=
password
;
}
public
String
getDn
()
{
return
dn
;
}
public
void
setDn
(
String
dn
)
{
this
.
dn
=
dn
;
}
}
src/main/java/com/keymobile/sso/persistence/model/LdapWhiteList.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
persistence
.
model
;
import
jakarta.persistence.Column
;
import
jakarta.persistence.Entity
;
import
jakarta.persistence.Id
;
import
jakarta.persistence.Table
;
/**
* @author xiesh
* @version 1.0.0
* @date 2024/4/26
* @desc
*/
@Entity
@Table
(
name
=
"sso_ldap_white_list"
)
public
class
LdapWhiteList
{
@Id
@Column
(
name
=
"USER_NAME"
,
nullable
=
false
)
private
String
username
;
@Column
(
name
=
"DNAME"
)
private
String
dname
;
public
String
getUsername
()
{
return
username
;
}
public
void
setUsername
(
String
username
)
{
this
.
username
=
username
;
}
}
src/main/java/com/keymobile/sso/service/ADService.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
service
;
import
com.keymobile.sso.persistence.model.LdapInfo
;
import
com.keymobile.sso.persistence.model.LdapWhiteList
;
import
jakarta.servlet.http.HttpServletRequest
;
import
java.util.List
;
/**
* @author xiesh
* @version 1.0.0
* @date 2024/11/20
* @desc ad域服务
*/
public
interface
ADService
{
LdapInfo
saveLdapInfo
(
LdapInfo
ldapInfo
);
LdapInfo
getLdapInfo
();
void
deleteLdapInfo
();
String
ldapAuthentication
(
String
username
,
String
password
);
String
login
(
HttpServletRequest
request
,
String
username
,
String
password
);
LdapWhiteList
saveWhiteList
(
LdapWhiteList
whiteList
);
void
deleteWhiteList
(
String
username
);
List
<
LdapWhiteList
>
listWhiteList
();
void
syncUser
();
}
src/main/java/com/keymobile/sso/service/AuthRemoteService.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
service
;
import
org.springframework.cloud.openfeign.FeignClient
;
import
org.springframework.web.bind.annotation.*
;
import
java.util.List
;
import
java.util.Map
;
@FeignClient
(
value
=
"authService"
)
public
interface
AuthRemoteService
{
@RequestMapping
(
value
=
"/users/findByName"
)
List
<
Map
<
String
,
Object
>>
getUserByName
(
@RequestParam
(
value
=
"match"
)
String
match
);
@PostMapping
(
value
=
"/users"
)
Map
<
String
,
Object
>
addUser
(
@RequestBody
Map
<
String
,
Object
>
user
);
@PostMapping
(
value
=
"/users/{userId}"
)
Map
<
String
,
Object
>
updateUser
(
@PathVariable
(
value
=
"userId"
)
Long
userId
,
@RequestBody
Map
<
String
,
Object
>
user
);
}
src/main/java/com/keymobile/sso/service/impl/ADServiceImpl.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
service
.
impl
;
import
com.keymobile.authservice.component.CustomizedUserDetailService
;
import
com.keymobile.sso.exception.LdapException
;
import
com.keymobile.sso.logging.LogManager
;
import
com.keymobile.sso.persistence.LdapInfoRepository
;
import
com.keymobile.sso.persistence.LdapWhiteListRepository
;
import
com.keymobile.sso.persistence.model.LdapInfo
;
import
com.keymobile.sso.persistence.model.LdapWhiteList
;
import
com.keymobile.sso.service.ADService
;
import
com.keymobile.sso.service.AuthRemoteService
;
import
com.keymobile.sso.util.AES
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpSession
;
import
org.apache.commons.lang.StringUtils
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.slf4j.MDC
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
;
import
org.springframework.security.core.context.SecurityContextHolder
;
import
org.springframework.security.core.userdetails.UserDetails
;
import
org.springframework.security.web.authentication.WebAuthenticationDetails
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.CollectionUtils
;
import
javax.naming.Context
;
import
javax.naming.NamingEnumeration
;
import
javax.naming.directory.Attribute
;
import
javax.naming.directory.Attributes
;
import
javax.naming.directory.SearchControls
;
import
javax.naming.directory.SearchResult
;
import
javax.naming.ldap.InitialLdapContext
;
import
javax.naming.ldap.LdapContext
;
import
java.util.*
;
import
java.util.stream.Collectors
;
@Service
public
class
ADServiceImpl
implements
ADService
{
@Autowired
private
LdapInfoRepository
ldapInfoRepository
;
@Autowired
private
LdapWhiteListRepository
ldapWhiteListRepository
;
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
ADServiceImpl
.
class
);
private
static
final
String
DEFAULT_TIME_OUT
=
"5000"
;
private
static
final
String
CTX_API
=
"sso.API"
;
@Autowired
private
AuthRemoteService
authService
;
@Autowired
private
CustomizedUserDetailService
customizedUserDetailService
;
public
static
final
String
LDAP_ID
=
"ldap"
;
public
static
final
String
LADP_S_AMACCOUNT_NAME
=
"sAMAccountName"
;
public
static
final
String
LADP_DISTINGUISHED_NAME
=
"distinguishedName"
;
public
static
final
String
LADP_CN
=
"cn"
;
@Value
(
"${ad.defaultRoleId:2}"
)
private
Long
defaultRoleId
;
@Value
(
"${ad.limit:true}"
)
private
String
adLimit
;
// ====================== 改造开始:多节点AD连接 ======================
/**
* 多节点LDAP连接(自动主从切换)
*/
/**
* 支持多节点、每个节点可独立配置端口
* 格式示例:
* 同端口:10.240.16.101,10.240.16.102
* 不同端口:10.240.16.101:389,10.240.16.102:636
*/
private
LdapContext
connectToLdap
(
String
bindUser
,
String
bindPwd
)
{
LdapInfo
ldapInfo
=
getLdapInfo
();
if
(
ldapInfo
==
null
)
{
logger
.
error
(
"未配置LDAP信息"
);
return
null
;
}
// 拆分节点列表
List
<
String
>
nodeList
=
Arrays
.
stream
(
ldapInfo
.
getHost
().
split
(
","
))
.
map
(
String:
:
trim
)
.
filter
(
StringUtils:
:
isNotEmpty
)
.
toList
();
if
(
nodeList
.
isEmpty
())
{
logger
.
error
(
"LDAP主机配置为空"
);
return
null
;
}
// 全局默认端口
String
globalPort
=
ldapInfo
.
getPort
();
String
dn
=
ldapInfo
.
getDn
();
for
(
String
node
:
nodeList
)
{
String
host
;
String
port
;
// 解析 节点自带端口 或 使用全局端口
if
(
node
.
contains
(
":"
))
{
String
[]
hp
=
node
.
split
(
":"
,
2
);
host
=
hp
[
0
].
trim
();
port
=
hp
[
1
].
trim
();
}
else
{
host
=
node
;
port
=
globalPort
;
}
Hashtable
<
String
,
String
>
env
=
new
Hashtable
<>();
String
ldapUrl
=
"ldap://"
+
host
+
":"
+
port
;
env
.
put
(
Context
.
INITIAL_CONTEXT_FACTORY
,
"com.sun.jndi.ldap.LdapCtxFactory"
);
env
.
put
(
Context
.
PROVIDER_URL
,
ldapUrl
);
env
.
put
(
Context
.
SECURITY_PRINCIPAL
,
bindUser
);
env
.
put
(
Context
.
SECURITY_CREDENTIALS
,
bindPwd
);
env
.
put
(
"com.sun.jndi.ldap.connect.timeout"
,
DEFAULT_TIME_OUT
);
env
.
put
(
"com.sun.jndi.ldap.read.timeout"
,
"5000"
);
try
{
logger
.
info
(
"尝试连接AD域节点:{} -> 账号:{}"
,
ldapUrl
,
bindUser
);
LdapContext
ctx
=
new
InitialLdapContext
(
env
,
null
);
logger
.
info
(
"AD域节点连接成功:{}"
,
ldapUrl
);
return
ctx
;
}
catch
(
Exception
e
)
{
logger
.
error
(
"AD域节点连接失败:{},原因:{}"
,
ldapUrl
,
e
.
getMessage
());
}
}
logger
.
error
(
"所有AD域节点全部连接失败"
);
return
null
;
}
// ====================== 改造:登录认证(多节点) ======================
@Override
public
String
ldapAuthentication
(
String
username
,
String
password
)
{
LdapInfo
ldapInfo
=
getLdapInfo
();
if
(
ldapInfo
==
null
)
return
"未配置ldap"
;
String
dn
=
ldapInfo
.
getDn
();
String
ldapUserName
=
username
+
"@"
+
dn
;
LdapContext
ctx
=
connectToLdap
(
ldapUserName
,
password
);
if
(
ctx
!=
null
)
{
try
{
ctx
.
close
();
}
catch
(
Exception
ignored
)
{}
return
"ok"
;
}
else
{
return
"AD域身份验证失败或所有域控不可用"
;
}
}
// ====================== 改造:查询用户信息(多节点) ======================
private
Map
<
String
,
String
>
searchUserInfoByName
(
String
searchName
)
throws
Exception
{
LdapInfo
ldapInfo
=
getLdapInfo
();
if
(
ldapInfo
==
null
)
throw
new
LdapException
(
"未配置ldap信息"
);
String
bindUser
=
ldapInfo
.
getUsername
()
+
"@"
+
ldapInfo
.
getDn
();
String
bindPwd
=
AES
.
decrypt
(
ldapInfo
.
getPassword
());
LdapContext
ctx
=
connectToLdap
(
bindUser
,
bindPwd
);
if
(
ctx
==
null
)
throw
new
Exception
(
"AD域连接失败,无法查询用户"
);
Map
<
String
,
String
>
userInfo
=
new
HashMap
<>();
try
{
String
[]
dnArg
=
StringUtils
.
split
(
ldapInfo
.
getDn
(),
"."
);
String
dnStr
=
Arrays
.
stream
(
dnArg
)
.
map
(
e
->
"DC="
+
e
)
.
collect
(
Collectors
.
joining
(
","
));
SearchControls
searchControls
=
new
SearchControls
();
searchControls
.
setSearchScope
(
SearchControls
.
SUBTREE_SCOPE
);
searchControls
.
setReturningAttributes
(
new
String
[]{
LADP_S_AMACCOUNT_NAME
,
LADP_CN
,
LADP_DISTINGUISHED_NAME
});
String
filter
=
String
.
format
(
"(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s))"
,
searchName
);
NamingEnumeration
<
SearchResult
>
answer
=
ctx
.
search
(
dnStr
,
filter
,
searchControls
);
if
(
answer
.
hasMore
())
{
SearchResult
result
=
answer
.
next
();
Attributes
attrs
=
result
.
getAttributes
();
Attribute
attr
=
attrs
.
get
(
LADP_S_AMACCOUNT_NAME
);
userInfo
.
put
(
LADP_S_AMACCOUNT_NAME
,
attr
==
null
?
null
:
attr
.
get
().
toString
());
attr
=
attrs
.
get
(
LADP_CN
);
userInfo
.
put
(
LADP_CN
,
attr
==
null
?
null
:
attr
.
get
().
toString
());
attr
=
attrs
.
get
(
LADP_DISTINGUISHED_NAME
);
userInfo
.
put
(
LADP_DISTINGUISHED_NAME
,
attr
==
null
?
null
:
attr
.
get
().
toString
());
}
}
finally
{
try
{
ctx
.
close
();
}
catch
(
Exception
e
)
{}
}
return
userInfo
;
}
// ====================== 改造:同步用户(多节点) ======================
@Override
public
void
syncUser
()
{
logger
.
info
(
"ad域账号同步开始"
);
LdapInfo
ldapInfo
=
getLdapInfo
();
if
(
ldapInfo
==
null
)
{
logger
.
error
(
"未配置ldap"
);
return
;
}
String
bindUser
=
ldapInfo
.
getUsername
()
+
"@"
+
ldapInfo
.
getDn
();
String
bindPwd
=
AES
.
decrypt
(
ldapInfo
.
getPassword
());
LdapContext
ctx
=
connectToLdap
(
bindUser
,
bindPwd
);
if
(
ctx
==
null
)
{
logger
.
error
(
"AD域同步用户失败:所有域控连接失败"
);
return
;
}
try
{
String
[]
dnArg
=
StringUtils
.
split
(
ldapInfo
.
getDn
(),
"."
);
String
dnStr
=
Arrays
.
stream
(
dnArg
)
.
map
(
e
->
"DC="
+
e
)
.
collect
(
Collectors
.
joining
(
","
));
SearchControls
controls
=
new
SearchControls
();
controls
.
setSearchScope
(
SearchControls
.
SUBTREE_SCOPE
);
NamingEnumeration
<
SearchResult
>
answer
=
ctx
.
search
(
dnStr
,
"(&(objectCategory=person)(objectClass=user))"
,
controls
);
while
(
answer
.
hasMore
())
{
Map
<
String
,
String
>
ldapUser
=
new
HashMap
<>();
Attributes
attrs
=
answer
.
next
().
getAttributes
();
Attribute
attr
=
attrs
.
get
(
LADP_S_AMACCOUNT_NAME
);
String
name
=
attr
==
null
?
null
:
attr
.
get
().
toString
();
ldapUser
.
put
(
LADP_S_AMACCOUNT_NAME
,
name
);
attr
=
attrs
.
get
(
LADP_CN
);
String
alias
=
attr
==
null
?
name
:
attr
.
get
().
toString
();
ldapUser
.
put
(
LADP_CN
,
alias
);
if
(
StringUtils
.
isNotBlank
(
name
))
{
Map
<
String
,
Object
>
user
=
getUserByName
(
name
);
if
(
user
==
null
)
{
Map
<
String
,
Object
>
toAdd
=
new
HashMap
<>();
toAdd
.
put
(
"name"
,
name
);
toAdd
.
put
(
"dname"
,
ldapUser
.
get
(
LADP_CN
));
toAdd
.
put
(
"password"
,
"37fa265330ad83eaa879efb12312db6380896cf639"
);
List
<
Map
<
String
,
Object
>>
roles
=
new
ArrayList
<>();
Map
<
String
,
Object
>
roleMap
=
new
HashMap
<>();
roleMap
.
put
(
"id"
,
defaultRoleId
);
roles
.
add
(
roleMap
);
toAdd
.
put
(
"dataRoleAbstracts"
,
roles
);
authService
.
addUser
(
toAdd
);
logger
.
info
(
"同步AD用户:{}"
,
name
);
}
}
}
}
catch
(
Exception
e
)
{
logger
.
error
(
"同步用户异常"
,
e
);
}
finally
{
try
{
ctx
.
close
();
}
catch
(
Exception
e
)
{}
}
logger
.
info
(
"ad域账号同步结束"
);
}
// ====================== 以下代码完全不动,保持你原有逻辑 ======================
@Override
public
LdapInfo
saveLdapInfo
(
LdapInfo
ldapInfo
)
{
ldapInfo
.
setId
(
LDAP_ID
);
LdapInfo
oldLdap
=
getLdapInfo
();
if
(
oldLdap
==
null
||
!
StringUtils
.
equals
(
ldapInfo
.
getPassword
(),
oldLdap
.
getPassword
()))
{
ldapInfo
.
setPassword
(
AES
.
encrypt
(
ldapInfo
.
getPassword
()));
}
return
ldapInfoRepository
.
save
(
ldapInfo
);
}
@Override
public
LdapInfo
getLdapInfo
()
{
return
ldapInfoRepository
.
findById
(
LDAP_ID
).
orElse
(
null
);
}
@Override
public
void
deleteLdapInfo
()
{
ldapInfoRepository
.
deleteAll
();
}
@Override
public
String
login
(
HttpServletRequest
request
,
String
username
,
String
password
)
{
String
result
=
null
;
if
(!
checkWhiteList
(
username
))
{
return
"用户无权访问"
;
}
try
{
result
=
ldapAuthentication
(
username
,
password
);
if
(
StringUtils
.
equals
(
result
,
"ok"
))
{
Map
<
String
,
Object
>
user
=
getUserByName
(
username
);
if
(
null
==
user
||
CollectionUtils
.
isEmpty
(
user
))
{
Map
<
String
,
String
>
ldapUserInfo
=
searchUserInfoByName
(
username
);
Map
<
String
,
Object
>
toAdd
=
new
HashMap
<>();
toAdd
.
put
(
"name"
,
username
);
toAdd
.
put
(
"dname"
,
ldapUserInfo
.
get
(
LADP_CN
)
==
null
?
username
:
ldapUserInfo
.
get
(
LADP_CN
));
toAdd
.
put
(
"password"
,
"37fa265330ad83eaa879efb12312db6380896cf639"
);
List
<
Map
<
String
,
Object
>>
dataRoleAbstracts
=
new
ArrayList
<>();
Map
<
String
,
Object
>
roleMap
=
new
HashMap
<>();
roleMap
.
put
(
"id"
,
defaultRoleId
);
dataRoleAbstracts
.
add
(
roleMap
);
toAdd
.
put
(
"dataRoleAbstracts"
,
dataRoleAbstracts
);
authService
.
addUser
(
toAdd
);
}
UserDetails
userDetails
=
customizedUserDetailService
.
loadUserByUsername
(
username
);
UsernamePasswordAuthenticationToken
authentication
=
new
UsernamePasswordAuthenticationToken
(
userDetails
,
userDetails
.
getPassword
(),
userDetails
.
getAuthorities
());
authentication
.
setDetails
(
new
WebAuthenticationDetails
(
request
));
SecurityContextHolder
.
getContext
().
setAuthentication
(
authentication
);
HttpSession
session
=
request
.
getSession
(
true
);
session
.
setAttribute
(
"SPRING_SECURITY_CONTEXT"
,
SecurityContextHolder
.
getContext
());
MDC
.
put
(
"user"
,
username
);
MDC
.
put
(
"session"
,
session
.
getId
());
LogManager
.
logInfo
(
CTX_API
,
"统一账号认证登录"
);
}
}
catch
(
Exception
e
)
{
logger
.
error
(
"登录异常!"
,
e
);
result
=
e
.
getMessage
();
}
return
result
;
}
@Override
public
LdapWhiteList
saveWhiteList
(
LdapWhiteList
ldapWhiteList
)
{
return
ldapWhiteListRepository
.
save
(
ldapWhiteList
);
}
@Override
public
void
deleteWhiteList
(
String
username
)
{
if
(
StringUtils
.
isNotBlank
(
username
))
{
ldapWhiteListRepository
.
deleteById
(
username
);
}
else
{
ldapWhiteListRepository
.
deleteAll
();
}
}
@Override
public
List
<
LdapWhiteList
>
listWhiteList
()
{
return
(
List
<
LdapWhiteList
>)
ldapWhiteListRepository
.
findAll
();
}
private
Map
<
String
,
Object
>
getUserByName
(
String
username
)
{
List
<
Map
<
String
,
Object
>>
matchUser
=
authService
.
getUserByName
(
username
);
if
(
matchUser
!=
null
)
{
return
matchUser
.
stream
()
.
filter
(
e
->
StringUtils
.
equals
(
username
,
String
.
valueOf
(
e
.
get
(
"name"
))))
.
findFirst
().
orElse
(
null
);
}
return
null
;
}
private
Boolean
checkWhiteList
(
String
userName
)
{
if
(!
StringUtils
.
equals
(
"true"
,
adLimit
))
{
return
true
;
}
return
ldapWhiteListRepository
.
findById
(
userName
).
isPresent
();
}
}
\ No newline at end of file
src/main/java/com/keymobile/sso/util/AES.java
0 → 100644
View file @
2311025b
package
com
.
keymobile
.
sso
.
util
;
import
org.apache.commons.lang.StringUtils
;
import
javax.crypto.Cipher
;
import
javax.crypto.spec.SecretKeySpec
;
import
java.nio.charset.StandardCharsets
;
import
java.util.Base64
;
public
class
AES
{
//编码方式
public
static
final
String
CODE_TYPE
=
"UTF-8"
;
//填充类型
public
static
final
String
AES_TYPE
=
"AES/ECB/PKCS5Padding"
;
//私钥
private
static
String
AES_KEY
=
"4444111133332222"
;
//AES固定格式为128/192/256 bits.即:16/24/32bytes。DES固定格式为128bits,即8bytes。
/**
* 加密
*
* @param cleartext
* @return
*/
public
static
String
encrypt
(
String
cleartext
)
{
//加密方式: AES128(CBC/PKCS5Padding) + Base64, 私钥:1111222233334444
try
{
if
(
StringUtils
.
isNotBlank
(
cleartext
)){
//IvParameterSpec zeroIv = new IvParameterSpec(VIPARA.getBytes());
//两个参数,第一个为私钥字节数组, 第二个为加密方式 AES或者DES
SecretKeySpec
key
=
new
SecretKeySpec
(
AES_KEY
.
getBytes
(
StandardCharsets
.
UTF_8
),
"AES"
);
//实例化加密类,参数为加密方式,要写全
Cipher
cipher
=
Cipher
.
getInstance
(
AES_TYPE
);
//PKCS5Padding比PKCS7Padding效率高,PKCS7Padding可支持IOS加解密
//初始化,此方法可以采用三种方式,按加密算法要求来添加。(1)无第三个参数(2)第三个参数为SecureRandom random = new SecureRandom();中random对象,随机数。(AES不可采用这种方法)(3)采用此代码中的IVParameterSpec
//加密时使用:ENCRYPT_MODE; 解密时使用:DECRYPT_MODE;
cipher
.
init
(
Cipher
.
ENCRYPT_MODE
,
key
);
//CBC类型的可以在第三个参数传递偏移量zeroIv,ECB没有偏移量
//加密操作,返回加密后的字节数组,然后需要编码。主要编解码方式有Base64, HEX, UUE,7bit等等。此处看服务器需要什么编码方式
byte
[]
encryptedData
=
cipher
.
doFinal
(
cleartext
.
getBytes
(
StandardCharsets
.
UTF_8
));
// 修改点:使用 java.util.Base64 替代 sun.misc.BASE64Encoder
return
Base64
.
getEncoder
().
encodeToString
(
encryptedData
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
/**
* 解密
*
* @param encrypted
* @return
*/
public
static
String
decrypt
(
String
encrypted
)
{
try
{
if
(
StringUtils
.
isNotBlank
(
encrypted
)){
// 修改点:使用 java.util.Base64 替代 sun.misc.BASE64Decoder
byte
[]
byteMi
=
Base64
.
getDecoder
().
decode
(
encrypted
);
//IvParameterSpec zeroIv = new IvParameterSpec(VIPARA.getBytes());
SecretKeySpec
key
=
new
SecretKeySpec
(
AES_KEY
.
getBytes
(
StandardCharsets
.
UTF_8
),
"AES"
);
Cipher
cipher
=
Cipher
.
getInstance
(
AES_TYPE
);
//与加密时不同MODE:Cipher.DECRYPT_MODE
cipher
.
init
(
Cipher
.
DECRYPT_MODE
,
key
);
//CBC类型的可以在第三个参数传递偏移量zeroIv,ECB没有偏移量
byte
[]
decryptedData
=
cipher
.
doFinal
(
byteMi
);
return
new
String
(
decryptedData
,
CODE_TYPE
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
public
static
void
setAesKey
(
String
aesKey
){
AES_KEY
=
aesKey
;
}
/**
* 测试
*
* @param args
* @throws Exception
*/
public
static
void
main
(
String
[]
args
)
throws
Exception
{
String
pass
=
"Ims1555#"
;
setAesKey
(
"4444111133332222"
);
System
.
out
.
println
(
"加密内容:"
+
encrypt
(
pass
));
String
content
=
encrypt
(
pass
);
System
.
out
.
println
(
"解密内容:"
+
decrypt
(
content
));
}
}
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