오늘은 스프링 시큐리티 사용 시에 기본적으로 크게 어떤 과정을 거치면서 동작하는지에 대해서 공부했다. 처음 시큐리티에 관해 배울 때는 필터가 너무 많고 복잡해서 정말 배우기 어려울 것이라고 생각했는데 일단은 필터 자체에 대해서는 신경쓰지말고 큰 구조만 보는 식으로 공부했다.
스프링 시큐리티는 다음과 같은 방식으로 진행된다.
1. 서버로 요청함.
2. 인증필터(AuthenticationFilter)가 해당 요청을 가로채 필터를 거치게 함.
3. 인증관리자(AuthenticationManger)로 넘김, 인증관리자는 인증공급자를 이용해 인증을 함.
4. 인증공급자(AuthenticationProvider)에서는 UserDetailsService, PasswordEncorder를 통해 실제 회원과 정보가 일치하는지 확인함.
5. 다시 인증필터까지 거슬러 올라감. 인증필터에서 SecurityContext로 전달하여 해당 정보를 기억하도록함.
(위의 모든 과정을 거치는 것은 아니고 Filter 등 여러 요인에 따라 과정에 차이가 있을 수 있다.)
이 중에서 UserDetailsService는 유저를 읽는 기능밖에 하지 못한다.

실제로 직접 UserDetailsService를 살펴보면 구현할 메서드가 하나밖에 없다.
생성, 수정, 삭제 등을 하기위해서는 UserDetailsManager를 따로 구현하여 사용하면 된다.

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public Long save(UserRequestDto userDto) throws DataIntegrityViolationException {
if (userRepository.findByEmail(userDto.getEmail()) != null) {
throw new DataIntegrityViolationException("중복 아이디 발견");
}
userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword()));
return userRepository.save(userDto.toEntity()).getId();
}
public boolean checkIsDuplicatedUserEmail(String Email) {
if (userRepository.findByEmail(Email) != null) {
return true;
}
else return false;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
SecurityUser securityUser = new SecurityUser(user);
if (securityUser == null) {
throw new UsernameNotFoundException("해당 유저를 찾을 수 없습니다.");
}
return securityUser;
}
}
UserService에서 UserDetailsService를 구현하여 사용했다. 맨 밑을 보면 loadUserByUsername() 메서드를 오버라이딩한 것을 볼 수 있다. 최종적으로 반환은 UserDetails라는 것으로 해야하는데 해당 인터페이스는 getUsername()이나 getPassword(), isAccountNonExpired() 등 유저 관련 여러 메서드를 구현하도록 정의되어 있다.
@RequiredArgsConstructor
public class SecurityUser implements UserDetails {
private final User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("user"));
}
// 사용자 아이디
@Override
public String getUsername() {
return user.getEmail();
}
// 사용자 비밀번호
@Override
public String getPassword() {
return user.getPassword();
}
// 계정 만료 여부
@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를 구현하였다. Entity 클래스와 함께 작성하지 않고 따로 포함시켜 사용한 이유는 유지보수 때문이다. 위의 SecurityUser 클래스는 스프링 시큐리티에서 인증을 위해 사용하는 용도이고 아래의 User 클래스는 단순 JPA 관련 기능만 수행하도록 한 것이다. 단일 책임의 원칙과 관련이 있다.
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
@Getter
@Entity
@Table(name = "Users")
public class User extends BaseDateTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "email", length = 50, nullable = false)
private String email;
@Column(name = "password", nullable = false)
private String password;
@Column(name = "user_name", length = 30, nullable = false)
private String userName;
@Column(name = "birthday", length = 9, nullable = true) // 양력(0), 음력(1) + xxxx.xx.xx
private String birthday;
@Column(name = "nickname", length = 20, nullable = false)
private String nickname;
@Column(name = "phone", length = 15, nullable = false)
private String phone;
@Column(name = "profile_image")
private String profileImage;
@Column(name = "status", nullable = false)
private int status;
}
추가적으로 직접 어떤 클래스에서 UserDetailsService의 메서드를 구현하는 위의 방식도 있지만 직접 Bean으로 등록하여 사용하는 방법도 있다. 이 경우에는 해당 메서드에서 UserDetailsService를 반환하도록 하면 된다.
그리고 직접 UserDetailsService를 임의로 재정의하는 경우에는 PasswordEndcoder도 재정의해야 한다.
'공부 > Spring Security' 카테고리의 다른 글
| [Spring Security] CSRF 설정하기 (0) | 2024.01.10 |
|---|---|
| [Spring Security] 원하는 Filter 만들어서 사용하기 (0) | 2024.01.09 |
| [Spring Security] mvcMatchers, antMatchers, regexMatchers 대신 requestMatchers (0) | 2024.01.08 |
| [Spring Security] AuthenticationProvider 직접 구현하기 (0) | 2024.01.04 |
| [Spring Security] HTTP Basic 테스트 (0) | 2024.01.02 |