This is an automated email from the ASF dual-hosted git repository. jimin pushed a commit to branch 2.x in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push: new 50d7003694 feature: enforce account initialization and disable default credentials (#7261) 50d7003694 is described below commit 50d7003694d79dc61dc1f15945cbd153596f47e3 Author: Yongjun Hong <dev.yongj...@gmail.com> AuthorDate: Sun May 11 23:37:00 2025 +0900 feature: enforce account initialization and disable default credentials (#7261) --- changes/en-us/2.x.md | 2 +- changes/zh-cn/2.x.md | 2 +- .../security/CustomUserDetailsServiceImpl.java | 31 ++++++-- .../org/apache/seata/console/security/User.java | 4 + namingserver/src/main/resources/application.yml | 4 - .../AuthControllerWithCustomPropertiesTest.java | 78 ++++++++++++++++++ .../AuthControllerWithRandomPasswordTest.java | 92 ++++++++++++++++++++++ .../NamingControllerLoggerPrintSmokeTest.java | 41 ++++++++++ .../smoke/NamingControllerPropertiesSmokeTest.java | 41 ++++++++++ namingserver/src/test/resources/application.yml | 4 - 10 files changed, 282 insertions(+), 17 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 45822fec2f..3f35d9ff2a 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -20,7 +20,7 @@ Add changes here for all PR submitted to the 2.x branch. ### feature: -- [[#PR_NO](https://github.com/seata/seata/pull/PR_NO)] support XXX +- [[#7261](https://github.com/apache/incubator-seata/pull/7261)] enforce account initialization and disable default credentials ### bugfix: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index c796d2f816..2df96f51a2 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -20,7 +20,7 @@ ### feature: -- [[#PR_NO](https://github.com/seata/seata/pull/PR_NO)] support XXX +- [[#7261](https://github.com/apache/incubator-seata/pull/7261)] 强制进行账户初始化并禁用默认凭据 ### bugfix: diff --git a/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java b/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java index a001b4e116..d58798ce19 100644 --- a/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java +++ b/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java @@ -16,8 +16,10 @@ */ package org.apache.seata.console.security; +import java.util.UUID; import javax.annotation.PostConstruct; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -32,10 +34,12 @@ import org.springframework.stereotype.Service; @Service public class CustomUserDetailsServiceImpl implements UserDetailsService { - @Value("${console.user.username}") + private static final Logger LOGGER = LoggerFactory.getLogger(CustomUserDetailsServiceImpl.class); + + @Value("${console.user.username:seata}") private String username; - @Value("${console.user.password}") + @Value("${console.user.password:}") private String password; private User user; @@ -45,10 +49,23 @@ public class CustomUserDetailsServiceImpl implements UserDetailsService { */ @PostConstruct public void init() { - // TODO: get userInfo by db - user = new User(); - user.setUsername(username); - user.setPassword(new BCryptPasswordEncoder().encode(password)); + if (!password.isEmpty()) { + user = new User(username, new BCryptPasswordEncoder().encode(password)); + return; + } + + password = generateRandomPassword(); + LOGGER.info( + "No password was configured. A random password has been generated for security purposes. You may either:\n" + + "1. Use the auto-generated password: [{}]\n" + + "2. Set a custom password in the configuration.", + password); + + user = new User(username, new BCryptPasswordEncoder().encode(password)); + } + + private String generateRandomPassword() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 8); } @Override diff --git a/console/src/main/java/org/apache/seata/console/security/User.java b/console/src/main/java/org/apache/seata/console/security/User.java index 44067dc51e..797430628b 100644 --- a/console/src/main/java/org/apache/seata/console/security/User.java +++ b/console/src/main/java/org/apache/seata/console/security/User.java @@ -30,6 +30,10 @@ public class User { */ String password; + public User(String username, String password) { + this.username = username; + this.password = password; + } //region Getter && Setter diff --git a/namingserver/src/main/resources/application.yml b/namingserver/src/main/resources/application.yml index 4764227f0e..0ea1c8b5d2 100644 --- a/namingserver/src/main/resources/application.yml +++ b/namingserver/src/main/resources/application.yml @@ -25,10 +25,6 @@ logging: config: classpath:logback-spring.xml file: path: ${log.home:${user.home}/logs/seata} -console: - user: - username: seata - password: seata heartbeat: threshold: 90000 period: 60000 diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/AuthControllerWithCustomPropertiesTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/AuthControllerWithCustomPropertiesTest.java new file mode 100644 index 0000000000..2b0b79a6c3 --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/AuthControllerWithCustomPropertiesTest.java @@ -0,0 +1,78 @@ +/* + * 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.seata.namingserver; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.seata.common.result.Code; +import org.apache.seata.console.config.WebSecurityConfig; +import org.apache.seata.console.security.User; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +@SpringBootTest +@TestPropertySource(properties = {"console.user.username=seata", "console.user.password=foo"}) +@AutoConfigureMockMvc +public class AuthControllerWithCustomPropertiesTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void loginSuccess_shouldReturnTokenAndAddToHeader() throws Exception { + User user = new User("seata", "foo"); + String userJson = objectMapper.writeValueAsString(user); + + MvcResult result = mockMvc.perform(post("/api/v1/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").isNotEmpty()) + .andExpect(header().exists(WebSecurityConfig.AUTHORIZATION_HEADER)) + .andReturn(); + + String authHeader = result.getResponse().getHeader(WebSecurityConfig.AUTHORIZATION_HEADER); + assertNotNull(authHeader); + assert (authHeader.startsWith(WebSecurityConfig.TOKEN_PREFIX)); + } + + @Test + public void loginFailure_shouldReturnErrorCode() throws Exception { + User user = new User("wrong_user", "wrong_password"); + String userJson = objectMapper.writeValueAsString(user); + + mockMvc.perform(post("/api/v1/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(Code.LOGIN_FAILED.getCode())); + } +} diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/AuthControllerWithRandomPasswordTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/AuthControllerWithRandomPasswordTest.java new file mode 100644 index 0000000000..af886b0baa --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/AuthControllerWithRandomPasswordTest.java @@ -0,0 +1,92 @@ +/* + * 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.seata.namingserver; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.seata.common.result.Code; +import org.apache.seata.console.config.WebSecurityConfig; +import org.apache.seata.console.security.User; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +@SpringBootTest +@ExtendWith(OutputCaptureExtension.class) +@AutoConfigureMockMvc +public class AuthControllerWithRandomPasswordTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void loginSuccess_shouldReturnTokenAndAddToHeader(CapturedOutput output) throws Exception { + String logs = output.getOut(); + + Pattern pattern = Pattern.compile("Use the auto-generated password: \\[(.+?)\\]"); + Matcher matcher = pattern.matcher(logs); + + assertTrue(matcher.find(), "captured password not found in logs"); + + String extractedPassword = matcher.group(1); + User user = new User("seata", extractedPassword); + + String userJson = objectMapper.writeValueAsString(user); + + MvcResult result = mockMvc.perform(post("/api/v1/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").isNotEmpty()) + .andExpect(header().exists(WebSecurityConfig.AUTHORIZATION_HEADER)) + .andReturn(); + + String authHeader = result.getResponse().getHeader(WebSecurityConfig.AUTHORIZATION_HEADER); + assertNotNull(authHeader); + assert (authHeader.startsWith(WebSecurityConfig.TOKEN_PREFIX)); + } + + @Test + public void loginFailure_shouldReturnErrorCode() throws Exception { + User user = new User("wrong_user", "wrong_password"); + String userJson = objectMapper.writeValueAsString(user); + + mockMvc.perform(post("/api/v1/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(Code.LOGIN_FAILED.getCode())); + } +} diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/smoke/NamingControllerLoggerPrintSmokeTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/smoke/NamingControllerLoggerPrintSmokeTest.java new file mode 100644 index 0000000000..9fe83142cb --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/smoke/NamingControllerLoggerPrintSmokeTest.java @@ -0,0 +1,41 @@ +/* + * 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.seata.namingserver.smoke; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.seata.namingserver.NamingserverApplication; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +@SpringBootTest( + classes = NamingserverApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "console.user.password=") +@ExtendWith(OutputCaptureExtension.class) +class NamingControllerLoggerPrintSmokeTest { + + @Test + void processShouldPrintLogAndGeneratePasswordWhenDefaultPasswordIsNotDefined(CapturedOutput output) { + String logs = output.getOut(); + assertTrue(logs.contains("No password was configured.")); + } +} diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/smoke/NamingControllerPropertiesSmokeTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/smoke/NamingControllerPropertiesSmokeTest.java new file mode 100644 index 0000000000..f2641ae7ea --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/smoke/NamingControllerPropertiesSmokeTest.java @@ -0,0 +1,41 @@ +/* + * 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.seata.namingserver.smoke; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.apache.seata.namingserver.NamingserverApplication; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +@SpringBootTest( + classes = NamingserverApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"console.user.username=seata", "console.user.password=foo"}) +@ExtendWith(OutputCaptureExtension.class) +class NamingControllerPropertiesSmokeTest { + + @Test + void processShouldNotPrintLogsAndGeneratePasswordWhenPasswordIsDefined(CapturedOutput output) { + String logs = output.getOut(); + assertFalse(logs.contains("No password was configured.")); + } +} diff --git a/namingserver/src/test/resources/application.yml b/namingserver/src/test/resources/application.yml index 573dde0b26..8ffcfe613b 100644 --- a/namingserver/src/test/resources/application.yml +++ b/namingserver/src/test/resources/application.yml @@ -25,10 +25,6 @@ logging: config: classpath:logback-spring.xml file: path: ${log.home:${user.home}/logs/seata} -console: - user: - username: seata - password: seata heartbeat: threshold: 5000 period: 5000 --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@seata.apache.org For additional commands, e-mail: notifications-h...@seata.apache.org