-
2024-02-01 인증 에러 해결개발 일지 2024. 2. 1. 22:57
https://tangpoo.tistory.com/161
2024-01-31 JWT 로그인 검증 에러
오늘은 이전에 만든 todoList 프로젝트에 JWT 로그인 기능을 추가하려고 해봤다. 아직 이해가 부족한 상태에서 강의에서 본 코드를 긁어와 사용하다보니 다양한 에러를 마주쳤고, 그 에러를 해결하
tangpoo.tistory.com
위 글에서 겪었던 에러 해결을 이어서 시도했다.
내배캠의 튜터님과 한참을 고민한 끝에 임시적인 해결책을 찾았다.
일단 로직적인 문제는 찾지 못했다.
return getAuthenticationManager().authenticate( new UsernamePasswordAuthenticationToken( requestDto.getUsername(), requestDto.getPassword(), null ) );
아이디, 비밀번호를 검증하는 위 코드가 실행될 때 사용되는 클라이언트의 정보와, DB에서 꺼내온 정보 모두 log를 통해
예상한 값임을 검증했다.
즉, 옳은 정보를 입력했음에도 View 페이지를 가지고 있는 프로젝트는 정상적으로 실행되고,
그렇지 않은 서버는 실패한다.
동일한 버전을 사용했으니, 남은 차이점이라고는 View 페이지의 여부 밖에 없어서 이것을 원인으로 꼽았다.
여기서 의심되는 것으로 Encoding 방법을 떠올렸다.
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
WebSecurityConfig 코드의 일부로, Encoding 방식을 BCryptPasswordEncoder 로 설정했다.
그렇다면 AuthenticationManager가 클라이언트가 입력한 비밀번호를 해쉬하는 방법도 BCryptPasswordEncoder 가 맞을까? 라는 의심이 들었지만, 따로 설정하지 않았다면 BCryptPasswordEncoder 로 해쉬하여 검증한다고 한다.
임시적인 해결책으로는 UserDetails 와 UserDetailsService 가 가진 검증 메서드를 Override 하여 검증 결과를
항상 true 로 만드는 것이다.
명백하게 좋은 해결법은 아니지만, 시간을 너무 오래 잡아먹었고,
디펜던시 과정에서 에러가 난 것이라면 어쩔 수 없다고 생각했다.
캠프 동료분의 도움으로 왜 그것이 에러가 났고, 왜 위 방법으로 해결이 됐는지 알게 되었다.
UserDetails를 쭉 파고들어가다 보면 DefaultPreAuthenticationChecks 의 check 함수가 사용된다.
private class DefaultPreAuthenticationChecks implements UserDetailsChecker { private DefaultPreAuthenticationChecks() { } public void check(UserDetails user) { if (!user.isAccountNonLocked()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account is locked"); throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked")); } else if (!user.isEnabled()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account is disabled"); throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled")); } else if (!user.isAccountNonExpired()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account has expired"); throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired")); } } }
이 함수는 false인 경우 무조건적으로 에러를 throw 하도록 만들어져 있다.
public class UserDetailsImpl implements UserDetails { private final User user; public UserDetailsImpl(User user) { this.user = user; } public User getUser() { return user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { UserRoleEnum role = user.getRole(); String authority = role.getAuthority(); SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority); Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(simpleGrantedAuthority); return authorities; } @Override public String getPassword(){ return user.getPassword(); } @Override public String getUsername(){ return user.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; } }
처음 UserDetails 의 메서드를 구현한다면 모든 메서드가 return false 로 설정되어 있다.
이 메서드는 따로 조건을 설정할 것이 아니라면 반드시 true 로 바꿔 주어야 에러가 터지지 않는 것이다.
이번엔 체험한다는 마인드로 Spring Security를 사용하고 있지만... 정말 주니어에 입장에서 사용할 프레임워크가 아니라는 생각이 든다.
'개발 일지' 카테고리의 다른 글
2024-02-20 테스트 코드의 범위 (0) 2024.02.20 2024-02-02 Auditing 기능, Header 방식으로 토큰 사용 (0) 2024.02.03 2024-01-31 JWT 로그인 검증 에러 (0) 2024.01.31 [팀 프로젝트] 팀 소개 사이트 회고 (3) 2023.12.26 [팀 프로젝트] 동물 소개 사이트 회고 (2) 2023.12.26