Skip to content

Commit 4dee414

Browse files
committed
feat: auth token service, oauth service, controller, test added
1 parent 7eb9aa7 commit 4dee414

33 files changed

+1731
-44
lines changed

auth/logs/auth.log

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,33 @@ Caused by: java.lang.IllegalStateException: The following classes could not be e
712712
2026-04-01T00:02:23.655+09:00 INFO 21912 --- [main] com.woobeee.auth.AuthApplicationTests : Starting AuthApplicationTests using Java 25.0.2 with PID 21912 (started by administrator in /Users/administrator/Documents/projects/art-market-place/auth)
713713
2026-04-01T00:02:23.657+09:00 INFO 21912 --- [main] com.woobeee.auth.AuthApplicationTests : No active profile set, falling back to 1 default profile: "default"
714714
2026-04-01T00:02:24.107+09:00 INFO 21912 --- [main] com.woobeee.auth.AuthApplicationTests : Started AuthApplicationTests in 0.572 seconds (process running for 1.241)
715+
2026-04-01T22:48:36.927+09:00 INFO 88456 --- [main] .UserCredentialControllerIntegrationTest : Starting UserCredentialControllerIntegrationTest using Java 25.0.2 with PID 88456 (started by administrator in /Users/administrator/Documents/projects/art-market-place/auth)
716+
2026-04-01T22:48:36.928+09:00 INFO 88456 --- [main] .UserCredentialControllerIntegrationTest : No active profile set, falling back to 1 default profile: "default"
717+
2026-04-01T22:48:37.411+09:00 INFO 88456 --- [main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
718+
2026-04-01T22:48:37.411+09:00 INFO 88456 --- [main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
719+
2026-04-01T22:48:37.412+09:00 INFO 88456 --- [main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
720+
2026-04-01T22:48:37.425+09:00 INFO 88456 --- [main] .UserCredentialControllerIntegrationTest : Started UserCredentialControllerIntegrationTest in 0.676 seconds (process running for 1.23)
721+
2026-04-01T22:48:58.807+09:00 INFO 88456 --- [main] c.w.a.c.UserCredentialControllerImpl : login request
722+
2026-04-01T22:48:58.896+09:00 INFO 88456 --- [main] c.w.a.c.UserCredentialControllerImpl : logout request
723+
2026-04-01T22:48:58.903+09:00 INFO 88456 --- [main] c.w.a.c.UserCredentialControllerImpl : sign in request
724+
2026-04-01T22:48:58.907+09:00 INFO 88456 --- [main] c.w.a.c.UserCredentialControllerImpl : refresh request
725+
2026-04-01T22:49:14.738+09:00 INFO 88727 --- [main] .UserCredentialControllerIntegrationTest : Starting UserCredentialControllerIntegrationTest using Java 25.0.2 with PID 88727 (started by administrator in /Users/administrator/Documents/projects/art-market-place/auth)
726+
2026-04-01T22:49:14.739+09:00 INFO 88727 --- [main] .UserCredentialControllerIntegrationTest : No active profile set, falling back to 1 default profile: "default"
727+
2026-04-01T22:49:15.202+09:00 INFO 88727 --- [main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
728+
2026-04-01T22:49:15.202+09:00 INFO 88727 --- [main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
729+
2026-04-01T22:49:15.203+09:00 INFO 88727 --- [main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
730+
2026-04-01T22:49:15.216+09:00 INFO 88727 --- [main] .UserCredentialControllerIntegrationTest : Started UserCredentialControllerIntegrationTest in 0.649 seconds (process running for 1.555)
731+
2026-04-01T22:49:36.613+09:00 INFO 88727 --- [main] c.w.a.c.UserCredentialControllerImpl : login request
732+
2026-04-01T22:49:36.658+09:00 INFO 88727 --- [main] c.w.a.c.UserCredentialControllerImpl : logout request
733+
2026-04-01T22:49:36.664+09:00 INFO 88727 --- [main] c.w.a.c.UserCredentialControllerImpl : sign in request
734+
2026-04-01T22:49:36.669+09:00 INFO 88727 --- [main] c.w.a.c.UserCredentialControllerImpl : refresh request
735+
2026-04-01T22:56:12.325+09:00 INFO 91028 --- [main] .UserCredentialControllerIntegrationTest : Starting UserCredentialControllerIntegrationTest using Java 25.0.2 with PID 91028 (started by administrator in /Users/administrator/Documents/projects/art-market-place/auth)
736+
2026-04-01T22:56:12.326+09:00 INFO 91028 --- [main] .UserCredentialControllerIntegrationTest : No active profile set, falling back to 1 default profile: "default"
737+
2026-04-01T22:56:12.760+09:00 INFO 91028 --- [main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
738+
2026-04-01T22:56:12.760+09:00 INFO 91028 --- [main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
739+
2026-04-01T22:56:12.761+09:00 INFO 91028 --- [main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
740+
2026-04-01T22:56:12.773+09:00 INFO 91028 --- [main] .UserCredentialControllerIntegrationTest : Started UserCredentialControllerIntegrationTest in 0.66 seconds (process running for 1.306)
741+
2026-04-01T22:56:13.027+09:00 INFO 91028 --- [main] c.w.a.c.UserCredentialControllerImpl : login request
742+
2026-04-01T22:56:13.099+09:00 INFO 91028 --- [main] c.w.a.c.UserCredentialControllerImpl : logout request
743+
2026-04-01T22:56:13.105+09:00 INFO 91028 --- [main] c.w.a.c.UserCredentialControllerImpl : sign in request
744+
2026-04-01T22:56:13.109+09:00 INFO 91028 --- [main] c.w.a.c.UserCredentialControllerImpl : refresh request

auth/pom.xml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
</dependencyManagement>
2828

2929
<dependencies>
30+
<dependency>
31+
<groupId>org.projectlombok</groupId>
32+
<artifactId>lombok</artifactId>
33+
</dependency>
3034
<dependency>
3135
<groupId>org.springframework.boot</groupId>
3236
<artifactId>spring-boot-starter-webmvc</artifactId>
@@ -36,6 +40,11 @@
3640
<artifactId>spring-boot-starter-test</artifactId>
3741
<scope>test</scope>
3842
</dependency>
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-webmvc-test</artifactId>
46+
<scope>test</scope>
47+
</dependency>
3948

4049
<dependency>
4150
<groupId>org.springframework.boot</groupId>
@@ -55,6 +64,10 @@
5564
<groupId>org.springframework.session</groupId>
5665
<artifactId>spring-session-data-redis</artifactId>
5766
</dependency>
67+
<dependency>
68+
<groupId>org.springframework.kafka</groupId>
69+
<artifactId>spring-kafka</artifactId>
70+
</dependency>
5871

5972
<!-- jwt token dependency -->
6073
<dependency>
@@ -74,6 +87,25 @@
7487
<version>0.12.3</version>
7588
<scope>runtime</scope>
7689
</dependency>
90+
91+
<!-- google -->
92+
<dependency>
93+
<groupId>com.google.api-client</groupId>
94+
<artifactId>google-api-client</artifactId>
95+
<version>2.7.2</version>
96+
</dependency>
97+
98+
99+
<!-- security -->
100+
<dependency>
101+
<groupId>org.springframework.security</groupId>
102+
<artifactId>spring-security-crypto</artifactId>
103+
</dependency>
104+
<dependency>
105+
<groupId>org.springdoc</groupId>
106+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
107+
<version>3.0.2</version>
108+
</dependency>
77109
</dependencies>
78110

79111
<build>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.woobeee.auth.config;
2+
3+
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
4+
import com.google.api.client.http.javanet.NetHttpTransport;
5+
import com.google.api.client.json.gson.GsonFactory;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
import java.util.Collections;
11+
12+
@Configuration
13+
public class GoogleOauthConfig {
14+
@Value("${oauth.google.client-id}")
15+
private String clientId;
16+
17+
@Bean
18+
public GoogleIdTokenVerifier googleIdTokenVerifier() {
19+
return new GoogleIdTokenVerifier
20+
.Builder(new NetHttpTransport(), new GsonFactory())
21+
.setAudience(Collections.singletonList(clientId))
22+
.build();
23+
}
24+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.woobeee.auth.config;
2+
3+
import org.apache.kafka.clients.consumer.ConsumerConfig;
4+
import org.apache.kafka.clients.producer.ProducerConfig;
5+
import org.apache.kafka.common.config.SaslConfigs;
6+
import org.apache.kafka.common.serialization.StringDeserializer;
7+
import org.apache.kafka.common.serialization.StringSerializer;
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.context.annotation.Bean;
10+
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
12+
import org.springframework.kafka.core.*;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
17+
@Configuration
18+
public class KafkaConfig {
19+
@Value("${spring.kafka.bootstrap-servers}")
20+
private String bootstrapServers;
21+
@Value("${spring.kafka.username}")
22+
private String username;
23+
@Value("${spring.kafka.password}")
24+
private String password;
25+
26+
@Value("${spring.cloud.config.profile}")
27+
private String profile;
28+
29+
@Bean
30+
public ConsumerFactory<String, String> consumerFactory() {
31+
Map<String, Object> config = new HashMap<>();
32+
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
33+
config.put(ConsumerConfig.GROUP_ID_CONFIG, "auth-" + profile);
34+
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
35+
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
36+
37+
config.put("security.protocol", "SASL_PLAINTEXT");
38+
config.put("sasl.mechanism", "PLAIN");
39+
config.put(SaslConfigs.SASL_JAAS_CONFIG,
40+
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
41+
username, password));
42+
return new DefaultKafkaConsumerFactory<>(config);
43+
}
44+
45+
@Bean
46+
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
47+
var factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
48+
factory.setConsumerFactory(consumerFactory());
49+
return factory;
50+
}
51+
52+
@Bean
53+
public ProducerFactory<String, String> producerFactory() {
54+
Map<String, Object> config = new HashMap<>();
55+
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
56+
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
57+
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
58+
59+
config.put("security.protocol", "SASL_PLAINTEXT");
60+
config.put("sasl.mechanism", "PLAIN");
61+
config.put(SaslConfigs.SASL_JAAS_CONFIG,
62+
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
63+
username, password));
64+
return new DefaultKafkaProducerFactory<>(config);
65+
}
66+
67+
@Bean
68+
public KafkaTemplate<String, String> kafkaTemplate() {
69+
return new KafkaTemplate<>(producerFactory());
70+
}
71+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.woobeee.auth.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.data.redis.connection.RedisConnectionFactory;
6+
import org.springframework.data.redis.core.StringRedisTemplate;
7+
import org.springframework.data.redis.serializer.StringRedisSerializer;
8+
9+
@Configuration
10+
public class RedisConfig {
11+
12+
@Bean
13+
public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
14+
StringRedisTemplate template = new StringRedisTemplate();
15+
template.setConnectionFactory(connectionFactory);
16+
17+
template.setKeySerializer(new StringRedisSerializer());
18+
template.setValueSerializer(new StringRedisSerializer());
19+
template.setHashKeySerializer(new StringRedisSerializer());
20+
template.setHashValueSerializer(new StringRedisSerializer());
21+
22+
return template;
23+
}
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.woobeee.auth.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
6+
import org.springframework.security.crypto.password.PasswordEncoder;
7+
8+
@Configuration
9+
public class SecurityConfig {
10+
11+
12+
/**
13+
* Encoding Method Bean.
14+
*/
15+
@Bean
16+
public PasswordEncoder passwordEncoder() {
17+
return new BCryptPasswordEncoder();
18+
}
19+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.woobeee.auth.controller;
2+
3+
import com.woobeee.auth.dto.request.OauthTokenRequest;
4+
import com.woobeee.auth.dto.response.ApiResponse;
5+
import com.woobeee.auth.dto.response.AuthTokenResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import jakarta.servlet.http.HttpServletResponse;
9+
import org.springframework.web.bind.annotation.*;
10+
11+
@RequestMapping("/api/auth")
12+
@Tag(name = "User Credential Controller", description = "유저 인증 컨트롤러")
13+
public interface UserCredentialController {
14+
@Operation(
15+
summary = "로그인 API"
16+
)
17+
@PostMapping("/login")
18+
ApiResponse<AuthTokenResponse> login(
19+
@RequestBody OauthTokenRequest request,
20+
HttpServletResponse response
21+
);
22+
23+
@Operation(
24+
summary = "로그아웃 API"
25+
)
26+
@PostMapping("/signIn")
27+
ApiResponse<AuthTokenResponse> signIn(
28+
@RequestBody OauthTokenRequest request,
29+
HttpServletResponse response
30+
);
31+
32+
@Operation(
33+
summary = "토큰 재발급 API"
34+
)
35+
@PostMapping("/refresh")
36+
ApiResponse<AuthTokenResponse> refresh(
37+
@CookieValue(value = "refreshToken", required = false) String refreshToken,
38+
HttpServletResponse response
39+
);
40+
41+
@Operation(
42+
summary = "로그아웃 API"
43+
)
44+
@GetMapping("/logout")
45+
ApiResponse<Void> logout(
46+
@CookieValue(value = "refreshToken", required = false) String refreshToken,
47+
HttpServletResponse response
48+
);
49+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.woobeee.auth.controller;
2+
3+
import com.woobeee.auth.aop.Idempotent;
4+
import com.woobeee.auth.dto.request.OauthTokenRequest;
5+
import com.woobeee.auth.dto.response.ApiResponse;
6+
import com.woobeee.auth.dto.response.AuthTokenResponse;
7+
import com.woobeee.auth.dto.response.IssuedAuthTokens;
8+
import com.woobeee.auth.service.OauthUserCredentialService;
9+
import com.woobeee.auth.token.RefreshTokenCookieProvider;
10+
import jakarta.servlet.http.HttpServletResponse;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.web.bind.annotation.CookieValue;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@RequiredArgsConstructor
17+
@Slf4j
18+
@RestController
19+
public class UserCredentialControllerImpl implements UserCredentialController{
20+
private final OauthUserCredentialService oauthUserCredentialService;
21+
private final RefreshTokenCookieProvider refreshTokenCookieProvider;
22+
23+
/// GET /api/auth/login
24+
@Override
25+
public ApiResponse<AuthTokenResponse> login(OauthTokenRequest request, HttpServletResponse response) {
26+
log.info("login request");
27+
IssuedAuthTokens issuedAuthTokens = oauthUserCredentialService.logIn(request.idToken());
28+
refreshTokenCookieProvider.addRefreshTokenCookie(response, issuedAuthTokens.refreshToken());
29+
return ApiResponse.success(
30+
issuedAuthTokens.response(),
31+
"login request success"
32+
);
33+
}
34+
35+
/// POST /api/auth/signIn
36+
@Override
37+
@Idempotent
38+
public ApiResponse<AuthTokenResponse> signIn(OauthTokenRequest request, HttpServletResponse response) {
39+
log.info("sign in request");
40+
IssuedAuthTokens issuedAuthTokens = oauthUserCredentialService.signIn(request.idToken());
41+
refreshTokenCookieProvider.addRefreshTokenCookie(response, issuedAuthTokens.refreshToken());
42+
return ApiResponse.success(
43+
issuedAuthTokens.response(),
44+
"sign in success");
45+
}
46+
47+
@Override
48+
public ApiResponse<AuthTokenResponse> refresh(
49+
@CookieValue(value = "refreshToken", required = false) String refreshToken,
50+
HttpServletResponse response
51+
) {
52+
log.info("refresh request");
53+
IssuedAuthTokens issuedAuthTokens = oauthUserCredentialService.refresh(refreshToken);
54+
refreshTokenCookieProvider.addRefreshTokenCookie(response, issuedAuthTokens.refreshToken());
55+
return ApiResponse.success(
56+
issuedAuthTokens.response(),
57+
"refresh success"
58+
);
59+
}
60+
61+
/// GET /api/auth/logout
62+
@Override
63+
public ApiResponse<Void> logout(
64+
@CookieValue(value = "refreshToken", required = false) String refreshToken,
65+
HttpServletResponse response
66+
) {
67+
log.info("logout request");
68+
oauthUserCredentialService.logout(refreshToken);
69+
refreshTokenCookieProvider.clearRefreshTokenCookie(response);
70+
return ApiResponse.success("logout success");
71+
}
72+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.woobeee.auth.dto.provider;
2+
3+
import lombok.Builder;
4+
5+
import java.util.UUID;
6+
7+
@Builder
8+
public record MessageEvent(
9+
UUID eventId,
10+
String topic,
11+
String key,
12+
Object message
13+
) {
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.woobeee.auth.dto.request;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record OauthTokenRequest (String idToken){
7+
}

0 commit comments

Comments
 (0)