일반 로그인 기능 추가 및 트러블슈팅
❌ 일반 로그인 시 마이페이지 접속할 때 principalDetails is null 에러 발생
원인
일반 로그인 시 반환되는 객체가 PrincipalDetails가 아닌 User 객체
해결
1. 사용자 정보를 PrincipalDetails 객체로 반환
CustomUserDetailsService → 사용자 정보를 DB에서 조회하여 PrincipalDetails 객체로 반환
@Service
@RequiredArgsConstructor
@Transactional(readOnly = false)
public class CustomUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("username(%s) not found".formatted(username)));
return new PrincipalDetails(member, null);
}
}
SecurityConfig에서 .formLogin() 설정 추가
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.formLogin(
formLogin -> formLogin
.loginPage("/usr/member/login")
.loginProcessingUrl("/usr/member/login")
.failureHandler(userLoginFailureHandler)
.defaultSuccessUrl("/main")
)
.oauth2Login(
oauth2Login -> oauth2Login
.loginPage("/usr/member/login")
.userInfoEndpoint()
.userService(principalOAuth2UserService) // OAuth2.0 로그인 성공 시 실행
.and()
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler(authenticationFailureHandler())
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/usr/member/login")
.invalidateHttpSession(true)
.deleteCookies("access_token") // 로그아웃 시 `access_token` 쿠키 삭제
)
.authenticationProvider(authenticationProvider());
return http.build();
}
OAuth2 로그인과 통합
기존에는 OAuth2 로그인 성공 시 PrincipalDetails를 생성
이제는 일반 로그인도 PrincipalDetails를 사용하도록 통일
PrincipalDetails 수정
@Getter
public class PrincipalDetails implements UserDetails, OAuth2User {
private final Member member;
private final OAuth2UserInfo oAuth2UserInfo;
public PrincipalDetails(Member member, OAuth2UserInfo oAuth2UserInfo) {
this.member = member;
this.oAuth2UserInfo = oAuth2UserInfo;
}
public Long getUserId() {
return member.getId();
}
public String getNickname() {
return member.getNickname();
}
// UserDetails 필수 구현 메서드
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// member.getRole()을 사용하려면 Enum 기반 권한이면 그 값 사용
// 예: ROLE_MEMBER
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(member.getRole().name()));
return grantedAuthorities;
}
@Override
public String getPassword() {
return member.getPassword();
}
@Override
public String getUsername() {
return member.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true; // 계정 만료 여부
}
@Override
public boolean isAccountNonLocked() {
return true; // 계정 잠김 여부
}
@Override
public boolean isCredentialsNonExpired() {
return true; // 비밀번호 만료 여부
}
@Override
public boolean isEnabled() {
return true; // 계정 활성화 여부
}
// OAuth2User 구현 메서드
@Override
public Map<String, Object> getAttributes() {
return oAuth2UserInfo != null ? oAuth2UserInfo.getAttributes() : Collections.emptyMap();
}
@Override
public String getName() {
return oAuth2UserInfo != null ? oAuth2UserInfo.getProviderId() : member.getId().toString();
}
@Override
public <A> A getAttribute(String name) {
return oAuth2UserInfo != null ? (A) oAuth2UserInfo.getAttributes().get(name) : null;
}
}