From de703cfca142d83cbfb127fa7ae78d0918790523 Mon Sep 17 00:00:00 2001
From: Pan_Yujie <102404203+Pan-YuJie@users.noreply.github.com>
Date: Tue, 15 Aug 2023 16:42:16 +0800
Subject: [PATCH] Feature: server add Ldap user authentication (#1392)
---
threadpool/server/auth/pom.xml | 4 +
.../auth/config/GlobalSecurityConfig.java | 39 ++++-
.../auth/config/LdapConfiguration.java | 72 ++++++++
.../auth/filter/JWTAuthenticationFilter.java | 18 +-
.../auth/filter/LdapAuthenticationFilter.java | 155 ++++++++++++++++++
.../cn/hippo4j/auth/model/LdapUserInfo.java | 39 +++++
.../cn/hippo4j/auth/service/LdapService.java | 28 ++++
.../auth/service/impl/LdapServiceImpl.java | 72 ++++++++
.../impl/LdapUserDetailsServiceImpl.java | 115 +++++++++++++
.../service/impl/UserDetailsServiceImpl.java | 15 +-
.../toolkit/BCryptPasswordEncoderTest.java | 20 +++
.../src/main/resources/ldap-back.properties | 14 ++
12 files changed, 584 insertions(+), 7 deletions(-)
create mode 100644 threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/LdapConfiguration.java
create mode 100644 threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/LdapAuthenticationFilter.java
create mode 100644 threadpool/server/auth/src/main/java/cn/hippo4j/auth/model/LdapUserInfo.java
create mode 100644 threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/LdapService.java
create mode 100644 threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapServiceImpl.java
create mode 100644 threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapUserDetailsServiceImpl.java
create mode 100644 threadpool/server/auth/src/test/java/cn/hippo4j/auth/toolkit/BCryptPasswordEncoderTest.java
create mode 100644 threadpool/server/bootstrap/src/main/resources/ldap-back.properties
diff --git a/threadpool/server/auth/pom.xml b/threadpool/server/auth/pom.xml
index ce8d8175..aff6a5d5 100644
--- a/threadpool/server/auth/pom.xml
+++ b/threadpool/server/auth/pom.xml
@@ -62,5 +62,9 @@
hippo4j-threadpool-server-common
${project.version}
+
+ org.springframework.boot
+ spring-boot-starter-data-ldap
+
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/GlobalSecurityConfig.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/GlobalSecurityConfig.java
index b239b42c..83b3d1ca 100644
--- a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/GlobalSecurityConfig.java
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/GlobalSecurityConfig.java
@@ -20,6 +20,7 @@ package cn.hippo4j.auth.config;
import cn.hippo4j.auth.constant.Constants;
import cn.hippo4j.auth.filter.JWTAuthenticationFilter;
import cn.hippo4j.auth.filter.JWTAuthorizationFilter;
+import cn.hippo4j.auth.filter.LdapAuthenticationFilter;
import cn.hippo4j.auth.security.JwtTokenManager;
import cn.hippo4j.auth.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Value;
@@ -28,6 +29,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -54,9 +56,12 @@ public class GlobalSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${hippo4j.core.auth.enabled:true}")
private Boolean enableAuthentication;
- @Resource
+ @Resource(name = "userDetailsServiceImpl")
private UserDetailsService userDetailsService;
+ @Resource(name = "ldapUserDetailsServiceImpl")
+ private UserDetailsService ldapUserDetailsService;
+
@Resource
private JwtTokenManager tokenManager;
@@ -93,7 +98,9 @@ public class GlobalSecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/static/**", "/index.html", "/favicon.ico", "/avatar.jpg").permitAll()
.antMatchers("/doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs").anonymous()
.and()
- .addFilter(new JWTAuthenticationFilter(authenticationManager()))
+ // .addFilter(new JWTAuthenticationFilter(authenticationManager())).authenticationProvider(authenticationProvider())
+ .addFilter(JWTAuthenticationFilter()).authenticationProvider(ldapAuthenticationProvider())
+ .addFilter(LdapAuthenticationFilter()).authenticationProvider(ldapAuthenticationProvider())
.addFilter(new JWTAuthorizationFilter(tokenManager, authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
disableAuthenticationIfNeeded(http);
@@ -106,6 +113,20 @@ public class GlobalSecurityConfig extends WebSecurityConfigurerAdapter {
web.ignoring().antMatchers(ignores);
}
+ private LdapAuthenticationFilter LdapAuthenticationFilter() throws Exception {
+ LdapAuthenticationFilter filter = new LdapAuthenticationFilter(authenticationManager());
+ filter.setLdapUserDetailsService(ldapUserDetailsService);
+ filter.setAuthenticationManager(authenticationManagerBean());
+ return filter;
+ }
+
+ private JWTAuthenticationFilter JWTAuthenticationFilter() throws Exception {
+ JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
+ filter.setLdapUserDetailsService(userDetailsService);
+ filter.setAuthenticationManager(authenticationManagerBean());
+ return filter;
+ }
+
/**
* Injection DaoAuthenticationProvider
* Modify hideUserNotFoundExceptions initial value to false
@@ -120,6 +141,20 @@ public class GlobalSecurityConfig extends WebSecurityConfigurerAdapter {
return provider;
}
+ @Bean
+ public DaoAuthenticationProvider ldapAuthenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(ldapUserDetailsService);
+ authProvider.setPasswordEncoder(bCryptPasswordEncoder());
+ return authProvider;
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.authenticationProvider(authenticationProvider())
+ .authenticationProvider(ldapAuthenticationProvider());
+ }
+
private void disableAuthenticationIfNeeded(HttpSecurity http) throws Exception {
if (Boolean.FALSE.equals(enableAuthentication)) {
http.authorizeRequests().antMatchers("/hippo4j/v1/cs/**").permitAll();
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/LdapConfiguration.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/LdapConfiguration.java
new file mode 100644
index 00000000..1932878d
--- /dev/null
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/config/LdapConfiguration.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hippo4j.auth.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.LdapContextSource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Ldap config.
+ */
+@Configuration
+public class LdapConfiguration {
+
+ private LdapTemplate ldapTemplate;
+
+ @Value("${spring.ldap.urls:}")
+ private String url;
+
+ @Value("${spring.ldap.base:}")
+ private String base;
+
+ @Value("${spring.ldap.embedded.credential.username:}")
+ private String username;
+
+ @Value("${spring.ldap.embedded.credential.password:}")
+ private String password;
+
+ @Bean
+ public LdapContextSource contextSource() {
+ LdapContextSource contextSource = new LdapContextSource();
+ Map config = new HashMap<>(10);
+ contextSource.setUrl(url);
+ contextSource.setBase(base);
+ contextSource.setUserDn(username);
+ contextSource.setPassword(password);
+ // fix garbled characters
+ config.put("java.naming.ldap.attributes.binary", "objectGUID");
+
+ contextSource.setPooled(true);
+ contextSource.setBaseEnvironmentProperties(config);
+ return contextSource;
+ }
+
+ @Bean
+ public LdapTemplate ldapTemplate() {
+ if (null == ldapTemplate) {
+ ldapTemplate = new LdapTemplate(contextSource());
+ }
+ return ldapTemplate;
+ }
+}
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/JWTAuthenticationFilter.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/JWTAuthenticationFilter.java
index 408aa3d4..d9f270b5 100644
--- a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/JWTAuthenticationFilter.java
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/JWTAuthenticationFilter.java
@@ -25,23 +25,25 @@ import cn.hippo4j.auth.toolkit.ReturnT;
import cn.hippo4j.common.toolkit.JSONUtil;
import cn.hippo4j.server.common.base.Results;
import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.codec.DecodingException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -60,11 +62,18 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
private final ThreadLocal rememberMe = new ThreadLocal();
+ private UserDetailsService userDetailsService;
+
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl(BASE_PATH + "/auth/login");
}
+ public void setLdapUserDetailsService(UserDetailsService userDetailsServiceImpl) {
+ this.userDetailsService = userDetailsServiceImpl;
+ }
+
+ @SneakyThrows
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
@@ -78,8 +87,9 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
request.setAttribute("loginUser", loginUser);
rememberMe.set(loginUser.getRememberMe());
- authenticate = authenticationManager.authenticate(
- new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList()));
+ UserDetails userDetails = userDetailsService.loadUserByUsername(loginUser.getUsername());
+ authenticate = new PreAuthenticatedAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+
} catch (GeneralSecurityException e) {
log.warn("Password decode exception: {}", e.getMessage());
throw new DecodingException(e.getMessage());
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/LdapAuthenticationFilter.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/LdapAuthenticationFilter.java
new file mode 100644
index 00000000..9ce982a3
--- /dev/null
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/filter/LdapAuthenticationFilter.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hippo4j.auth.filter;
+
+import cn.hippo4j.auth.model.biz.user.JwtUser;
+import cn.hippo4j.auth.model.biz.user.LoginUser;
+import cn.hippo4j.auth.toolkit.AESUtil;
+import cn.hippo4j.auth.toolkit.JwtTokenUtil;
+import cn.hippo4j.auth.toolkit.ReturnT;
+import cn.hippo4j.common.toolkit.JSONUtil;
+import cn.hippo4j.server.common.base.Results;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.hippo4j.auth.constant.Constants.SPLIT_COMMA;
+import static cn.hippo4j.common.constant.Constants.BASE_PATH;
+import static cn.hippo4j.common.constant.Constants.MAP_INITIAL_CAPACITY;
+
+/**
+ * Ldap Filter
+ */
+@Slf4j
+public class LdapAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
+
+ private final ThreadLocal rememberMe = new ThreadLocal<>();
+
+ private UserDetailsService ldapUserDetailsService;
+
+ public void setLdapUserDetailsService(UserDetailsService ldapUserDetailsServiceImpl) {
+ this.ldapUserDetailsService = ldapUserDetailsServiceImpl;
+ }
+
+ public LdapAuthenticationFilter(AuthenticationManager authenticationManager) {
+ super.setFilterProcessesUrl(BASE_PATH + "/auth/ldap/login");
+ }
+
+ /**
+ * Whether it's just the post way
+ */
+ private boolean postOnly = true;
+
+
+ /**
+ * filter obtains the username and password of LDAP and assembles it on the token.
+ * Then give the token for authorization
+ */
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request
+ , HttpServletResponse response) throws AuthenticationException {
+ if (postOnly && !"POST".equals(request.getMethod())) {
+ throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
+ } else {
+ // Get logged in information from the input stream.
+ Authentication authenticate = null;
+ try {
+ LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
+ String key = new StringBuffer(loginUser.getTag()).reverse().toString();
+ String password = AESUtil.decrypt(loginUser.getPassword(), key);
+ loginUser.setPassword(password);
+ request.setAttribute("loginUser", loginUser);
+ rememberMe.set(loginUser.getRememberMe());
+ // ldap validated
+ UserDetails userDetails = ldapUserDetailsService.loadUserByUsername(loginUser.getUsername());
+ authenticate = new PreAuthenticatedAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ } catch (UsernameNotFoundException e) {
+ log.debug("User {} not found", e.getMessage());
+ throw e;
+ } catch (BadCredentialsException e) {
+ log.debug("Bad credentials exception: {}", e.getMessage());
+ throw e;
+ } catch (Exception e) {
+ log.debug("Attempt authentication error", e);
+ }
+ return authenticate;
+ }
+ }
+
+ @Override
+ protected void successfulAuthentication(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain chain,
+ Authentication authResult) throws IOException {
+ try {
+ JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
+ boolean isRemember = rememberMe.get() == 1;
+ String role = "";
+ Collection extends GrantedAuthority> authorities = jwtUser.getAuthorities();
+ for (GrantedAuthority authority : authorities) {
+ role = authority.getAuthority();
+ }
+ String token = JwtTokenUtil.createToken(jwtUser.getId(), jwtUser.getUsername(), role, isRemember);
+ response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
+ response.setCharacterEncoding("UTF-8");
+ Map maps = new HashMap<>(MAP_INITIAL_CAPACITY);
+ maps.put("data", JwtTokenUtil.TOKEN_PREFIX + token);
+ maps.put("roles", role.split(SPLIT_COMMA));
+ response.getWriter().write(JSONUtil.toJSONString(Results.success(maps)));
+ } finally {
+ rememberMe.remove();
+ }
+ }
+
+ @Override
+ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
+ response.setCharacterEncoding("UTF-8");
+ response.getWriter().write(JSONUtil.toJSONString(new ReturnT<>(ReturnT.JWT_FAIL_CODE, getMessage(failed))));
+ }
+
+ /**
+ * Return different echo information to the front end according to different exception types
+ */
+ private String getMessage(AuthenticationException failed) {
+ String message = "Server Error";
+ if (failed instanceof UsernameNotFoundException) {
+ message = "用户不存在";
+ } else if (failed instanceof BadCredentialsException) {
+ message = "密码错误";
+ }
+ return message;
+ }
+}
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/model/LdapUserInfo.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/model/LdapUserInfo.java
new file mode 100644
index 00000000..9a09c0b8
--- /dev/null
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/model/LdapUserInfo.java
@@ -0,0 +1,39 @@
+package cn.hippo4j.auth.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import org.springframework.ldap.odm.annotations.Attribute;
+import org.springframework.ldap.odm.annotations.DnAttribute;
+import org.springframework.ldap.odm.annotations.Entry;
+import org.springframework.ldap.odm.annotations.Id;
+
+import javax.naming.Name;
+
+@Data
+@Entry(objectClasses = {"inetOrgPerson", "top"})
+public class LdapUserInfo {
+
+ @JsonIgnore
+ @Id
+ private Name dn;
+
+ @Attribute(name = "cn")
+ @DnAttribute(value = "cn")
+ private String userName;
+
+ @Attribute(name = "sn")
+ private String lastName;
+
+ @Attribute(name = "description")
+ private String description;
+
+ @Attribute(name = "telephoneNumber")
+ private String telephoneNumber;
+
+ @Attribute(name = "userPassword")
+ private String password;
+
+ @Attribute(name = "ou")
+ private String organizational;
+
+}
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/LdapService.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/LdapService.java
new file mode 100644
index 00000000..823286d3
--- /dev/null
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/LdapService.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hippo4j.auth.service;
+
+/**
+ * Ldap service.
+ */
+public interface LdapService {
+ /**
+ * Login ldap
+ */
+ void login(String username, String password);
+}
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapServiceImpl.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapServiceImpl.java
new file mode 100644
index 00000000..bb43be02
--- /dev/null
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapServiceImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hippo4j.auth.service.impl;
+
+import cn.hippo4j.auth.service.LdapService;
+import cn.hippo4j.server.common.base.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.ldap.AuthenticationException;
+import org.springframework.ldap.UncategorizedLdapException;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.query.LdapQueryBuilder;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import static org.springframework.ldap.query.LdapQueryBuilder.query;
+
+@Service
+@Slf4j
+public class LdapServiceImpl implements LdapService {
+
+ private final LdapTemplate ldapTemplate;
+
+ @Value("${spring.ldap.object-class:}")
+ private String objectClassName;
+
+ @Value("${spring.ldap.account-attribute:}")
+ private String accountAttribute;
+
+ public LdapServiceImpl(LdapTemplate ldapTemplate) {
+ this.ldapTemplate = ldapTemplate;
+ }
+
+ @Override
+ public void login(String username, String password) {
+ try {
+ ldapTemplate.authenticate(LdapQueryBuilder.query()
+ .where(accountAttribute).is(username)
+ .and(query().where("objectClass").is(objectClassName))
+ , password);
+ log.debug("{} ldap Login successful", username);
+ } catch (EmptyResultDataAccessException e) {
+ throw new UsernameNotFoundException("ldap Can't find the user information ");
+ } catch (AuthenticationException e) {
+ log.debug("The user name or account error");
+ throw new BadCredentialsException("The username or password error");
+ } catch (UncategorizedLdapException e) {
+ log.debug("Please check whether the user name password input");
+ throw new BadCredentialsException("Please check whether the username password input");
+ } catch (Exception e) {
+ throw new ServiceException("Abnormal server");
+ }
+ }
+
+}
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapUserDetailsServiceImpl.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapUserDetailsServiceImpl.java
new file mode 100644
index 00000000..ee5411f1
--- /dev/null
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/LdapUserDetailsServiceImpl.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hippo4j.auth.service.impl;
+
+import cn.hippo4j.auth.mapper.UserMapper;
+import cn.hippo4j.auth.model.UserInfo;
+import cn.hippo4j.auth.model.biz.user.JwtUser;
+import cn.hippo4j.auth.model.biz.user.LoginUser;
+import cn.hippo4j.auth.service.LdapService;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * User details service impl.
+ */
+@Slf4j
+@Service
+public class LdapUserDetailsServiceImpl implements UserDetailsService {
+
+ @Value("${hippo4j.core.auth.enabled:true}")
+ private Boolean enableAuthentication;
+
+ @Resource
+ private UserMapper userMapper;
+
+ @Resource
+ private LdapService ldapService;
+
+ @Override
+ public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
+ JwtUser anonymous = dealWithAnonymous();
+ if (!Objects.isNull(anonymous)) {
+ return anonymous;
+ }
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(requestAttributes)).getRequest();
+ LoginUser loginUser = (LoginUser) request.getAttribute("loginUser");
+ // ldap authentication
+ ldapService.login(userName, loginUser.getPassword());
+ // By querying the data inventory this user does not exist
+ UserInfo userInfo = userMapper.selectOne(Wrappers.lambdaQuery(UserInfo.class)
+ .eq(UserInfo::getUserName, userName)
+ );
+ // the database does not, create a ROLE_USER permission to the default user, password is empty
+ if (Objects.isNull(userInfo)) {
+ userInfo = new UserInfo();
+ userInfo.setPassword("");
+ userInfo.setUserName(loginUser.getUsername());
+ userInfo.setRole("ROLE_USER");
+ userMapper.insert(userInfo);
+ }
+ // structure jwtUser
+ JwtUser jwtUser = new JwtUser();
+ jwtUser.setId(userInfo.getId());
+ jwtUser.setUsername(userName);
+ jwtUser.setPassword("");
+ Set authorities = Collections.singleton(new SimpleGrantedAuthority(userInfo.getRole() + ""));
+ jwtUser.setAuthorities(authorities);
+ return jwtUser;
+ }
+
+ private JwtUser dealWithAnonymous() {
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (requestAttributes == null) {
+ return null;
+ }
+ HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
+ LoginUser loginUser = (LoginUser) request.getAttribute("loginUser");
+ if (Objects.isNull(loginUser)) {
+ return null;
+ }
+ if (Boolean.FALSE.equals(enableAuthentication)) {
+ JwtUser jwtUser = new JwtUser();
+ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
+ jwtUser.setId(1L);
+ jwtUser.setUsername("anonymous");
+ jwtUser.setPassword(bCryptPasswordEncoder.encode(loginUser.getPassword()));
+ Set authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"));
+ jwtUser.setAuthorities(authorities);
+ return jwtUser;
+ }
+ return null;
+ }
+}
diff --git a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/UserDetailsServiceImpl.java b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/UserDetailsServiceImpl.java
index dc62a170..960c25ab 100644
--- a/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/UserDetailsServiceImpl.java
+++ b/threadpool/server/auth/src/main/java/cn/hippo4j/auth/service/impl/UserDetailsServiceImpl.java
@@ -24,11 +24,13 @@ import cn.hippo4j.auth.model.biz.user.LoginUser;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -43,6 +45,7 @@ import java.util.Set;
* User details service impl.
*/
@Slf4j
+@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Value("${hippo4j.core.auth.enabled:true}")
@@ -57,10 +60,20 @@ public class UserDetailsServiceImpl implements UserDetailsService {
if (!Objects.isNull(anonymous)) {
return anonymous;
}
- UserInfo userInfo = userMapper.selectOne(Wrappers.lambdaQuery(UserInfo.class).eq(UserInfo::getUserName, userName));
+ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+ LoginUser loginUser = (LoginUser) request.getAttribute("loginUser");
+ String loginPassword = loginUser.getPassword();
+ UserInfo userInfo = userMapper.selectOne(Wrappers.lambdaQuery(UserInfo.class)
+ .eq(UserInfo::getUserName, userName)
+ );
if (Objects.isNull(userInfo)) {
throw new UsernameNotFoundException(userName);
}
+ // Validation password
+ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
+ if (!bCryptPasswordEncoder.matches(loginPassword, userInfo.getPassword())) {
+ throw new BadCredentialsException(userName + "密码错误,请重新输入");
+ }
JwtUser jwtUser = new JwtUser();
jwtUser.setId(userInfo.getId());
jwtUser.setUsername(userName);
diff --git a/threadpool/server/auth/src/test/java/cn/hippo4j/auth/toolkit/BCryptPasswordEncoderTest.java b/threadpool/server/auth/src/test/java/cn/hippo4j/auth/toolkit/BCryptPasswordEncoderTest.java
new file mode 100644
index 00000000..f1d8a24b
--- /dev/null
+++ b/threadpool/server/auth/src/test/java/cn/hippo4j/auth/toolkit/BCryptPasswordEncoderTest.java
@@ -0,0 +1,20 @@
+package cn.hippo4j.auth.toolkit;
+
+import cn.hippo4j.common.toolkit.Assert;
+import org.junit.Test;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+public class BCryptPasswordEncoderTest {
+
+ @Test
+ public void bCryptPasswordEncoderTest() {
+
+ String password = "12345abc";
+
+ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
+ String encode = bCryptPasswordEncoder.encode(password);
+ boolean matches = bCryptPasswordEncoder.matches(password, encode);
+ Assert.isTrue(matches);
+ }
+
+}
diff --git a/threadpool/server/bootstrap/src/main/resources/ldap-back.properties b/threadpool/server/bootstrap/src/main/resources/ldap-back.properties
new file mode 100644
index 00000000..5b950ede
--- /dev/null
+++ b/threadpool/server/bootstrap/src/main/resources/ldap-back.properties
@@ -0,0 +1,14 @@
+#*************** Ldap Sample Configurations ***************#
+### This configuration file does not take effect
+### Change the LDAP server information to yourself
+### Configure the following configuration file into application.properties
+
+# Ldap Config
+spring.ldap.urls=ldap://127.0.0.1:389
+spring.ldap.base=dc=xxx,dc=com
+spring.ldap.embedded.credential.username=cn=xxxx,dc=xxx,dc=com
+spring.ldap.embedded.credential.password=password
+# Ldap Entry object-class
+spring.ldap.object-class=person
+# Ldap account-attribute CommonName ( cn / uid / username / ... )
+spring.ldap.account-attribute=cn