-
Notifications
You must be signed in to change notification settings - Fork 2
MOSU-88 feat: OAuth 처리 절차 수정 #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6797f67
da199f3
91387c1
0a8882f
d21d60c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,26 +5,31 @@ | |||||||||||||||||
| import java.util.Map; | ||||||||||||||||||
| import life.mosu.mosuserver.domain.user.UserJpaEntity; | ||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||
| import org.springframework.security.core.GrantedAuthority; | ||||||||||||||||||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||||||||||||||||||
| import org.springframework.security.core.userdetails.UserDetails; | ||||||||||||||||||
| import org.springframework.security.oauth2.core.user.OAuth2User; | ||||||||||||||||||
|
|
||||||||||||||||||
| public class OAuthUser implements OAuth2User, UserDetails { | ||||||||||||||||||
| @Slf4j | ||||||||||||||||||
| public class OAuthUser implements OAuth2User { | ||||||||||||||||||
|
|
||||||||||||||||||
| @Getter | ||||||||||||||||||
| private final UserJpaEntity user; | ||||||||||||||||||
| private final Map<String, Object> attributes; | ||||||||||||||||||
| private final String attributeKey; | ||||||||||||||||||
| @Getter | ||||||||||||||||||
| Boolean isProfileRegistered; | ||||||||||||||||||
|
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix field naming convention and consider type choice. The field has several issues:
- @Getter
- Boolean isProfileRegistered;
+ @Getter
+ private final boolean profileRegistered;Then update the constructor parameter accordingly. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider using primitive boolean type for better null safety. Using @Getter
-Boolean isProfileRegistered;
+boolean isProfileRegistered;If null values are intentionally allowed for this field, please add documentation explaining when and why this field would be null. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| public OAuthUser( | ||||||||||||||||||
| final UserJpaEntity user, | ||||||||||||||||||
| final Map<String, Object> attributes, | ||||||||||||||||||
| final String attributeKey | ||||||||||||||||||
| final String attributeKey, | ||||||||||||||||||
| final Boolean isProfileRegistered | ||||||||||||||||||
| ) { | ||||||||||||||||||
|
Comment on lines
+26
to
28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add parameter validation and maintain consistency with field naming. If the field naming is updated as suggested, ensure the constructor parameter name is also updated for consistency. Consider adding validation for the new parameter. public OAuthUser(
final UserJpaEntity user,
final Map<String, Object> attributes,
- final String attributeKey,
- final Boolean isProfileRegistered
+ final String attributeKey,
+ final boolean profileRegistered
) {
this.user = user;
this.attributes = attributes;
this.attributeKey = attributeKey;
- this.isProfileRegistered = isProfileRegistered;
+ this.profileRegistered = profileRegistered;
}Also applies to: 32-32 🤖 Prompt for AI Agents |
||||||||||||||||||
| this.user = user; | ||||||||||||||||||
| this.attributes = attributes; | ||||||||||||||||||
| this.attributeKey = attributeKey; | ||||||||||||||||||
| this.isProfileRegistered = isProfileRegistered; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
|
|
@@ -44,34 +49,4 @@ public Collection<? extends GrantedAuthority> getAuthorities() { | |||||||||||||||||
| new SimpleGrantedAuthority(role) | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public String getPassword() { | ||||||||||||||||||
| return null; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public String getUsername() { | ||||||||||||||||||
| return user.getName(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public boolean isAccountNonExpired() { | ||||||||||||||||||
| return UserDetails.super.isAccountNonExpired(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public boolean isAccountNonLocked() { | ||||||||||||||||||
| return UserDetails.super.isAccountNonLocked(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public boolean isCredentialsNonExpired() { | ||||||||||||||||||
| return UserDetails.super.isCredentialsNonExpired(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public boolean isEnabled() { | ||||||||||||||||||
| return UserDetails.super.isEnabled(); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,26 +1,23 @@ | ||||||||||
| package life.mosu.mosuserver.global.handler; | ||||||||||
|
|
||||||||||
| import jakarta.servlet.http.Cookie; | ||||||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||
| import java.io.IOException; | ||||||||||
| import life.mosu.mosuserver.application.auth.AccessTokenService; | ||||||||||
| import life.mosu.mosuserver.application.oauth.OAuthUser; | ||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||
| import org.springframework.security.core.Authentication; | ||||||||||
| import org.springframework.security.web.authentication.AuthenticationSuccessHandler; | ||||||||||
| import org.springframework.stereotype.Component; | ||||||||||
| import org.springframework.web.util.UriComponentsBuilder; | ||||||||||
|
|
||||||||||
| @Slf4j | ||||||||||
| @Component | ||||||||||
| @RequiredArgsConstructor | ||||||||||
| public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { | ||||||||||
|
|
||||||||||
| private final AccessTokenService accessTokenService; | ||||||||||
| @Value("${target.url}") | ||||||||||
| private String targetUrl; | ||||||||||
|
|
||||||||||
| @Override | ||||||||||
| public void onAuthenticationSuccess( | ||||||||||
|
|
@@ -29,16 +26,20 @@ public void onAuthenticationSuccess( | |||||||||
| final Authentication authentication | ||||||||||
| ) throws IOException { | ||||||||||
| final OAuthUser oAuthUser = (OAuthUser) authentication.getPrincipal(); | ||||||||||
|
|
||||||||||
| final String accessToken = accessTokenService.generateAccessToken(oAuthUser.getUser()); | ||||||||||
|
|
||||||||||
| final String redirectUrlWithToken = UriComponentsBuilder.fromUriString(targetUrl) | ||||||||||
| .queryParam("token", accessToken) | ||||||||||
| .build() | ||||||||||
| .toUriString(); | ||||||||||
|
|
||||||||||
| log.info("로그인 성공. 리다이렉트 URL: {}", redirectUrlWithToken); | ||||||||||
|
|
||||||||||
| response.sendRedirect(redirectUrlWithToken); | ||||||||||
| String jsonResponse = | ||||||||||
| "{\"isProfileRegistered\": \"" + oAuthUser.getIsProfileRegistered() + "\"}"; | ||||||||||
|
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manually constructing JSON strings is error-prone. The boolean value While the best long-term solution is to use a JSON serialization library like Jackson (e.g., via
Suggested change
|
||||||||||
|
|
||||||||||
| Cookie cookie = new Cookie("accessToken", accessToken); | ||||||||||
| cookie.setHttpOnly(true); | ||||||||||
| cookie.setSecure(true); | ||||||||||
| cookie.setPath("/"); | ||||||||||
| cookie.setMaxAge(3600); | ||||||||||
| response.addCookie(cookie); | ||||||||||
|
|
||||||||||
| response.setStatus(HttpServletResponse.SC_OK); | ||||||||||
| response.setContentType("application/json"); | ||||||||||
| response.setCharacterEncoding("UTF-8"); | ||||||||||
| response.getWriter().write(jsonResponse); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package life.mosu.mosuserver.presentation.auth.dto; | ||
|
|
||
| public record LoginResponse( | ||
| Token token, | ||
| Boolean isProfileRegistered | ||
| ) { | ||
|
|
||
| public static LoginResponse of(final Token token, final Boolean isProfileRegistered) { | ||
| return new LoginResponse(token, isProfileRegistered); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,7 +11,6 @@ | |||||
| import lombok.extern.slf4j.Slf4j; | ||||||
| import org.springframework.http.HttpStatus; | ||||||
| import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.security.access.prepost.PreAuthorize; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||||
| import org.springframework.web.bind.annotation.PutMapping; | ||||||
|
|
@@ -38,7 +37,7 @@ public ResponseEntity<ApiResponseWrapper<Void>> create( | |||||
| } | ||||||
|
|
||||||
| @PutMapping | ||||||
| @PreAuthorize("isAuthenticated()") | ||||||
| // @PreAuthorize("isAuthenticated()") | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Please reinstate this annotation to ensure that only authenticated users can modify profiles.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainCritical: Authentication check removed from profile update endpoint. The Please verify this change is intentional and that alternative authentication mechanisms are in place. 🏁 Script executed: #!/bin/bash
# Verify if there are alternative authentication mechanisms for this endpoint
ast-grep --pattern 'class ProfileController {
$$$
@PutMapping
$$$
public ResponseEntity<$_> update($_) {
$$$
}
$$$
}'Length of output: 135 🏁 Script executed: #!/bin/bash
# Locate and inspect ProfileController for authentication annotations on the update endpoint
FILE="src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java"
if [[ -f "$FILE" ]]; then
echo "----- Begin ProfileController.java (lines 1–200) -----"
sed -n '1,200p' "$FILE"
echo "----- End excerpt -----"
echo
echo "----- Security annotations in ProfileController.java -----"
rg -n "@PreAuthorize|@Secured|@RolesAllowed" "$FILE" || echo "(none found)"
echo
echo "----- @PutMapping occurrences in ProfileController.java -----"
rg -n "@PutMapping" "$FILE" || echo "(none found)"
else
echo "Error: File not found: $FILE"
exit 1
fiLength of output: 3303 Critical: Profile update endpoint is unprotected and accepts arbitrary user IDs Observations:
Recommended fixes:
🤖 Prompt for AI Agents |
||||||
| public ResponseEntity<ApiResponseWrapper<Void>> update( | ||||||
| @RequestParam Long userId, | ||||||
| @Valid @RequestBody EditProfileRequest request | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
catch (final Exception e)block is too broad. It catches all exceptions, including potentialRuntimeExceptions, and masks them as anINCORRECT_ID_OR_PASSWORDerror. This can make debugging difficult as it hides the true cause of an error.It's better to catch the more specific
AuthenticationExceptionthat is thrown byauthenticate()on login failure. Other unexpected exceptions should be allowed to propagate to be handled by a global exception handler.