-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Feat: 카카오 소셜 로그인 구현
- Loading branch information
Showing
23 changed files
with
602 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,56 @@ | ||
plugins { | ||
id 'java' | ||
id 'org.springframework.boot' version '3.3.5' | ||
id 'io.spring.dependency-management' version '1.1.6' | ||
id 'java' | ||
id 'org.springframework.boot' version '3.3.5' | ||
id 'io.spring.dependency-management' version '1.1.6' | ||
} | ||
|
||
group = 'com.hackathon' | ||
version = '0.0.1-SNAPSHOT' | ||
|
||
java { | ||
toolchain { | ||
languageVersion = JavaLanguageVersion.of(17) | ||
} | ||
toolchain { | ||
languageVersion = JavaLanguageVersion.of(17) | ||
} | ||
} | ||
|
||
configurations { | ||
compileOnly { | ||
extendsFrom annotationProcessor | ||
} | ||
compileOnly { | ||
extendsFrom annotationProcessor | ||
} | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' | ||
implementation 'org.springframework.boot:spring-boot-starter-security' | ||
implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
implementation 'org.springframework.boot:spring-boot-starter-web' | ||
compileOnly 'org.projectlombok:lombok' | ||
developmentOnly 'org.springframework.boot:spring-boot-devtools' | ||
runtimeOnly 'com.mysql:mysql-connector-j' | ||
annotationProcessor 'org.projectlombok:lombok' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'org.springframework.security:spring-security-test' | ||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' | ||
implementation 'org.springframework.boot:spring-boot-starter-security' | ||
implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
implementation 'org.springframework.boot:spring-boot-starter-web' | ||
compileOnly 'org.projectlombok:lombok' | ||
developmentOnly 'org.springframework.boot:spring-boot-devtools' | ||
runtimeOnly 'com.mysql:mysql-connector-j' | ||
annotationProcessor 'org.projectlombok:lombok' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'org.springframework.security:spring-security-test' | ||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
|
||
// JWT | ||
implementation 'io.jsonwebtoken:jjwt-api:0.11.5' | ||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' | ||
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' | ||
|
||
// Redis | ||
implementation 'org.springframework.boot:spring-boot-starter-data-redis' | ||
|
||
// Swagger | ||
implementation 'org.springdoc:springdoc-openapi-starter-common:2.1.0' | ||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' | ||
|
||
} | ||
|
||
tasks.named('test') { | ||
useJUnitPlatform() | ||
useJUnitPlatform() | ||
} |
39 changes: 39 additions & 0 deletions
39
src/main/java/com/hackathon/momento/auth/api/AuthController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.hackathon.momento.auth.api; | ||
|
||
import com.hackathon.momento.auth.api.dto.AuthResDto; | ||
import com.hackathon.momento.global.oauth.KakaoOauthService; | ||
import com.hackathon.momento.global.template.RspTemplate; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.AccessLevel; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Tag(name = "회원가입/로그인", description = "회원가입/로그인을 담당하는 api 그룹") | ||
@RequestMapping("/api/v1/auth") | ||
public class AuthController { | ||
|
||
private final KakaoOauthService kakaoOAuthService; | ||
|
||
@GetMapping("/callback") | ||
@Operation( | ||
summary = "카카오 회원가입/로그인 콜백", | ||
description = "카카오 로그인 후 리다이렉션된 URI입니다. 인가 코드를 받아서 accessToken을 요청하고, 회원가입 또는 로그인을 처리합니다.", | ||
responses = { | ||
@ApiResponse(responseCode = "200", description = "회원가입/로그인 성공"), | ||
@ApiResponse(responseCode = "400", description = "잘못된 요청"), | ||
@ApiResponse(responseCode = "500", description = "관리자 문의") | ||
} | ||
) | ||
public RspTemplate<AuthResDto> kakaoCallback(@RequestParam(name = "code") String code) { | ||
AuthResDto token = kakaoOAuthService.signUpOrLogin(code); | ||
return new RspTemplate<>(HttpStatus.OK, "토큰 발급", token); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/hackathon/momento/auth/api/dto/AuthResDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.hackathon.momento.auth.api.dto; | ||
|
||
import lombok.Builder; | ||
|
||
@Builder | ||
public record AuthResDto( | ||
String accessToken, | ||
String refreshToken | ||
) { | ||
public static AuthResDto of(String accessToken, String refreshToken) { | ||
return AuthResDto.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
src/main/java/com/hackathon/momento/auth/application/TokenRenewService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.hackathon.momento.auth.application; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class TokenRenewService { | ||
|
||
private static final String REFRESH_TOKEN_PREFIX = "refreshToken:"; | ||
|
||
private final RedisTemplate<String, String> redisTemplate; | ||
|
||
@Transactional | ||
public void saveRefreshToken(String refreshToken, Long memberId) { | ||
deleteExistingToken(memberId); | ||
|
||
String key = REFRESH_TOKEN_PREFIX + memberId; | ||
redisTemplate.opsForValue().set(key, refreshToken); | ||
} | ||
|
||
private void deleteExistingToken(Long memberId) { | ||
String key = REFRESH_TOKEN_PREFIX + memberId; | ||
redisTemplate.delete(key); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/com/hackathon/momento/global/config/RedisConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.hackathon.momento.global.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.lettuce.LettuceConnectionFactory; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
|
||
@Configuration | ||
public class RedisConfig { | ||
|
||
@Value("${spring.data.redis.host}") | ||
private String redisHost; | ||
|
||
@Value("${spring.data.redis.port}") | ||
private int redisPort; | ||
|
||
@Bean | ||
public RedisConnectionFactory redisConnectionFactory() { | ||
return new LettuceConnectionFactory(redisHost, redisPort); | ||
} | ||
|
||
@Bean | ||
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) { | ||
RedisTemplate<String, String> template = new RedisTemplate<>(); | ||
template.setConnectionFactory(connectionFactory); | ||
template.setKeySerializer(new StringRedisSerializer()); | ||
template.setValueSerializer(new StringRedisSerializer()); | ||
return template; | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
src/main/java/com/hackathon/momento/global/config/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.hackathon.momento.global.config; | ||
|
||
import com.hackathon.momento.global.jwt.JwtFilter; | ||
import com.hackathon.momento.global.jwt.TokenProvider; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
@RequiredArgsConstructor | ||
public class SecurityConfig { | ||
|
||
private final TokenProvider tokenprovider; | ||
private final String[] PERMIT_ALL_URLS = { | ||
"swagger-ui/**", | ||
"v3/api-docs/**", | ||
"api/v1/auth/**" | ||
}; | ||
|
||
@Bean | ||
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { | ||
return httpSecurity | ||
.httpBasic(AbstractHttpConfigurer::disable) | ||
.csrf(AbstractHttpConfigurer::disable) | ||
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy( | ||
SessionCreationPolicy.STATELESS)) | ||
.formLogin(AbstractHttpConfigurer::disable) | ||
.logout(AbstractHttpConfigurer::disable) | ||
.authorizeHttpRequests(authorizeRequests -> authorizeRequests | ||
.requestMatchers(PERMIT_ALL_URLS).permitAll() | ||
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN") // 관리자만 해당 URL에 접근 가능 | ||
.anyRequest().authenticated() | ||
) | ||
.addFilterBefore(new JwtFilter(tokenprovider), UsernamePasswordAuthenticationFilter.class) | ||
.build(); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
src/main/java/com/hackathon/momento/global/config/SwaggerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.hackathon.momento.global.config; | ||
|
||
import io.swagger.v3.oas.models.Components; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.info.Info; | ||
import io.swagger.v3.oas.models.security.SecurityRequirement; | ||
import io.swagger.v3.oas.models.security.SecurityScheme; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class SwaggerConfig { | ||
|
||
private Info apiInfo() { | ||
return new Info() | ||
.version("v1.0.0") | ||
.title("Momento API") | ||
.description("Momento API 명세서"); | ||
} | ||
|
||
@Bean | ||
public OpenAPI openAPI() { | ||
String authHeader = "Authorization"; | ||
|
||
return new OpenAPI() | ||
.info(apiInfo()) | ||
.addSecurityItem(new SecurityRequirement().addList(authHeader)) | ||
.components(new Components() | ||
.addSecuritySchemes(authHeader, new SecurityScheme() | ||
.name(authHeader) | ||
.type(SecurityScheme.Type.HTTP) | ||
.scheme("Bearer") | ||
.bearerFormat("JWT"))); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/com/hackathon/momento/global/entity/BaseEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.hackathon.momento.global.entity; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.EntityListeners; | ||
import jakarta.persistence.MappedSuperclass; | ||
import java.time.LocalDateTime; | ||
import lombok.Getter; | ||
import org.springframework.data.annotation.CreatedDate; | ||
import org.springframework.data.annotation.LastModifiedDate; | ||
import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
||
@Getter | ||
@MappedSuperclass | ||
@EntityListeners(AuditingEntityListener.class) | ||
public class BaseEntity { | ||
|
||
@CreatedDate | ||
@Column(nullable = false, updatable = false) | ||
private LocalDateTime createdAt; | ||
|
||
@LastModifiedDate | ||
@Column(nullable = false) | ||
private LocalDateTime updatedAt; | ||
} |
32 changes: 32 additions & 0 deletions
32
src/main/java/com/hackathon/momento/global/jwt/JwtFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.hackathon.momento.global.jwt; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.ServletRequest; | ||
import jakarta.servlet.ServletResponse; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import java.io.IOException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.util.StringUtils; | ||
import org.springframework.web.filter.GenericFilterBean; | ||
|
||
@RequiredArgsConstructor | ||
public class JwtFilter extends GenericFilterBean { | ||
|
||
private final TokenProvider tokenProvider; | ||
|
||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) | ||
throws IOException, ServletException { | ||
|
||
String token = tokenProvider.resolveToken((HttpServletRequest) request); | ||
|
||
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { | ||
Authentication authentication = tokenProvider.getAuthentication(token); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
} |
Oops, something went wrong.