From 90d05b035764ca439f2887d8227cceb1335e5892 Mon Sep 17 00:00:00 2001 From: Shashwat Tiwari Date: Fri, 17 Jan 2025 18:47:32 +0530 Subject: [PATCH] [LDAP] Add LDAP Authentication Provider Support. (#269) Co-authored-by: BilwaST --- README.md | 26 +++++++ README_CN.md | 25 ++++++- pom.xml | 11 +++ seatunnel-server/seatunnel-app/pom.xml | 9 +++ .../seatunnel/app/common/Constants.java | 3 + .../app/config/LdapAuthenticationBuilder.java | 74 +++++++++++++++++++ ...eatunnelAuthenticationProvidersConfig.java | 32 ++++++++ .../SeatunnelLdapAuthenticationProvider.java | 64 ++++++++++++++++ .../app/controller/UserController.java | 7 +- .../seatunnel/app/dal/dao/IUserDao.java | 2 +- .../app/dal/dao/impl/UserDaoImpl.java | 5 +- .../apache/seatunnel/app/dal/entity/User.java | 2 + .../seatunnel/app/dal/mapper/UserMapper.java | 4 +- .../app/domain/dto/user/UpdateUserDto.java | 1 + .../strategy/IAuthenticationStrategy.java | 24 ++++++ .../impl/DBAuthenticationStrategy.java | 54 ++++++++++++++ .../impl/LDAPAuthenticationStrategy.java | 68 +++++++++++++++++ .../seatunnel/app/service/IUserService.java | 2 +- .../app/service/impl/UserServiceImpl.java | 53 +++++++++---- .../src/main/resources/application.yml | 12 ++- .../seatunnel/app/dal/mapper/UserMapper.xml | 10 ++- .../resources/script/seatunnel_server_h2.sql | 1 + .../script/seatunnel_server_mysql.sql | 1 + .../server/common/SeatunnelErrorEnum.java | 4 + .../app/common/SeatunnelWebTestingBase.java | 26 ++++++- .../app/test/UserControllerTest.java | 60 ++++++++++++++- .../src/test/resources/application.yml | 10 +++ tools/dependencies/known-dependencies.txt | 4 + 28 files changed, 561 insertions(+), 33 deletions(-) create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/LdapAuthenticationBuilder.java create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelAuthenticationProvidersConfig.java create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelLdapAuthenticationProvider.java create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/IAuthenticationStrategy.java create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/DBAuthenticationStrategy.java create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/LDAPAuthenticationStrategy.java diff --git a/README.md b/README.md index 28e06a619..30a1aadaa 100644 --- a/README.md +++ b/README.md @@ -238,3 +238,29 @@ NOTE: This feature is currently useful when execution is done through the API. T Execute the following SQL to upgrade the database: ```ALTER TABLE `t_st_job_instance` ADD COLUMN `error_message` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL;``` + +#### 2. Upgrade from 1.0.2 or before to 1.0.3 or after. +- Execute the following SQL to upgrade the database: + ``` + ALTER TABLE `user` ADD COLUMN `auth_provider` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'DB'; + ``` +- Enabling LDAP Support, + - For LDAP support, you need to add the LDAP server configurations and include LDAP in the list of authentication providers in the application.yml file. + - If no authentication providers are defined, default DB strategy will be used, and no changes are required. + - Below is a sample configuration for both the authentication providers and LDAP server settings. + ``` + # sample application.yaml + spring: + ldap: + url: ldap://localhost:389 + search: + base: ou=people,dc=example,dc=com + filter: (uid={0}) + domain: example.com + seatunnel: + authentication: + providers: + - DB + - LDAP + ``` + diff --git a/README_CN.md b/README_CN.md index 8be3e6abb..e221ecc12 100644 --- a/README_CN.md +++ b/README_CN.md @@ -231,4 +231,27 @@ sh bin/seatunnel-backend-daemon.sh start ```ALTER TABLE `t_st_job_instance` ADD COLUMN `error_message` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL;``` - +#### 2. 从1.0.2或更早版本升级到1.0.3或更高版本。 +- 执行以下SQL语句以升级数据库: + ``` + ALTER TABLE `user` ADD COLUMN `auth_provider` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'DB'; + ``` +- 启用LDAP支持, + - 要启用LDAP支持,您需要在`application.yml`文件中添加LDAP服务器配置,并将LDAP包含在认证提供者列表中。 + - 如果未定义任何认证提供者,将使用默认的DB策略,不需要做任何更改。 + - 以下是认证提供者和LDAP服务器设置的示例配置。 + ``` + # sample application.yaml + spring: + ldap: + url: ldap://localhost:389 + search: + base: ou=people,dc=example,dc=com + filter: (uid={0}) + domain: example.com + seatunnel: + authentication: + providers: + - DB + - LDAP + ``` diff --git a/pom.xml b/pom.xml index 3b064bad8..852f2f308 100644 --- a/pom.xml +++ b/pom.xml @@ -90,6 +90,7 @@ 2.6.8 5.3.20 + 5.6.5 3.5.3.1 1.2.9 2.6.1 @@ -349,6 +350,16 @@ spring-boot-starter-jdbc ${spring-boot.version} + + org.springframework.security + spring-security-core + ${spring.security.version} + + + org.springframework.security + spring-security-ldap + ${spring.security.version} + org.springframework.boot spring-boot-starter-test diff --git a/seatunnel-server/seatunnel-app/pom.xml b/seatunnel-server/seatunnel-app/pom.xml index 24c2a44fe..98d93670c 100644 --- a/seatunnel-server/seatunnel-app/pom.xml +++ b/seatunnel-server/seatunnel-app/pom.xml @@ -310,6 +310,15 @@ org.springframework.boot spring-boot-starter-jdbc + + org.springframework.security + spring-security-core + + + + org.springframework.security + spring-security-ldap + com.baomidou diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java index b32c48ed4..886c1f70e 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java @@ -655,4 +655,7 @@ private Constants() { public static final int DEFAULT_ALERT_GROUP_ID = 1; public static final String TASK_ID = "taskId"; + + public static final String AUTHENTICATION_PROVIDER_LDAP = "LDAP"; + public static final String AUTHENTICATION_PROVIDER_DB = "DB"; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/LdapAuthenticationBuilder.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/LdapAuthenticationBuilder.java new file mode 100644 index 000000000..a69e9c63b --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/LdapAuthenticationBuilder.java @@ -0,0 +1,74 @@ +/* + * 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 org.apache.seatunnel.app.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy; +import org.springframework.security.core.Authentication; +import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.authentication.BindAuthenticator; +import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; +import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class LdapAuthenticationBuilder { + + @Value("${spring.ldap.url}") + private String ldapUrl; + + @Value("${spring.ldap.search.base}") + private String ldapSearchBase; + + @Value("${spring.ldap.search.domain}") + private String ldapSearchDomain; + + public LdapAuthenticationProvider buildLdapAuthenticationProvider( + Authentication authentication) { + + LdapContextSource ldapContextSource = new DefaultSpringSecurityContextSource(ldapUrl); + String ldapBindUser = authentication.getName(); + ldapContextSource.setUserDn( + new StringBuilder() + .append(ldapBindUser) + .append("@") + .append(ldapSearchDomain) + .toString()); + ldapContextSource.setPassword(authentication.getCredentials().toString()); + ldapContextSource.setCacheEnvironmentProperties(false); + ldapContextSource.setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy()); + ldapContextSource.afterPropertiesSet(); + + String ldapAuthID = + ldapBindUser.toLowerCase().endsWith("@" + ldapSearchDomain) + ? ldapBindUser + : ldapBindUser + "@" + ldapSearchDomain; + String searchFilter = "(userPrincipalName=" + ldapAuthID + ")"; + FilterBasedLdapUserSearch userSearch = + new FilterBasedLdapUserSearch(ldapSearchBase, searchFilter, ldapContextSource); + userSearch.setSearchSubtree(true); + BindAuthenticator authenticator = new BindAuthenticator(ldapContextSource); + authenticator.setUserSearch(userSearch); + LdapAuthenticationProvider ldapAuthenticationProvider = + new LdapAuthenticationProvider(authenticator); + return ldapAuthenticationProvider; + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelAuthenticationProvidersConfig.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelAuthenticationProvidersConfig.java new file mode 100644 index 000000000..e1c52c432 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelAuthenticationProvidersConfig.java @@ -0,0 +1,32 @@ +/* + * 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 org.apache.seatunnel.app.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Configuration +@ConfigurationProperties(prefix = "spring.authentication") +public class SeatunnelAuthenticationProvidersConfig { + private List providers = new ArrayList<>(); +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelLdapAuthenticationProvider.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelLdapAuthenticationProvider.java new file mode 100644 index 000000000..846943613 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/SeatunnelLdapAuthenticationProvider.java @@ -0,0 +1,64 @@ +/* + * 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 org.apache.seatunnel.app.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +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.ldap.authentication.LdapAuthenticationProvider; + +import lombok.extern.slf4j.Slf4j; + +@Configuration +@Slf4j +public class SeatunnelLdapAuthenticationProvider implements AuthenticationProvider { + + @Autowired LdapAuthenticationBuilder ldapAuthenticationBuilder; + + @Override + public boolean supports(Class authentication) { + return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); + } + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + try { + LdapAuthenticationProvider ldapAuthenticationProvider = + ldapAuthenticationBuilder.buildLdapAuthenticationProvider(authentication); + return ldapAuthenticationProvider.authenticate(authentication); + } catch (BadCredentialsException ex) { + log.error("Invalid credentials for user : {}", authentication.getName()); + throw new BadCredentialsException("Invalid credentials"); + } catch (Exception ex) { + log.error( + "Error while authenticating user : {}, reason :{}", + authentication.getName(), + ex.getMessage()); + throw new AuthenticationException(ex.getMessage().toString()) { + @Override + public String getMessage() { + return super.getMessage(); + } + }; + } + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/UserController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/UserController.java index 106f23ff9..59c68ab4b 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/UserController.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/UserController.java @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -110,8 +111,10 @@ public Result disable( } @PostMapping("/login") - public Result login(@RequestBody UserLoginReq req) { - return Result.success(iUserService.login(req)); + public Result login( + @RequestBody UserLoginReq req, + @RequestHeader(value = "X-Seatunnel-Auth-Type", required = false) String authType) { + return Result.success(iUserService.login(req, authType)); } @PatchMapping("/logout") diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java index 8adb64422..be5dd16df 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java @@ -45,7 +45,7 @@ public interface IUserDao { User getByName(String user); - User checkPassword(String username, String password); + User checkPassword(String username, String password, String authProvider); long insertLoginLog(UserLoginLogDto dto); diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java index 0b26991d9..3d1afef29 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java @@ -52,6 +52,7 @@ public int add(UpdateUserDto dto) { user.setPassword(dto.getPassword()); user.setType((byte) dto.getType()); user.setStatus((byte) dto.getStatus()); + user.setAuthProvider(dto.getAuthProvider()); userMapper.insert(user); return user.getId(); @@ -114,8 +115,8 @@ public User getByName(String user) { } @Override - public User checkPassword(String username, String password) { - return userMapper.selectByNameAndPasswd(username, password); + public User checkPassword(String username, String password, String authProvider) { + return userMapper.selectByNameAndPasswd(username, password, authProvider); } @Override diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/User.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/User.java index 737776534..978360a0d 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/User.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/User.java @@ -36,4 +36,6 @@ public class User { private Date createTime; private Date updateTime; + + private String authProvider; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java index 5d13b9761..9648ecd99 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java @@ -44,7 +44,9 @@ List selectBySelectiveAndPage( int countBySelective(@Param("user") User user); User selectByNameAndPasswd( - @Param("username") String username, @Param("password") String password); + @Param("username") String username, + @Param("password") String password, + @Param("authProvider") String authProvider); List queryEnabledUsers(); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UpdateUserDto.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UpdateUserDto.java index d65383ed8..f4d430cd4 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UpdateUserDto.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UpdateUserDto.java @@ -28,4 +28,5 @@ public class UpdateUserDto { private String password; private int status; private int type; + private String authProvider; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/IAuthenticationStrategy.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/IAuthenticationStrategy.java new file mode 100644 index 000000000..4e77c8caf --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/IAuthenticationStrategy.java @@ -0,0 +1,24 @@ +/* + * 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 org.apache.seatunnel.app.security.authentication.strategy; + +import org.apache.seatunnel.app.dal.entity.User; +import org.apache.seatunnel.app.domain.request.user.UserLoginReq; + +public interface IAuthenticationStrategy { + User authenticate(UserLoginReq req); +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/DBAuthenticationStrategy.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/DBAuthenticationStrategy.java new file mode 100644 index 000000000..eff7fa367 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/DBAuthenticationStrategy.java @@ -0,0 +1,54 @@ +/* + * 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 org.apache.seatunnel.app.security.authentication.strategy.impl; + +import org.apache.seatunnel.app.common.Constants; +import org.apache.seatunnel.app.dal.dao.IUserDao; +import org.apache.seatunnel.app.dal.entity.User; +import org.apache.seatunnel.app.domain.request.user.UserLoginReq; +import org.apache.seatunnel.app.security.authentication.strategy.IAuthenticationStrategy; +import org.apache.seatunnel.app.utils.PasswordUtils; +import org.apache.seatunnel.server.common.SeatunnelException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static org.apache.seatunnel.server.common.SeatunnelErrorEnum.USERNAME_PASSWORD_NO_MATCHED; + +@Component +public class DBAuthenticationStrategy implements IAuthenticationStrategy { + + @Autowired private IUserDao userDaoImpl; + + @Value("${user.default.passwordSalt:seatunnel}") + private String defaultSalt; + + @Override + public User authenticate(UserLoginReq req) { + final String password = PasswordUtils.encryptWithSalt(defaultSalt, req.getPassword()); + final User user = + userDaoImpl.checkPassword( + req.getUsername(), password, Constants.AUTHENTICATION_PROVIDER_DB); + if (Objects.isNull(user)) { + throw new SeatunnelException(USERNAME_PASSWORD_NO_MATCHED); + } + return user; + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/LDAPAuthenticationStrategy.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/LDAPAuthenticationStrategy.java new file mode 100644 index 000000000..1fa9bad51 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/authentication/strategy/impl/LDAPAuthenticationStrategy.java @@ -0,0 +1,68 @@ +/* + * 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 org.apache.seatunnel.app.security.authentication.strategy.impl; + +import org.apache.seatunnel.app.common.Constants; +import org.apache.seatunnel.app.config.SeatunnelLdapAuthenticationProvider; +import org.apache.seatunnel.app.dal.dao.IUserDao; +import org.apache.seatunnel.app.dal.entity.User; +import org.apache.seatunnel.app.domain.dto.user.UpdateUserDto; +import org.apache.seatunnel.app.domain.request.user.UserLoginReq; +import org.apache.seatunnel.app.security.authentication.strategy.IAuthenticationStrategy; +import org.apache.seatunnel.server.common.SeatunnelException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; + +import static org.apache.seatunnel.server.common.SeatunnelErrorEnum.USERNAME_PASSWORD_NO_MATCHED; + +@Component +public class LDAPAuthenticationStrategy implements IAuthenticationStrategy { + + @Autowired private IUserDao userDaoImpl; + + @Autowired private SeatunnelLdapAuthenticationProvider seatunnelLdapAuthenticationProvider; + + @Override + public User authenticate(UserLoginReq req) { + String username = req.getUsername(); + String password = req.getPassword(); + Authentication authenticationRequest = + new UsernamePasswordAuthenticationToken(username, password); + try { + seatunnelLdapAuthenticationProvider.authenticate(authenticationRequest); + } catch (AuthenticationException ex) { + throw new SeatunnelException(USERNAME_PASSWORD_NO_MATCHED); + } + + if (userDaoImpl.getByName(username) == null) { + // 2. add a new user. + final UpdateUserDto dto = + UpdateUserDto.builder() + .id(null) + .username(username) + .password("") + .authProvider(Constants.AUTHENTICATION_PROVIDER_LDAP) + .build(); + userDaoImpl.add(dto); + } + return userDaoImpl.getByName(username); + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IUserService.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IUserService.java index 60ba8d430..1b84559a6 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IUserService.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IUserService.java @@ -39,5 +39,5 @@ public interface IUserService { void disable(int id); - UserSimpleInfoRes login(UserLoginReq req); + UserSimpleInfoRes login(UserLoginReq req, String authType); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java index 95f2321f8..853e6819d 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java @@ -17,7 +17,9 @@ package org.apache.seatunnel.app.service.impl; +import org.apache.seatunnel.app.common.Constants; import org.apache.seatunnel.app.common.UserTokenStatusEnum; +import org.apache.seatunnel.app.config.SeatunnelAuthenticationProvidersConfig; import org.apache.seatunnel.app.dal.dao.IUserDao; import org.apache.seatunnel.app.dal.entity.User; import org.apache.seatunnel.app.domain.dto.user.ListUserDto; @@ -31,24 +33,31 @@ import org.apache.seatunnel.app.domain.response.user.AddUserRes; import org.apache.seatunnel.app.domain.response.user.UserSimpleInfoRes; import org.apache.seatunnel.app.security.JwtUtils; +import org.apache.seatunnel.app.security.authentication.strategy.IAuthenticationStrategy; +import org.apache.seatunnel.app.security.authentication.strategy.impl.DBAuthenticationStrategy; +import org.apache.seatunnel.app.security.authentication.strategy.impl.LDAPAuthenticationStrategy; import org.apache.seatunnel.app.service.IRoleService; import org.apache.seatunnel.app.service.IUserService; import org.apache.seatunnel.app.utils.PasswordUtils; import org.apache.seatunnel.server.common.PageData; +import org.apache.seatunnel.server.common.SeatunnelErrorEnum; import org.apache.seatunnel.server.common.SeatunnelException; +import org.apache.commons.lang3.StringUtils; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import javax.annotation.PostConstruct; import javax.annotation.Resource; +import java.util.HashMap; import java.util.List; -import java.util.Objects; +import java.util.Map; import java.util.stream.Collectors; -import static org.apache.seatunnel.server.common.SeatunnelErrorEnum.USERNAME_PASSWORD_NO_MATCHED; - @Component public class UserServiceImpl implements IUserService { @Resource private IUserDao userDaoImpl; @@ -60,6 +69,26 @@ public class UserServiceImpl implements IUserService { @Value("${user.default.passwordSalt:seatunnel}") private String defaultSalt; + private final Map strategies = new HashMap<>(); + + @Autowired private DBAuthenticationStrategy dbAuthenticationStrategy; + + @Autowired private LDAPAuthenticationStrategy ldapAuthenticationStrategy; + + @Autowired + private SeatunnelAuthenticationProvidersConfig seatunnelAuthenticationProvidersConfig; + + @PostConstruct + public void init() { + List providers = seatunnelAuthenticationProvidersConfig.getProviders(); + if (providers.isEmpty() || providers.contains(Constants.AUTHENTICATION_PROVIDER_DB)) { + strategies.put(Constants.AUTHENTICATION_PROVIDER_DB, dbAuthenticationStrategy); + } + if (providers.contains(Constants.AUTHENTICATION_PROVIDER_LDAP)) { + strategies.put(Constants.AUTHENTICATION_PROVIDER_LDAP, ldapAuthenticationStrategy); + } + } + @Override @Transactional(rollbackFor = Exception.class) public AddUserRes add(AddUserReq addReq) { @@ -75,6 +104,7 @@ public AddUserRes add(AddUserReq addReq) { .password(PasswordUtils.encryptWithSalt(defaultSalt, addReq.getPassword())) .status(addReq.getStatus()) .type(addReq.getType()) + .authProvider(Constants.AUTHENTICATION_PROVIDER_DB) .build(); final int userId = userDaoImpl.add(dto); @@ -139,16 +169,14 @@ public void disable(int id) { } @Override - public UserSimpleInfoRes login(UserLoginReq req) { - - final String username = req.getUsername(); - final String password = PasswordUtils.encryptWithSalt(defaultSalt, req.getPassword()); - - final User user = userDaoImpl.checkPassword(username, password); - if (Objects.isNull(user)) { - throw new SeatunnelException(USERNAME_PASSWORD_NO_MATCHED); + public UserSimpleInfoRes login(UserLoginReq req, String authType) { + authType = StringUtils.isEmpty(authType) ? Constants.AUTHENTICATION_PROVIDER_DB : authType; + if (!strategies.containsKey(authType)) { + throw new SeatunnelException( + SeatunnelErrorEnum.INVALID_AUTHENTICATION_PROVIDER, authType); } - + IAuthenticationStrategy strategy = strategies.get(authType); + User user = strategy.authenticate(req); UserSimpleInfoRes translate = translate(user); final String token = jwtUtils.genToken(translate.toMap()); translate.setToken(token); @@ -160,7 +188,6 @@ public UserSimpleInfoRes login(UserLoginReq req) { .userId(user.getId()) .build(); userDaoImpl.insertLoginLog(logDto); - return translate; } diff --git a/seatunnel-server/seatunnel-app/src/main/resources/application.yml b/seatunnel-server/seatunnel-app/src/main/resources/application.yml index a85295a6f..6a06d519b 100644 --- a/seatunnel-server/seatunnel-app/src/main/resources/application.yml +++ b/seatunnel-server/seatunnel-app/src/main/resources/application.yml @@ -31,13 +31,23 @@ spring: mvc: pathmatch: matching-strategy: ant_path_matcher - + ldap: + url: ldap://localhost:389 + search: + base: ou=people,dc=example,dc=com + filter: (uid={0}) + domain: example.com + authentication: + providers: + - DB + #- LDAP # LDAP authentication is disabled by default jwt: expireTime: 86400 # please add key when deploy secretKey: algorithm: HS256 + --- spring: config: diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserMapper.xml index 0539f9cc7..f4e8abcb8 100644 --- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserMapper.xml +++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserMapper.xml @@ -23,6 +23,7 @@ + id, @@ -31,13 +32,14 @@ `status`, `type`, create_time, - update_time + update_time, + auth_provider - insert into `user` (username, password, status, type) + insert into `user` (username, password, status, type, auth_provider) values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{status,jdbcType=TINYINT}, - #{type,jdbcType=TINYINT}) + #{type,jdbcType=TINYINT}, #{authProvider,jdbcType=VARCHAR}) update `user` @@ -94,7 +96,7 @@ select from `user` - where username = #{username,jdbcType=VARCHAR} and password = #{password,jdbcType=VARCHAR} + where username = #{username,jdbcType=VARCHAR} and password = #{password,jdbcType=VARCHAR} and auth_provider = #{authProvider,jdbcType=VARCHAR}