From 00219c6d7e1a8c3968e7353cf91135569d658bb4 Mon Sep 17 00:00:00 2001 From: JooHyunLee Date: Tue, 16 Apr 2024 17:52:35 +0900 Subject: [PATCH] =?UTF-8?q?feature:=20#14=20SecureKeyManager=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/component/JWTUtils.java | 6 +- .../account/config/RedisConfig.java | 55 ------------ .../service/DefaultUserDetailsService.java | 1 + .../config/DataSourceConfig.java | 21 +++++ .../config/DatabasePropertiesConfig.java | 33 +++++++ .../authenticationapi/config/RedisConfig.java | 62 +++++++++++++ .../{account => }/config/SecurityConfig.java | 5 +- .../property/DatabaseProperties.java | 17 ++++ .../property/RedisProperties.java | 17 ++++ src/main/resources/application.yml | 14 --- src/main/resources/application_prod.yml | 4 + .../DefaultUserDetailsServiceTest.java | 16 +++- .../filter/CustomLogoutFilterTest.java | 4 - .../filter/LoginFilterTest.java | 86 ------------------- src/test/resources/application.yml | 14 +-- 15 files changed, 183 insertions(+), 172 deletions(-) delete mode 100644 src/main/java/com/t3t/authenticationapi/account/config/RedisConfig.java create mode 100644 src/main/java/com/t3t/authenticationapi/config/DataSourceConfig.java create mode 100644 src/main/java/com/t3t/authenticationapi/config/DatabasePropertiesConfig.java create mode 100644 src/main/java/com/t3t/authenticationapi/config/RedisConfig.java rename src/main/java/com/t3t/authenticationapi/{account => }/config/SecurityConfig.java (96%) create mode 100644 src/main/java/com/t3t/authenticationapi/property/DatabaseProperties.java create mode 100644 src/main/java/com/t3t/authenticationapi/property/RedisProperties.java create mode 100644 src/main/resources/application_prod.yml delete mode 100644 src/test/java/com/t3t/authenticationapi/filter/CustomLogoutFilterTest.java delete mode 100644 src/test/java/com/t3t/authenticationapi/filter/LoginFilterTest.java diff --git a/src/main/java/com/t3t/authenticationapi/account/component/JWTUtils.java b/src/main/java/com/t3t/authenticationapi/account/component/JWTUtils.java index 01d393c..e3796ab 100644 --- a/src/main/java/com/t3t/authenticationapi/account/component/JWTUtils.java +++ b/src/main/java/com/t3t/authenticationapi/account/component/JWTUtils.java @@ -1,11 +1,12 @@ package com.t3t.authenticationapi.account.component; +import com.t3t.authenticationapi.keymanager.properties.SecretKeyProperties; +import com.t3t.authenticationapi.keymanager.service.SecretKeyManagerService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.security.Key; @@ -21,7 +22,8 @@ @Component public class JWTUtils { private Key key; - public JWTUtils(@Value("${spring.security.key}") String secret) { + public JWTUtils(SecretKeyManagerService secretKeyManagerService, SecretKeyProperties secretKeyProperties) { + String secret = secretKeyManagerService.getSecretValue(secretKeyProperties.getJwtSecretKeyId()); byte[] byteSecretKEy = Decoders.BASE64.decode(secret); key = Keys.hmacShaKeyFor(byteSecretKEy); } diff --git a/src/main/java/com/t3t/authenticationapi/account/config/RedisConfig.java b/src/main/java/com/t3t/authenticationapi/account/config/RedisConfig.java deleted file mode 100644 index ebf03a1..0000000 --- a/src/main/java/com/t3t/authenticationapi/account/config/RedisConfig.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.t3t.authenticationapi.account.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -/** - * redis 연결을 위한 configuration 클래스 - * @author joohyun1996 (이주현) - */ -@Configuration -@EnableRedisRepositories -public class RedisConfig { - @Value("${spring.redis.host}") - private String host; - - @Value("${spring.redis.port}") - private int port; - - @Value("${spring.redis.database}") - private int database; - - @Value("${spring.redis.password}") - private String password; - - /** - * RedisServer에 연결을 생성하는데 사용되는 클래스 - * getConnection() 호출될 때 마다 새로운 LettuceConnection 생성 - * Thread-safe 하다 - * 동기, 비동기, 리액티브 api 모두 가능 - * @author joohyun1996 (이주현) - */ - @Bean - public RedisConnectionFactory redisConnectionFactory(){ - RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port); - configuration.setPassword(password); - configuration.setDatabase(database); - return new LettuceConnectionFactory(configuration); - } - - @Bean - public RedisTemplate redisTemplate(){ - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); - redisTemplate.setConnectionFactory(redisConnectionFactory()); - return redisTemplate; - } -} diff --git a/src/main/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsService.java b/src/main/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsService.java index 1cf85b5..5d268c2 100644 --- a/src/main/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsService.java +++ b/src/main/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsService.java @@ -40,6 +40,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx userEntity.setUsername(userEntityDto.getUsername()); userEntity.setUserId(userEntityDto.getUserId()); userEntity.setPassword(userEntityDto.getPassword()); +// userEntity.setPassword(bCryptPasswordEncoder.encode(userEntityDto.getPassword())); userEntity.setRole(userEntityDto.getRole()); return new CustomUserDetails(userEntity); diff --git a/src/main/java/com/t3t/authenticationapi/config/DataSourceConfig.java b/src/main/java/com/t3t/authenticationapi/config/DataSourceConfig.java new file mode 100644 index 0000000..65c1395 --- /dev/null +++ b/src/main/java/com/t3t/authenticationapi/config/DataSourceConfig.java @@ -0,0 +1,21 @@ +package com.t3t.authenticationapi.config; + +import com.t3t.authenticationapi.property.DatabaseProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +@Configuration +public class DataSourceConfig { + @Bean + public DataSource dataSource(DatabaseProperties databaseProperties){ + return DataSourceBuilder.create() + .url(databaseProperties.getDatabaseUrl()) + .driverClassName(databaseProperties.getDriverClassName()) + .username(databaseProperties.getUsername()) + .password(databaseProperties.getPassword()) + .build(); + } +} diff --git a/src/main/java/com/t3t/authenticationapi/config/DatabasePropertiesConfig.java b/src/main/java/com/t3t/authenticationapi/config/DatabasePropertiesConfig.java new file mode 100644 index 0000000..a7a53b9 --- /dev/null +++ b/src/main/java/com/t3t/authenticationapi/config/DatabasePropertiesConfig.java @@ -0,0 +1,33 @@ +package com.t3t.authenticationapi.config; + +import com.t3t.authenticationapi.keymanager.properties.SecretKeyProperties; +import com.t3t.authenticationapi.keymanager.service.SecretKeyManagerService; +import com.t3t.authenticationapi.property.DatabaseProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; + +@Configuration +public class DatabasePropertiesConfig { + @Bean + @Profile({"prod", "dev", "test"}) + public DatabaseProperties dataSourceProperties(SecretKeyManagerService secretKeyManagerService, + SecretKeyProperties secretKeyProperties, + Environment environment) { + + String activeProfile = environment.getActiveProfiles()[0]; + String activeProfileSuffix = activeProfile.equals("prod") ? "" : "_" + activeProfile; + + return DatabaseProperties.builder() + .databaseUrl(String.format("jdbc:mysql://%s:%s/%s%s", + secretKeyManagerService.getSecretValue(secretKeyProperties.getDatabaseIpAddressKeyId()), + secretKeyManagerService.getSecretValue(secretKeyProperties.getDatabasePortKeyId()), + secretKeyManagerService.getSecretValue(secretKeyProperties.getDatabaseNameKeyId()), + activeProfileSuffix)) + .driverClassName("com.mysql.cj.jdbc.Driver") + .username(secretKeyManagerService.getSecretValue(secretKeyProperties.getDatabaseUsernameKeyId())) + .password(secretKeyManagerService.getSecretValue(secretKeyProperties.getDatabasePasswordKeyId())) + .build(); + } +} diff --git a/src/main/java/com/t3t/authenticationapi/config/RedisConfig.java b/src/main/java/com/t3t/authenticationapi/config/RedisConfig.java new file mode 100644 index 0000000..cfd2849 --- /dev/null +++ b/src/main/java/com/t3t/authenticationapi/config/RedisConfig.java @@ -0,0 +1,62 @@ +package com.t3t.authenticationapi.config; + +import com.t3t.authenticationapi.keymanager.properties.SecretKeyProperties; +import com.t3t.authenticationapi.keymanager.service.SecretKeyManagerService; +import com.t3t.authenticationapi.property.RedisProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis 연결을 위한 configuration 클래스 + * @author joohyun1996 (이주현) + */ +@Configuration +@EnableRedisRepositories +public class RedisConfig { + @Bean + public RedisProperties redisProperties(SecretKeyManagerService secretKeyManagerService, + SecretKeyProperties secretKeyProperties, + Environment environment){ + + String activeProfile = environment.getActiveProfiles()[0]; + String activeProfileSuffix = activeProfile.equals("prod") ? "" : "_" + activeProfile; + + return RedisProperties.builder() + .host(secretKeyManagerService.getSecretValue(secretKeyProperties.getRedisIpAddressKeyId())) + .port(Integer.valueOf(secretKeyManagerService.getSecretValue(secretKeyProperties.getRedisPortKeyId()))) + .password(secretKeyManagerService.getSecretValue(secretKeyProperties.getRedisPasswordKeyId())) + .database(20) + .build(); + } + + /** + * RedisServer에 연결을 생성하는데 사용되는 클래스 + * getConnection() 호출될 때 마다 새로운 LettuceConnection 생성 + * Thread-safe 하다 + * 동기, 비동기, 리액티브 api 모두 가능 + * @author joohyun1996 (이주현) + */ + @Bean + public RedisConnectionFactory redisConnectionFactory(RedisProperties redisProperties){ + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()); + configuration.setPassword(redisProperties.getPassword()); + configuration.setDatabase(redisProperties.getDatabase()); + return new LettuceConnectionFactory(configuration); + } + + @Bean + public RedisTemplate redisTemplate(RedisProperties redisProperties){ + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory(redisProperties)); + return redisTemplate; + } +} diff --git a/src/main/java/com/t3t/authenticationapi/account/config/SecurityConfig.java b/src/main/java/com/t3t/authenticationapi/config/SecurityConfig.java similarity index 96% rename from src/main/java/com/t3t/authenticationapi/account/config/SecurityConfig.java rename to src/main/java/com/t3t/authenticationapi/config/SecurityConfig.java index 56f7d25..44bcfd4 100644 --- a/src/main/java/com/t3t/authenticationapi/account/config/SecurityConfig.java +++ b/src/main/java/com/t3t/authenticationapi/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.t3t.authenticationapi.account.config; +package com.t3t.authenticationapi.config; import com.t3t.authenticationapi.account.component.JWTUtils; import com.t3t.authenticationapi.account.filter.CommonExceptionFilter; @@ -41,7 +41,7 @@ public BCryptPasswordEncoder bCryptPasswordEncoder(){ /** * Security Filter Chain 설정. * Auth-Server에서는 인증만 담당하기 때문에 다른 URL에 대해서는 설정 X - * @param HttpSecurity + * @param http * @author joohyun1996 (이주현) */ @Bean @@ -52,7 +52,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .httpBasic().disable() .authorizeRequests((auth) -> auth .antMatchers("/login").permitAll() - .antMatchers("/logins").permitAll() .antMatchers("/refresh").permitAll() .antMatchers("/logout").authenticated() .anyRequest().authenticated()) diff --git a/src/main/java/com/t3t/authenticationapi/property/DatabaseProperties.java b/src/main/java/com/t3t/authenticationapi/property/DatabaseProperties.java new file mode 100644 index 0000000..8a1c488 --- /dev/null +++ b/src/main/java/com/t3t/authenticationapi/property/DatabaseProperties.java @@ -0,0 +1,17 @@ +package com.t3t.authenticationapi.property; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabaseProperties { + private String databaseUrl; + private String driverClassName; + private String username; + private String password; +} diff --git a/src/main/java/com/t3t/authenticationapi/property/RedisProperties.java b/src/main/java/com/t3t/authenticationapi/property/RedisProperties.java new file mode 100644 index 0000000..4b51e92 --- /dev/null +++ b/src/main/java/com/t3t/authenticationapi/property/RedisProperties.java @@ -0,0 +1,17 @@ +package com.t3t.authenticationapi.property; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RedisProperties { + private String host; + private Integer port; + private Integer database; + private String password; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5beb8db..dfdfa3b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,17 +1,4 @@ spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://133.186.223.228:3306/t3team?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8 - username: t3team - password: uPJQz6QaL6@6h]BG - security: - key: sakdjA24HSdflasbdglag2yhsdrg342TASGASd58aw4t3AWEIGzsoigbaWEIGHP3tug0ajw4s23a8th24tgaw2854yq3p48ghaa294 - redis: - host: 133.186.223.228 - password: "*N2vya7H@muDTwdNMR!" - port: 6379 - database: 20 - jpa: open-in-view: true hibernate: @@ -24,7 +11,6 @@ spring: hibernate.format_sql: true dialect: org.hibernate.dialect.MySQL8InnoDBDialect - logging: level: org.hibernate.SQL: debug diff --git a/src/main/resources/application_prod.yml b/src/main/resources/application_prod.yml new file mode 100644 index 0000000..887fa0b --- /dev/null +++ b/src/main/resources/application_prod.yml @@ -0,0 +1,4 @@ +eureka: + client: + service-url: + defaultZone: ${eurekaServiceUrlDefaultZone} \ No newline at end of file diff --git a/src/test/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsServiceTest.java b/src/test/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsServiceTest.java index 00b27ae..a6133b4 100644 --- a/src/test/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsServiceTest.java +++ b/src/test/java/com/t3t/authenticationapi/account/service/DefaultUserDetailsServiceTest.java @@ -13,7 +13,19 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;*/ -/* +import com.t3t.authenticationapi.account.auth.CustomUserDetails; +import com.t3t.authenticationapi.account.dto.UserEntityDto; +import com.t3t.authenticationapi.account.repository.AccountRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + @ExtendWith(MockitoExtension.class) class DefaultUserDetailsServiceTest { @Mock @@ -58,4 +70,4 @@ public void TestLoadUserByUserNameFailed(){ Assertions.assertEquals("User Not Found", exception.getMessage()); } -}*/ +} diff --git a/src/test/java/com/t3t/authenticationapi/filter/CustomLogoutFilterTest.java b/src/test/java/com/t3t/authenticationapi/filter/CustomLogoutFilterTest.java deleted file mode 100644 index a3948cf..0000000 --- a/src/test/java/com/t3t/authenticationapi/filter/CustomLogoutFilterTest.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.t3t.authenticationapi.filter; - -public class CustomLogoutFilterTest { -} diff --git a/src/test/java/com/t3t/authenticationapi/filter/LoginFilterTest.java b/src/test/java/com/t3t/authenticationapi/filter/LoginFilterTest.java deleted file mode 100644 index 584af71..0000000 --- a/src/test/java/com/t3t/authenticationapi/filter/LoginFilterTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.t3t.authenticationapi.filter; - -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) -@AutoConfigureMockMvc -@Transactional -public class LoginFilterTest { - /*@Autowired - private MockMvc mockMvc; - @MockBean - private AuthenticationManager authenticationManager; - @MockBean - private JWTUtils jwtUtils; - @MockBean - private TokenService tokenService; - @MockBean - private DefaultUserDetailsService userDetailsService; - - @Test - public void TestLoginSuccess() throws Exception { - Map testLogin = new LinkedHashMap<>(); - testLogin.put("username","user"); - testLogin.put("password","1234"); - - ObjectMapper objectMapper = new ObjectMapper(); - String messageBody = objectMapper.writeValueAsString(testLogin); - LoginDto loginDto = objectMapper.readValue(messageBody, LoginDto.class); - - - UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("user", "1234", null); - Mockito.when(authenticationManager.authenticate(any())).thenReturn(auth); - - UserEntity userEntity = new UserEntity(); - userEntity.setUserId("1"); - userEntity.setUsername("user"); - userEntity.setPassword("1234"); - userEntity.setRole("ROLE_USER"); - - Mockito.when(userDetailsService.loadUserByUsername(any())).thenReturn(new CustomUserDetails(userEntity)); - - CustomUserDetails customUserDetails = (CustomUserDetails) userDetailsService.loadUserByUsername("user"); - String userId = customUserDetails.getUserId(); - - String uuid = UUID.randomUUID().toString(); - String access = jwtUtils.createJwt("access", userId, "ROLE_USER", uuid, 900000l); - String refresh = jwtUtils.createJwt("refresh", userId, "ROLE_USER", uuid,604800000l); - - Mockito.when(jwtUtils.createJwt(any(), any(), any(), any(), any())).thenReturn(access, refresh); - - MvcResult mvcResult = mockMvc.perform(post("/login") - .contentType(MediaType.APPLICATION_JSON) - .content(messageBody)) - .andExpect(status().isOk()) - .andReturn(); - - Cookie accessCookie = mvcResult.getResponse().getCookie("access"); - Assertions.assertNotNull(accessCookie); - Assertions.assertEquals("Bearer+" + access, accessCookie.getValue()); - - Cookie refreshCookie = mvcResult.getResponse().getCookie("refresh"); - Assertions.assertNotNull(refreshCookie); - Assertions.assertEquals(refresh, refreshCookie.getValue()); - } - - @Test - public void TestAttemptAuthenticationFailed() throws Exception{ - Map testLogin = new LinkedHashMap<>(); - testLogin.put("user","user"); - testLogin.put("pw","1234"); - - ObjectMapper objectMapper = new ObjectMapper(); - String messageBody = objectMapper.writeValueAsString(testLogin); - - mockMvc.perform(post("/login") - .contentType(MediaType.APPLICATION_JSON) - .content(messageBody)) - .andExpect(result -> - Assertions.assertThrows(JsonFieldNotMatchException.class, () -> - authenticationManager.authenticate(any()) - )); - }*/ -} - diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index ff6706f..0885b83 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -13,14 +13,14 @@ spring: format_sql: true show_sql: true redis: - host: 133.186.223.228 - password: "*N2vya7H@muDTwdNMR!" - port: 6379 - database: 29 - security: - key: sakdjA24HSdflasbdglag2yhsdrg342TASGASd58aw4t3AWEIGzsoigbaWEIGHP3tug0ajw4s23a8th24tgaw2854yq3p48ghaa294 + host: ${redisHost} + password: ${redisPassword} + port: ${redisPort} + database: ${redisDatabase} application: name: eureka-client + profiles: + active: test eureka: instance: @@ -58,3 +58,5 @@ t3t: keyId: "0582f8b117604b7d86e9f3ff26931cde" redisServerPassword: keyId: "ec1eb8e0706e402cbec8487cbcb86564" + token: + key: ${jwtSecretKey}