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 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