Skip to content

Commit 22fff8d

Browse files
authored
[FEAT] Docs 작업(Swagger, Security, Cors) (#77)
* 📝docs: Swagger 작업 진행 * ✨feat: 인증/인가 추가 작업 * ✨feat: cors 작업 * 🐛fix: 코드 리뷰 피드백 반영
1 parent d67ea91 commit 22fff8d

File tree

12 files changed

+210
-23
lines changed

12 files changed

+210
-23
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ dependencies {
4545
developmentOnly 'com.h2database:h2'
4646
testImplementation 'com.h2database:h2'
4747

48+
//Swagger
49+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
4850

4951
}
5052

src/main/java/team/wego/wegobackend/auth/application/AuthService.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ public LoginResponse login(LoginRequest request) {
7171
throw new DeletedUserException();
7272
}
7373

74-
String accessToken = jwtTokenProvider.createAccessToken(user.getEmail(),
74+
String accessToken = jwtTokenProvider.createAccessToken(user.getId(), user.getEmail(),
7575
user.getRole().name());
7676

77-
String refreshToken = jwtTokenProvider.createRefreshToken(user.getEmail());
77+
String refreshToken = jwtTokenProvider.createRefreshToken(user.getId(), user.getEmail());
7878

7979
Long expiresIn = jwtTokenProvider.getAccessTokenExpiresIn();
8080

@@ -99,10 +99,8 @@ public RefreshResponse refresh(String refreshToken) {
9999
throw new DeletedUserException();
100100
}
101101

102-
String newAccessToken = jwtTokenProvider.createAccessToken(
103-
user.getEmail(),
104-
user.getRole().name()
105-
);
102+
String newAccessToken = jwtTokenProvider.createAccessToken(user.getId(), user.getEmail(),
103+
user.getRole().name());
106104

107105
Long expiresIn = jwtTokenProvider.getAccessTokenExpiresIn();
108106

src/main/java/team/wego/wegobackend/auth/presentation/AuthController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
@RestController
2626
@RequiredArgsConstructor
2727
@RequestMapping("/api/v1/auth")
28-
public class AuthController {
28+
public class AuthController implements AuthControllerDocs {
2929

3030
private final AuthService authService;
3131

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package team.wego.wegobackend.auth.presentation;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import jakarta.servlet.http.HttpServletResponse;
6+
import jakarta.validation.Valid;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.CookieValue;
9+
import org.springframework.web.bind.annotation.RequestBody;
10+
import team.wego.wegobackend.auth.application.dto.request.LoginRequest;
11+
import team.wego.wegobackend.auth.application.dto.request.SignupRequest;
12+
import team.wego.wegobackend.auth.application.dto.response.LoginResponse;
13+
import team.wego.wegobackend.auth.application.dto.response.RefreshResponse;
14+
import team.wego.wegobackend.auth.application.dto.response.SignupResponse;
15+
import team.wego.wegobackend.common.response.ApiResponse;
16+
17+
@Tag(name = "인증/인가 API", description = "인증 및 인가에 대한 API 리스트 \uD83D\uDC08")
18+
public interface AuthControllerDocs {
19+
20+
@Operation(summary = "회원가입", description = "회원가입을 위한 엔드포인트")
21+
ResponseEntity<ApiResponse<SignupResponse>> signup(
22+
@Valid @RequestBody SignupRequest request);
23+
24+
@Operation(summary = "로그인", description = "로그인을 위한 엔드포인트")
25+
ResponseEntity<ApiResponse<LoginResponse>> login(
26+
@Valid @RequestBody LoginRequest request,
27+
HttpServletResponse response);
28+
29+
@Operation(summary = "로그아웃", description = "리프레시 토큰 쿠키만 삭제합니다.")
30+
ResponseEntity<ApiResponse<Void>> logout(HttpServletResponse response);
31+
32+
@Operation(summary = "액세스 토큰 재발급", description = "리프레시 토큰 만료가 안되었을 경우 액세스 토큰을 재발급합니다.")
33+
ResponseEntity<ApiResponse<RefreshResponse>> refresh(
34+
@CookieValue(name = "refreshToken", required = false) String refreshToken);
35+
36+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package team.wego.wegobackend.common.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.servlet.config.annotation.CorsRegistry;
6+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7+
8+
@Configuration
9+
public class CorsConfig {
10+
@Bean
11+
public WebMvcConfigurer corsConfigurer() {
12+
return new WebMvcConfigurer() {
13+
@Override
14+
public void addCorsMappings(CorsRegistry registry) {
15+
registry.addMapping("/**")
16+
.allowedOrigins(
17+
"http://localhost:3000",
18+
"https://wego.monster"
19+
)
20+
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
21+
.allowedHeaders("*")
22+
.allowCredentials(true)
23+
.maxAge(3600);
24+
}
25+
};
26+
}
27+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package team.wego.wegobackend.common.config;
2+
3+
import io.swagger.v3.oas.models.Components;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Info;
6+
import io.swagger.v3.oas.models.security.SecurityRequirement;
7+
import io.swagger.v3.oas.models.security.SecurityScheme;
8+
import java.util.List;
9+
import org.springframework.context.annotation.Bean;
10+
import org.springframework.context.annotation.Configuration;
11+
12+
@Configuration
13+
public class SwaggerConfig {
14+
15+
@Bean
16+
public OpenAPI openAPI() {
17+
SecurityScheme securityScheme = new SecurityScheme()
18+
.type(SecurityScheme.Type.HTTP)
19+
.scheme("bearer")
20+
.bearerFormat("JWT")
21+
.in(SecurityScheme.In.HEADER)
22+
.name("Authorization");
23+
24+
SecurityRequirement securityRequirement = new SecurityRequirement()
25+
.addList("bearerAuth");
26+
27+
return new OpenAPI()
28+
.components(new Components()
29+
.addSecuritySchemes("bearerAuth", securityScheme))
30+
.security(List.of(securityRequirement))
31+
.info(apiInfo());
32+
}
33+
34+
private Info apiInfo() {
35+
return new Info()
36+
.title("WeGo-API Documentation")
37+
.description("Welcome To WeGo-API Documentation \uD83D\uDE0A")
38+
.version("1.0.0");
39+
}
40+
}

src/main/java/team/wego/wegobackend/common/security/JwtAuthenticationFilter.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import jakarta.servlet.FilterChain;
5-
import jakarta.servlet.ServletException;
65
import jakarta.servlet.http.HttpServletRequest;
76
import jakarta.servlet.http.HttpServletResponse;
87
import java.io.IOException;
@@ -12,11 +11,11 @@
1211
import org.springframework.http.HttpStatus;
1312
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1413
import org.springframework.security.core.context.SecurityContextHolder;
15-
import org.springframework.security.core.userdetails.UserDetails;
1614
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
1715
import org.springframework.stereotype.Component;
1816
import org.springframework.util.AntPathMatcher;
1917
import org.springframework.util.StringUtils;
18+
import org.springframework.web.cors.CorsUtils;
2019
import org.springframework.web.filter.OncePerRequestFilter;
2120
import team.wego.wegobackend.auth.exception.UserNotFoundException;
2221
import team.wego.wegobackend.common.response.ErrorResponse;
@@ -53,7 +52,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
5352

5453
String email = jwtTokenProvider.getEmailFromToken(jwt);
5554

56-
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
55+
CustomUserDetails userDetails = (CustomUserDetails) userDetailsService.loadUserByUsername(email);
5756

5857
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
5958
userDetails, null, userDetails.getAuthorities());
@@ -66,11 +65,16 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
6665

6766
log.debug("JWT 인증 성공: {}", email);
6867

69-
filterChain.doFilter(request, response);
70-
return;
7168
}
69+
else {
70+
if(!isPublicEndpoint(request)) {
71+
sendJsonError(response, "토큰을 찾을 수 없습니다.");
72+
return;
73+
}
74+
}
75+
76+
filterChain.doFilter(request, response);
7277

73-
sendJsonError(response, "토큰을 찾을 수 없습니다.");
7478

7579
} catch (ExpiredTokenException | InvalidTokenException |UserNotFoundException e) {
7680
sendJsonError(response, e.getMessage());
@@ -107,4 +111,25 @@ private void sendJsonError(HttpServletResponse response, String message) throws
107111

108112
objectMapper.writeValue(response.getWriter(), errorResponse);
109113
}
114+
115+
/**
116+
* Public 엔드포인트 확인
117+
*/
118+
private boolean isPublicEndpoint(HttpServletRequest request) {
119+
String path = request.getRequestURI();
120+
String method = request.getMethod();
121+
122+
if (CorsUtils.isPreFlightRequest(request)) {
123+
return true;
124+
}
125+
126+
//TODO : PUBLIC_PATTERNS 관리 포인트 개선 필요 (메서드까지 관리 확장)
127+
if ("GET".equals(method) && pathMatcher.match("/api/v1/users/*", path)) {
128+
return true;
129+
}
130+
131+
// SecurityEndpoints.PUBLIC_PATTERNS 체크
132+
return Arrays.stream(SecurityEndpoints.PUBLIC_PATTERNS)
133+
.anyMatch(pattern -> pathMatcher.match(pattern, path));
134+
}
110135
}

src/main/java/team/wego/wegobackend/common/security/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import lombok.RequiredArgsConstructor;
44
import org.springframework.context.annotation.Bean;
55
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.http.HttpMethod;
67
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
78
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
89
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
@@ -29,6 +30,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2930

3031
http
3132
.authorizeHttpRequests((auth) -> auth
33+
.requestMatchers(HttpMethod.GET, "/api/v1/users/*").permitAll()
3234
.requestMatchers(SecurityEndpoints.PUBLIC_PATTERNS).permitAll()
3335
.anyRequest().authenticated()
3436
);

src/main/java/team/wego/wegobackend/common/security/SecurityEndpoints.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ public class SecurityEndpoints {
44

55
public static final String[] PUBLIC_PATTERNS = {
66
"/api/v*/auth/**",
7-
"/api/v*/docs/**",
87
"/api/v*/health",
98
"/h2-console/**",
10-
"/error"
9+
"/error",
10+
11+
//SpringDoc
12+
"/swagger-ui/**",
13+
"/swagger-ui.html",
14+
"/api-docs/**",
15+
"/v*/api-docs/**",
1116
};
17+
1218
}

src/main/java/team/wego/wegobackend/common/security/jwt/JwtTokenProvider.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,21 @@ protected void init() {
4646
/**
4747
* JWT 토큰 생성
4848
*/
49-
public String createAccessToken(String email, String role) {
50-
return createToken(email, role, accessTokenExpiration, "access");
49+
public String createAccessToken(Long userId, String email, String role) {
50+
return createToken(userId, email, role, accessTokenExpiration, "access");
5151
}
5252

53-
public String createRefreshToken(String email) {
54-
return createToken(email, null, refreshTokenExpiration, "refresh");
53+
public String createRefreshToken(Long userId, String email) {
54+
return createToken(userId, email, null, refreshTokenExpiration, "refresh");
5555
}
5656

57-
private String createToken(String email, String role, long expiration, String type) {
57+
private String createToken(Long userId, String email, String role, long expiration, String type) {
5858
Date now = new Date();
5959
Date expiryDate = new Date(now.getTime() + expiration);
6060

6161
JwtBuilder builder = Jwts.builder()
6262
.subject(email)
63+
.claim("userId", userId)
6364
.claim("type", type)
6465
.issuedAt(now)
6566
.expiration(expiryDate);
@@ -71,12 +72,16 @@ private String createToken(String email, String role, long expiration, String ty
7172
return builder.signWith(secretKey, Jwts.SIG.HS256).compact();
7273
}
7374

75+
public String getTokenUserId(String token) {
76+
return getClaims(token).get("userId", String.class);
77+
}
78+
7479
public String getEmailFromToken(String token) {
7580
return getClaims(token).getSubject();
7681
}
7782

78-
public String getRoleFromToken(String token) {
79-
return getClaims(token).get("role", String.class);
83+
public Long getRoleFromToken(String token) {
84+
return getClaims(token).get("role", Long.class);
8085
}
8186

8287
public String getTokenType(String token) {

0 commit comments

Comments
 (0)