Skip to content
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ jobs:
key: ${{ secrets.EC2_KEY }}
script: |
set -e
for i in {1..10}; do
for i in {1..20}; do
echo "[$i] health check..."
if curl -f http://localhost:${{ env.STOPPED_PORT }}/actuator/health > /dev/null 2>&1; then
echo "health check success"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UriUtils;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
Expand All @@ -38,16 +40,19 @@ public String getGoogleLoginUrl(String redirectUrl) {
return googleOauth.getOauthRedirectURL(redirectUrl);
}

public GoogleCallbackDto loginWithGoogle(String code, String redirectUrl) {
public GoogleCallbackDto loginWithGoogle(String code, String state) {
validateCode(code);
validateRedirectUrl(redirectUrl);

String decodedRedirectUrl = UriUtils.decode(state, StandardCharsets.UTF_8);

validateRedirectUrl(decodedRedirectUrl);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The loginWithGoogle method decodes the state parameter and uses it as a redirect URL. The validation performed by validateRedirectUrl uses startsWith, which can be bypassed (e.g., http://localhost:3000.evil.com starts with http://localhost:3000). This allows an attacker to redirect users to a malicious site after a successful login.


GoogleUserInfoResponse userInfo = getGoogleUserInfo(code);
User user = findOrCreateGoogleUser(userInfo);

Map<String, String> tokens = authService.issueTokens(user.getId());

return GoogleCallbackDto.create(redirectUrl, tokens.get("accessToken"), tokens.get("refreshToken"));
return GoogleCallbackDto.create(decodedRedirectUrl, tokens.get("accessToken"), tokens.get("refreshToken"));
}

private void validateCode(String code) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
Expand All @@ -25,6 +26,8 @@

import java.util.Arrays;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
Expand Down Expand Up @@ -73,38 +76,47 @@ public PasswordEncoder passwordEncoder() {
};

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
@Order(1)
public SecurityFilterChain swaggerFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher(SwaggerPatterns)
.cors(c -> c.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable);
.httpBasic(withDefaults());

http.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);

if (environmentUtil.isProdProfile()) {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(SwaggerPatterns).authenticated()
)
.httpBasic(basic -> {});
}
else {
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.httpBasic(withDefaults());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

swaggerFilterChain 내에서 httpBasic(withDefaults())가 중복으로 호출되고 있습니다. 이미 85번 라인에서 설정되었으므로, isProdProfile() 조건문 안의 이 호출은 제거해도 무방합니다. 중복 호출은 기능상 문제는 없지만, 코드의 명확성을 위해 제거하는 것이 좋습니다.

Suggested change
.httpBasic(withDefaults());
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())

} else {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(SwaggerPatterns).permitAll()
);
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
}

return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers(PermitAllPatterns).permitAll()
.requestMatchers(HttpMethod.GET, GetPermitPatterns).permitAll()
.anyRequest().authenticated()
);
.securityMatcher("/api/**")
.csrf(AbstractHttpConfigurer::disable)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

CSRF protection is explicitly disabled for the API (/api/**). However, the JwtAuthenticationFilter is configured to resolve the authentication token from cookies. This combination makes the application vulnerable to Cross-Site Request Forgery (CSRF) attacks, as an attacker can trick a logged-in user's browser into making unauthorized requests to the API.

.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.cors(c -> c.configurationSource(corsConfigurationSource()));

http.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers(HttpMethod.GET, GetPermitPatterns).permitAll()
.requestMatchers(PermitAllPatterns).permitAll()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

The PermitAllPatterns array includes the /env path, which is then permitted for all users in the apiFilterChain. In Spring Boot applications, the /env endpoint (often part of Actuator) can expose sensitive environment variables, including database credentials, API keys, and other secrets. Exposing this endpoint publicly is a significant security risk.

.anyRequest().authenticated()
);

http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

Expand Down Expand Up @@ -136,4 +148,4 @@ public CorsConfigurationSource corsConfigurationSource() {
}


}
}