-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] : JwtFilter의 인증 로직을 Security FilterChain 으로 통합한다 #312
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
Merged
Merged
Changes from 7 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
cf1d197
#311 [feat] : AdminUser를 UserDetails 객체로 래핑한다
anxi01 89755e4
#311 [feat] : SecurityContext에서 Admin ID를 조회하도록 변경한다
anxi01 26cb0d7
#311 [feat] : 테스트 환경에서 SecurityContext에 Admin, User를 분리하여 주입하도록 변경한다
anxi01 ae1c887
#311 [feat] : CustomUserDetails에 User Authority를 설정한다
anxi01 8e70e8e
#311 [feat] : Security Filterchain에서 401, 403 예외를 핸들링한다
anxi01 6aaf05b
#311 [feat] : Security Filterchain에서 요청의 인증, 인가를 관리한다
anxi01 75d1d0e
#311 [feat] : JwtFilter에서 토큰으로 Admin, User를 비교하여 SecurityContext에 주입한다
anxi01 3f6893f
#311 [refactor] : UserDetails의 default method를 제거한다
anxi01 1351fb9
#311 [feat] : Authentication NPE 방지 및 Admin 401 ErrorStatus를 추가한다
anxi01 db2c7c0
#311 [refactor] : SecurityContext에서 객체를 가져오지 못하는 경우의 에러타입을 변경한다
anxi01 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
56 changes: 56 additions & 0 deletions
56
src/main/java/side/onetime/auth/dto/CustomAdminDetails.java
This file contains hidden or 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,56 @@ | ||
| package side.onetime.auth.dto; | ||
|
|
||
| import org.springframework.security.core.GrantedAuthority; | ||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
| import org.springframework.security.core.userdetails.UserDetails; | ||
| import side.onetime.domain.AdminUser; | ||
| import side.onetime.domain.enums.AdminStatus; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
|
|
||
| public record CustomAdminDetails(AdminUser admin) implements UserDetails { | ||
|
|
||
| @Override | ||
| public Collection<? extends GrantedAuthority> getAuthorities() { | ||
| if (admin.getAdminStatus() == AdminStatus.PENDING_APPROVAL) { | ||
| return Collections.singletonList(new SimpleGrantedAuthority("ROLE_PENDING_APPROVAL")); | ||
| } | ||
|
|
||
| return Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN")); | ||
| } | ||
|
|
||
| @Override | ||
| public String getPassword() { | ||
| return admin.getPassword(); | ||
| } | ||
|
|
||
| @Override | ||
| public String getUsername() { | ||
| return admin.getName(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isAccountNonExpired() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isAccountNonLocked() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isCredentialsNonExpired() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isEnabled() { | ||
| return true; | ||
| } | ||
|
|
||
| public Long getId() { | ||
| return admin.getId(); | ||
| } | ||
| } | ||
This file contains hidden or 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
47 changes: 47 additions & 0 deletions
47
src/main/java/side/onetime/auth/exception/CustomAccessDeniedHandler.java
This file contains hidden or 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,47 @@ | ||
| package side.onetime.auth.exception; | ||
|
|
||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.security.access.AccessDeniedException; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
| import org.springframework.security.web.access.AccessDeniedHandler; | ||
| import org.springframework.stereotype.Component; | ||
| import side.onetime.global.common.status.ErrorStatus; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| public class CustomAccessDeniedHandler implements AccessDeniedHandler { | ||
|
|
||
| /** | ||
| * 접근할 수 있는 권한(Role)이 없을 때 발생한 accessDeniedException을 클라이언트에게 응답 형태로 반환합니다. | ||
| * | ||
| * @param request HTTP 요청 객체 | ||
| * @param response HTTP 응답 객체 | ||
| * @param accessDeniedException 접근 권한이 없어 발생한 exception | ||
| * @throws IOException 출력 스트림 처리 중 오류 발생 시 | ||
| */ | ||
| @Override | ||
| public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { | ||
| Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||
| String username = (authentication != null) ? authentication.getName() : "ANONYMOUS"; | ||
| log.error("❌ 금지된 접근 - 사용자: {}, 요청 URI: {}, 메서드: {}", username, request.getRequestURI(), request.getMethod()); | ||
|
|
||
| ErrorStatus status = ErrorStatus._FORBIDDEN; | ||
|
|
||
| response.setStatus(status.getHttpStatus().value()); | ||
| response.setContentType("application/json;charset=UTF-8"); | ||
| response.getWriter().write( | ||
| "{" | ||
| + "\"is_success\": false," | ||
| + "\"code\": \"" + status.getCode() + "\"," | ||
| + "\"message\": \"" + status.getMessage() + "\"," | ||
| + "\"payload\": null" | ||
| + "}" | ||
| ); | ||
| } | ||
| } |
43 changes: 43 additions & 0 deletions
43
src/main/java/side/onetime/auth/exception/CustomAuthenticationEntryPoint.java
This file contains hidden or 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,43 @@ | ||
| package side.onetime.auth.exception; | ||
|
|
||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.security.core.AuthenticationException; | ||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||
| import org.springframework.stereotype.Component; | ||
| import side.onetime.global.common.status.ErrorStatus; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||
|
|
||
| /** | ||
| * 로그인을 하지 않고 접근할 떄 발생한 authException을 클라이언트에게 응답 형태로 반환합니다. | ||
| * | ||
| * @param request HTTP 요청 객체 | ||
| * @param response HTTP 응답 객체 | ||
| * @param authException 로그인 없이 로그인이 필요한 리소스에 접근하여 발생한 exception | ||
| * @throws IOException 출력 스트림 처리 중 오류 발생 시 | ||
| */ | ||
| @Override | ||
| public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { | ||
| log.error("❌ 인증되지 않은 접근 - 요청 URI: {}, 메서드: {}", request.getRequestURI(), request.getMethod()); | ||
|
|
||
| ErrorStatus status = ErrorStatus._UNAUTHORIZED; | ||
|
|
||
| response.setStatus(status.getHttpStatus().value()); | ||
| response.setContentType("application/json;charset=UTF-8"); | ||
| response.getWriter().write( | ||
| "{" | ||
| + "\"is_success\": false," | ||
| + "\"code\": \"" + status.getCode() + "\"," | ||
| + "\"message\": \"" + status.getMessage() + "\"," | ||
| + "\"payload\": null" | ||
| + "}" | ||
| ); | ||
| } | ||
| } |
52 changes: 52 additions & 0 deletions
52
src/main/java/side/onetime/auth/service/CustomAdminDetailsService.java
This file contains hidden or 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,52 @@ | ||
| package side.onetime.auth.service; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.core.userdetails.UserDetails; | ||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||
| import org.springframework.stereotype.Service; | ||
| import side.onetime.auth.dto.CustomAdminDetails; | ||
| import side.onetime.domain.AdminUser; | ||
| import side.onetime.exception.CustomException; | ||
| import side.onetime.exception.status.AdminErrorStatus; | ||
| import side.onetime.repository.AdminRepository; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class CustomAdminDetailsService implements UserDetailsService { | ||
|
|
||
| private final AdminRepository adminRepository; | ||
|
|
||
| /** | ||
| * 관리자 이름으로 관리자 정보를 로드합니다. | ||
| * | ||
| * 데이터베이스에서 주어진 관리자 이름(username)을 기반으로 관리자를 조회하고, | ||
| * CustomAdminDetails 객체로 래핑하여 반환합니다. | ||
| * | ||
| * @param username 관리자 이름 | ||
| * @return 관리자 상세 정보 (CustomAdminDetails 객체) | ||
| * @throws CustomException 관리자 이름에 해당하는 관리자가 없을 경우 예외를 발생시킵니다. | ||
| */ | ||
| @Override | ||
| public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | ||
| AdminUser admin = adminRepository.findByName(username) | ||
| .orElseThrow(() -> new CustomException(AdminErrorStatus._NOT_FOUND_ADMIN_USER)); | ||
| return new CustomAdminDetails(admin); | ||
| } | ||
|
|
||
| /** | ||
| * 관리자 ID로 관리자 정보를 로드합니다. | ||
| * | ||
| * 데이터베이스에서 주어진 관리자 ID를 기반으로 관리자를 조회하고, | ||
| * CustomAdminDetails 객체로 래핑하여 반환합니다. | ||
| * | ||
| * @param adminId 관리자 ID | ||
| * @return 관리자 상세 정보 (CustomAdminDetails 객체) | ||
| * @throws CustomException 관리자 ID에 해당하는 관리자가 없을 경우 예외를 발생시킵니다. | ||
| */ | ||
| public UserDetails loadAdminByAdminId(Long adminId) throws UsernameNotFoundException { | ||
| AdminUser admin = adminRepository.findById(adminId) | ||
| .orElseThrow(() -> new CustomException(AdminErrorStatus._NOT_FOUND_ADMIN_USER)); | ||
| return new CustomAdminDetails(admin); | ||
| } | ||
| } |
This file contains hidden or 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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
이 메서드 4개는 디폴트가 true 리턴이라고 해서 현재는 불필요 메서드로 나오네요. 나중에 특정 비즈니스 로직이 필요할 때 구현해도 괜찮을 것 같습니다! (유저 details도 동일)
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.
좋습니다~ 추후에 필요할 때 추가해서 구현하는걸로!
3f6893f