Spring Security 에서 로그인을 구현하는 하려고 한다.
내가 가장 중요하게 생각 하는 부분은 "DB 에 있는 회원으로 로그인" 되어야 한다는 것이다.
Spring Security 는 로그인 처리에 대한 로직을 사용자가 직접 전통적인 방식으로 구현하지 않고, 이관 받아 처리하기 때문에, 어느 정보로 로그인 할 것인가? 에 대한 답을 주어야 한다.
0. 로그인 사전준비
[Member Entity]
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseEntity{
/**
* member_id(PK)
* username
* password
* email
* phone
* userRole
*/
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private String password;
private String email;
private String phone;
private UserRole userRole = UserRole.USER;
@OneToMany(mappedBy = "member")
private List<Task> tasks = new ArrayList<>();
}
이와 같이 정의된 Member Entity 로 로그인 할 것이다.
[Test Data 주입]
[DB에 저장된 member]
정상적으로 DB에 저장된 것을 확인 할 수 있다.
[로그인 화면]
이와 같은 로그인 화면에
Username : member1
password : 12345678
Username : member2
password : 12345678
위 정보로 로그인이 가능해야 한다.
자, 사전 준비는 끝났다. 이제 DB에 저장된 정보로 로그인이 가능하도록 해보자.
1. Spring Security가 이해하도록 번역하기
"사용자의 인증 정보를 데이터베이스에서 조회하여 스프링 시큐리티가 이해할 수 있는 형태로 변환하는 역할" 을 하는 클래스를 구축해야 한다.
여기서 DB에서 조회하는 데이터는 " DB 에 저장된 회원(Member)" 정보 이다.
CustomUserDetailsService.class 를 구축해보자.
[ CustomUserDetailsService.class ]
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired private MemberRepository memberRepository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with name: " + username));
return new User(member.getUsername(), member.getPassword(), getAuthorities(member));
}
private Collection<? extends GrantedAuthority> getAuthorities(Member member) {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + member.getUserRole().name()));
return authorities;
}
}
loadUserByUsername 메서드는 사용자의 이름(username)을 파라미터로 받아 Spring Security 의 User 클래스의 인스턴스를 생성하여 반환 한다. 이 객체는 사용자 이름(username), 비밀번호(password), 권한 목록 을 포함한다.
getAuthorities 메서드는 사용자(member) 를 파라미터로 받아 해당 사용자가 가지고 있는 권한 목록을 반환한다.
권한 명 앞에 "ROLE_" 을 붙이는데, 이는 Spring Security 가 일반적으로 사용하는 권한 관리 방식을 따른 것이다.
이를 통해 Spring Security 가 DB 에 있는 Member 정보를 이해할 수 있는 형태로 변환 하였다.
2. 로그인 방식 설정하기
로그인 방식을 설정해야 한다. 여기서 말하는 로그인 방식은 방금 구축한 CustomUserDetailsService.class 를 통해,
"DB 에 저장된 Member 데이터로 로그인 할꺼야" 라는 것을 Spring Security 에게 알려 주는 것이다.
@Autowired private CustomUserDetailsService customUserDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests
.requestMatchers("/", "/members/login", "/members/join").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) ->
form
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/members/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/", true)
.permitAll()
)
.userDetailsService(customUserDetailsService)
.logout(logout ->
logout
.logoutUrl("/logout") //로그아웃 처리 URL
.logoutSuccessUrl("/") //로그아웃 성공 후 리다이렉트 할 URL
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
)
.csrf(csrf ->
csrf
.ignoringRequestMatchers("/api/**") // /api/** 경로에 대한 CSRF 보호를 비활성화
)
;
return http.build();
}
.formLogin 메서드 바로 뒤에 있는
.userDetailsService 메서드에 customUserDetailsService 를 입력하여, 로그인 방식을 설정 한다.
로그인에 성공한 후 "/" 로 리다이렉트 된다.
※ filterChain 에 대한 자세한 설명은 다음 글을 통해 정리하겠다.
3. 로그인 해보기
[username : member1 , password : 12345678 로그인]
로그인에 성공한 모습을 볼 수 있다.
[username : member2 , password : 12345678 로그인]
로그인에 성공한 모습을 볼 수 있다.
[username : member777 , password : 12345678 로그인]
로그인에 실패한 모습을 볼 수 있다. (/members/login?error)