Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3d856c0
feat : 회원가입 추가 #1
minhyuk2 Jul 15, 2025
4421d59
feat : CustomException 및 에러 추가 #1
minhyuk2 Jul 16, 2025
988aac5
feat : 유저역할 추가 #1
minhyuk2 Jul 16, 2025
643b841
feat : 회원정보 수정 및 로그아웃 추가 #1
minhyuk2 Jul 16, 2025
6ca0104
feat : 토큰 관련 기능 추가 #1
minhyuk2 Jul 16, 2025
7fda6cf
feat : 회원정보 수정 dto 추가 #1
minhyuk2 Jul 16, 2025
f0a2078
feat : 회원탈퇴 기능 구현 #1
minhyuk2 Jul 16, 2025
33e53b0
feat : 토큰 검증 부분 추가 #1
minhyuk2 Jul 16, 2025
697da14
style : 패키지 위치 이동 #1
minhyuk2 Jul 16, 2025
2e5bdb8
feat : application.yml 추가 #1
minhyuk2 Jul 16, 2025
806e642
feat : application.yml 추가 #1
minhyuk2 Jul 16, 2025
109d4aa
feat : SecurityConfig 추가 #1
minhyuk2 Jul 16, 2025
6dee27e
feat : SwaggerConfig 추가 #1
minhyuk2 Jul 16, 2025
d926cdc
fix : 파일 위치 수정 #1
minhyuk2 Jul 17, 2025
7b1198b
feat : 로그인 request 추가 #1
minhyuk2 Jul 17, 2025
20624e4
feat : 로그인 request 필드 추가로 수정 #1
minhyuk2 Jul 17, 2025
699bffc
fix : oauth properties 수정 #1
minhyuk2 Jul 17, 2025
b799b96
fix : 중복되는 속성 제거 #1
minhyuk2 Jul 17, 2025
3f6e218
fix : gradle 버전 일치 #1
minhyuk2 Jul 17, 2025
3b2964d
fix : 필요없는 설정 제거 #1
minhyuk2 Jul 17, 2025
ac9a3d4
feat : swagger config 추가 #1
minhyuk2 Jul 17, 2025
3cf5c26
fix : authentication 중복 제거 #1
minhyuk2 Jul 17, 2025
924478d
fix : 시큐리티 exclue 제거 #1
minhyuk2 Jul 17, 2025
f0f6344
feat : 에러코드 추가 #1
minhyuk2 Jul 17, 2025
3240c69
style : 공백 제거 #1
minhyuk2 Jul 17, 2025
f5d3f45
feat : 카카오 로그인 추가 #1
minhyuk2 Jul 17, 2025
602bc62
fix : 자잘한 변경들 추가 #1
minhyuk2 Jul 17, 2025
14839bb
style : summary 추가 #1
minhyuk2 Jul 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/modules/1984157471/greatjourney.main.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules/backend.greatjourney.main.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions greatjourney/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ out/


application.properties
application.yml

src/main/resources/application.properties

Expand Down
17 changes: 12 additions & 5 deletions greatjourney/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ repositories {
}

dependencies {
implementation 'org.springframework:spring-webmvc:6.1.11'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand All @@ -31,10 +32,10 @@ dependencies {
testImplementation 'org.springframework.security:spring-security-test'

//jwt
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate OAuth2 client dependency.

The spring-boot-starter-oauth2-client dependency is declared twice (lines 38 and 47). This can cause confusion and potential version conflicts.

Apply this diff to remove the duplicate:

 	//jwt
 	implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
 	implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
 	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
-	implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
 
 	// AWS SDK for S3 (v1)

Also applies to: 47-47

🤖 Prompt for AI Agents
In greatjourney/build.gradle at lines 38 and 47, the dependency
'org.springframework.boot:spring-boot-starter-oauth2-client' is declared twice.
Remove the duplicate declaration at line 38 to avoid confusion and potential
version conflicts, keeping only one instance of this dependency in the file.


// AWS SDK for S3 (v1)
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.565'
Expand All @@ -52,7 +53,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-mail'

//swagger 관련 코드
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

//jsoup 관련 코드
implementation 'org.jsoup:jsoup:1.18.1'
Expand All @@ -68,6 +69,12 @@ dependencies {

implementation 'org.apache.commons:commons-csv:1.10.0'
implementation 'org.apache.poi:poi-ooxml:5.2.5'

//querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@EnableJpaAuditing
@SpringBootApplication
public class GreatjourneyApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package backend.greatjourney.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@Configuration
public class QueryDslConfig {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
package backend.greatjourney.config;

import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import org.springframework.context.annotation.Bean;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;

@Configuration
public class SwaggerConfig {
// @Bean
// public OpenAPI api() {
// SecurityScheme apiKey = new SecurityScheme()
// .type(SecurityScheme.Type.APIKEY)
// .in(SecurityScheme.In.HEADER)
// .name("Authorization");
//
// SecurityRequirement securityRequirement = new SecurityRequirement()
// .addList("Bearer Token");
//
// return new OpenAPI()
// .components(new Components().addSecuritySchemes("Bearer Token", apiKey))
// .addSecurityItem(securityRequirement);
// }
}
@Bean
public OpenAPI openAPI() {
SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth");

return new OpenAPI()
.components(new Components())
.info(apiInfo())
.addSecurityItem(securityRequirement)
.schemaRequirement("BearerAuth", securityScheme());
}
Comment on lines +14 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the security scheme registration method.

The method schemaRequirement on line 22 should be components() to properly register the security scheme. The current implementation won't correctly associate the security scheme with the OpenAPI specification.

Apply this diff to fix the security scheme registration:

 return new OpenAPI()
-	.components(new Components())
+	.components(new Components().addSecuritySchemes("BearerAuth", securityScheme()))
 	.info(apiInfo())
 	.addSecurityItem(securityRequirement)
-	.schemaRequirement("BearerAuth", securityScheme());

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In greatjourney/src/main/java/backend/greatjourney/config/SwaggerConfig.java
lines 14 to 23, replace the call to schemaRequirement("BearerAuth",
securityScheme()) with a proper registration of the security scheme inside the
components() method. Specifically, add the security scheme to the Components
object and set it via components() on the OpenAPI instance to correctly
associate the security scheme with the OpenAPI specification.


private Info apiInfo() {
return new Info()
.title("어디로 API")
.description("대장정이팀 API 명세서입니다")
.version("1.0.0");
}

private SecurityScheme securityScheme() {
return new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name("Authorization");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package backend.greatjourney.domain.token.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class TokenResponse {
private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package backend.greatjourney.domain.token.entity;

import java.time.Instant;

import org.springframework.web.bind.annotation.RequestParam;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused import.

The @RequestParam import is not used in this entity class and should be removed.

Apply this diff to remove the unused import:

 import java.time.Instant;
 
-import org.springframework.web.bind.annotation.RequestParam;
-
 import jakarta.persistence.Entity;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import org.springframework.web.bind.annotation.RequestParam;
import java.time.Instant;
import jakarta.persistence.Entity;
🤖 Prompt for AI Agents
In
greatjourney/src/main/java/backend/greatjourney/domain/token/entity/RefreshToken.java
at line 5, remove the unused import statement for
org.springframework.web.bind.annotation.RequestParam since it is not used
anywhere in this entity class.


import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@Getter
public class RefreshToken {

@Id
private String tokenId;
private Long userId;
private String token;
private Instant expiryDate;
Comment on lines +18 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding JPA column annotations for better control.

Consider adding column annotations for better database schema control and validation:

 	@Id
+	@Column(length = 255, nullable = false)
 	private String tokenId;
+	
+	@Column(nullable = false)
 	private Long userId;
+	
+	@Column(columnDefinition = "TEXT", nullable = false)
 	private String token;
+	
+	@Column(nullable = false)
 	private Instant expiryDate;

The tokenId as a String primary key is acceptable for tokens, but ensure it's always properly set in the application logic since there's no @GeneratedValue.

🤖 Prompt for AI Agents
In
greatjourney/src/main/java/backend/greatjourney/domain/token/entity/RefreshToken.java
around lines 18 to 22, add JPA @Column annotations to the fields tokenId,
userId, token, and expiryDate to specify column properties such as name, length,
nullable, and uniqueness as needed for better schema control and validation.
Also, ensure the tokenId field is always explicitly set in the application since
it lacks @GeneratedValue for automatic ID generation.


@Builder
private RefreshToken(Long userId, String id, String token, Instant expiryDate) {
this.userId = userId;
this.tokenId = id;
this.token = token;
this.expiryDate = expiryDate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package backend.greatjourney.domain.token.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import backend.greatjourney.domain.token.entity.RefreshToken;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken,Long> {

Void deleteByToken(String token);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the return type for delete operation.

The deleteByToken method should return void (primitive) instead of Void (wrapper). Spring Data JPA delete operations typically return void.

-	Void deleteByToken(String token);
+	void deleteByToken(String token);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Void deleteByToken(String token);
Void deleteByToken(String token);
+ void deleteByToken(String token);
-Void deleteByToken(String token);
🤖 Prompt for AI Agents
In
greatjourney/src/main/java/backend/greatjourney/domain/token/repository/RefreshTokenRepository.java
at line 9, change the return type of the deleteByToken method from the wrapper
type Void to the primitive void, as Spring Data JPA delete operations
conventionally use void for their return type.

RefreshToken findByUserId(Long userId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Optional for null safety.

The findByUserId method should return Optional<RefreshToken> instead of RefreshToken directly to handle cases where no token is found for the user ID, preventing potential null pointer exceptions.

-	RefreshToken findByUserId(Long userId);
+	Optional<RefreshToken> findByUserId(Long userId);
🤖 Prompt for AI Agents
In
greatjourney/src/main/java/backend/greatjourney/domain/token/repository/RefreshTokenRepository.java
at line 10, change the return type of the method findByUserId from RefreshToken
to Optional<RefreshToken> to improve null safety. This involves updating the
method signature to return Optional<RefreshToken> and adjusting any calling code
to handle the Optional accordingly.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package backend.greatjourney.domain.token.service;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring("Bearer ".length());

if (jwtTokenProvider.validateToken(token)) {
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) jwtTokenProvider.getAuthentication(token);

SecurityContextHolder.getContext().setAuthentication(authentication);
}
}

filterChain.doFilter(request, response);
}
Comment on lines +22 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add exception handling and improve token validation robustness.

The current implementation has several areas for improvement:

  1. Missing exception handling: Token validation could throw exceptions that would break the filter chain
  2. Unsafe casting: Direct casting to UsernamePasswordAuthenticationToken without type checking
  3. Silent failures: No logging when authentication fails

Consider this improved implementation:

@Override
protected void doFilterInternal(HttpServletRequest request,
	HttpServletResponse response,
	FilterChain filterChain)
	throws ServletException, IOException {

	String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

	if (authHeader != null && authHeader.startsWith("Bearer ")) {
		String token = authHeader.substring("Bearer ".length());
		
+		try {
			if (jwtTokenProvider.validateToken(token)) {
-				UsernamePasswordAuthenticationToken authentication =
-					(UsernamePasswordAuthenticationToken) jwtTokenProvider.getAuthentication(token);
+				Authentication authentication = jwtTokenProvider.getAuthentication(token);
+				if (authentication instanceof UsernamePasswordAuthenticationToken) {
					SecurityContextHolder.getContext().setAuthentication(authentication);
+				}
			}
+		} catch (Exception e) {
+			// Log the exception but continue the filter chain
+			// Invalid tokens should not break the request processing
+		}
	}

	filterChain.doFilter(request, response);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring("Bearer ".length());
if (jwtTokenProvider.validateToken(token)) {
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring("Bearer ".length());
try {
if (jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
if (authentication instanceof UsernamePasswordAuthenticationToken) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
} catch (Exception e) {
// Log the exception but continue the filter chain
// Invalid tokens should not break the request processing
}
}
filterChain.doFilter(request, response);
}
🤖 Prompt for AI Agents
In
greatjourney/src/main/java/backend/greatjourney/domain/token/service/JwtAuthenticationFilter.java
around lines 22 to 42, add exception handling around the token validation and
authentication retrieval to prevent breaking the filter chain on errors. Replace
the direct cast to UsernamePasswordAuthenticationToken with a safe type check
before setting the authentication in the SecurityContextHolder. Also, add
logging to capture and report authentication failures or exceptions to improve
observability and debugging.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package backend.greatjourney.domain.token.service;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationManager implements AuthenticationManager {
private final JwtTokenProvider jwtTokenProvider;


public JwtAuthenticationManager(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}

@Override
public Authentication authenticate(Authentication authentication) {
String token = authentication.getCredentials().toString();
return jwtTokenProvider.getAuthentication(token);
}
Comment on lines +16 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and error handling.

The current implementation lacks proper validation and error handling, which could lead to runtime exceptions.

Consider this improved implementation:

@Override
public Authentication authenticate(Authentication authentication) {
+	if (authentication == null || authentication.getCredentials() == null) {
+		throw new AuthenticationException("Invalid authentication object") {};
+	}
+	
	String token = authentication.getCredentials().toString();
+	if (token == null || token.trim().isEmpty()) {
+		throw new AuthenticationException("Invalid token") {};
+	}
+	
	return jwtTokenProvider.getAuthentication(token);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public Authentication authenticate(Authentication authentication) {
String token = authentication.getCredentials().toString();
return jwtTokenProvider.getAuthentication(token);
}
@Override
public Authentication authenticate(Authentication authentication) {
if (authentication == null || authentication.getCredentials() == null) {
throw new AuthenticationException("Invalid authentication object") {};
}
String token = authentication.getCredentials().toString();
if (token.trim().isEmpty()) {
throw new AuthenticationException("Invalid token") {};
}
return jwtTokenProvider.getAuthentication(token);
}
🤖 Prompt for AI Agents
In
greatjourney/src/main/java/backend/greatjourney/domain/token/service/JwtAuthenticationManager.java
around lines 16 to 20, the authenticate method lacks input validation and error
handling, which may cause runtime exceptions. Add checks to ensure the
authentication object and its credentials are not null before proceeding. Wrap
the call to jwtTokenProvider.getAuthentication(token) in a try-catch block to
handle potential exceptions gracefully, and throw an appropriate
AuthenticationException if validation fails or an error occurs.

}


Loading
Loading