📝 BCryptPasswordEncoder
- 이번 시간에는 사용자가 회원가입을 할 때 작성한 비밀번호를 해쉬로 암호화한 뒤 데이터베이스에 저장하고 로그인 해보는 작업을 수행해보도록 하겠습니다.
- 우선 지난 시간에 구현한 SecurityConfig 클래스에 아래와 같은 함수를 추가해주었습니다.
- 위 함수는 BCryptPasswordEncoder 객체를 반환하며 @Bean 어노테이션을 메서드에 명시해주어 스프링 빈으로 등록함으로써, 해당 객체를 원할 때 가져다 쓸 수 있도록 하였습니다.
- 해당 객체는 암호화하기 위한 메소드를 가지고 있으며 우리는 이 객체를 사용할 것입니다.
- UserService 클래스를 열고 회원가입 함수에 다음과 같이 코드를 추가해줍니다.
- 다음으로 이전 시간에 작성하였던 SecurityConfig 클래스의 configure 함수를 다음과 같이 수정해주었습니다.
- csrf 토큰을 비활성화 한 이유는 우리가 회원가입을 하는 방식이 자바스크립트를 이용하는 방식이기 때문에 스프링 시큐리티에서 이를 공격으로 간주하고 막아버리기 때문입니다. 따라서 테스트를 위해서는 해당 기능을 비활성화 해주어야 합니다.
CSRF(Cross Site Request Forgery)란 웹 어플리케이션 취약점 중 하나로 인터넷 사용자(희생자)가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 만드는 공격입니다.
📝 Spring Security 로그인
- 이번에는 스프링 시큐리티를 사용해 로그인해보도록 하겠습니다.
- 우선 우리가 로그인 버튼을 <form> 태그 안에 집어넣어 주었기 때문에 로그인 버튼을 누를시 <form> 태그의 action 속성에 지정된 경로로 요청하게 되므로 action 속성에 값을 지정해주었습니다.
- 경로는 지정해주었지만 save 함수와 같이 따로 요청을 받는 함수를 구현해주지는 않을 것입니다. 그 이유는 스프링 시큐리티가 이를 가로채게 만들 것이기 때문입니다.
- 다음으로 SecurityConfig 클래스의 configure 함수를 다음과 같이 수정해주었습니다.
- loginProcessingUrl 함수는 매개변수로 지정된 경로로 로그인 요청이 오게 되면 스프링 시큐리티가 이를 가로채서 로그인을 대신 수행하도록 해줍니다.
- defaultSuccessUrl 함수는 로그인을 성공할 시 이동하게 될 경로를 설정합니다.
- 스프링 시큐리티가 로그인을 가로채서 대신 수행하기 위해서는 사용자의 아이디(username)와 비밀번호(password)를 가로채야 하는데, 이를 위해서는 UserDetails 타입의 객체가 필요합니다.
UserDetails란?
UserDetails란 Spring Security에서 사용자의 정보를 담는 인터페이스로써, 사용자의 정보를 불러오기 위해 구현해야 하는 인터페이스로, getUsername(), getPassword() 등과 같은 기본 오버라이드 메서드들을 제공하고 있습니다.
대부분의 경우 Spring Security의 기본 UserDetails만으로는 실무에서 필요한 정보를 모두 담을 수 없기 때문에 아래와 같이 직접 구현하여 사용하는 편입니다.
- 따라서 UserDetails 클래스를 상속받는 클래스를 생성하여 아래와 같이 구현해줍니다.
// 스프링 시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료가 되면
// UserDetails 타입의 오브젝트를 스프링 시큐리티 고유의 세션 저장소에 저장
public class PrincipalDetail implements UserDetails {
private User user;
public PrincipalDetail(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정이 만료되지 않았는지를 확인(true = 만료 안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않은지를 확인(true = 잠기지 않음)
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
// 비밀번호가 만료되지 않았는지를 확인(true = 만료 안됨)
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
// 계정 활성화(사용 가능)가 되어있는지를 확인(true = 활성화)
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
// 계정이 갖고 있는 권한 목록을 확인
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
collectors.add(() -> {
return "ROLE_" + user.getRole(); // ROLE_USER
});
return collectors;
}
}
- 해당 클래스의 객체는 스프링 시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료하게 되면, 스프링 시큐리티 고유의 세션 저장소에 저장되게 됩니다.
- 다음으로 스프링 시큐리티가 대신 로그인 할 시에 위에서 언급한대로 password를 가로채게 되는데, 해당 password가 어떤식으로 암호화 되어 회원가입 되었는지를 알아야, 같은 방식으로 암호화하여 DB에 있는 해쉬와 비교하여 로그인할 수 있으므로 SecurityConfig 클래스에 다음과 같이 필드와 메서드를 추가해주었습니다.
- 아직 PrincipalDetailService 클래스를 만들지 않았으므로 해당 이름으로 클래스를 생성해주어야 하는데, 그 전에 먼저 UserRepository 클래스에 다음과 같이 코드를 추가해줍니다.
- 위 함수는 DB에서 username을 통해 User 객체를 찾아서 반환해주는 역할을 수행합니다.
- 다음으로 PrincipalDetailService 클래스를 생성한 뒤 다음과 같이 구현해줍니다.
UserDetailsService란?
UserDetailsService란 Spring Security에서 사용자 정보를 가져오는 인터페이스로, 사용자 정보를 불러오기 위해서 구현해야하는 인터페이스 입니다.
기본 오버라이드 메서드는 위와 같이 loadUserByUsername() 메서드가 있으며, 이는 username을 기준으로 사용자 정보를 불러와 UserDetails 객체를 반환하는 역할을 수행합니다.
- 위에서 언급했듯이 스프링 시큐리티가 로그인 요청을 가로챌 시 username과 password 변수를 가로채게 되는데, password 처리는 스프링 시큐리티가 알아서 하기 때문에 우리는 사용자가 요청한 username이 DB에 있는지만 확인하여 알맞은 결과를 반환해주기만 하면 됩니다.
- 위처럼 할 경우 아이디를 정상적으로 찾게 되면 PrincipalDetail 객체, 즉 UserDetails 객체를 반환하게 되며 이때 스프링 시큐리티 세션에 유저 정보가 저장되게 됩니다.
- 이후 컨트롤러에서 세션을 찾기 위해서는 다음과 같은 방법을 사용할 수 있습니다.
- 이후 로그인을 테스트하여 잘 동작하는지 확인합니다.
💡 알게 된 점
- CSRF의 개념
- 스프링 시큐리티의 요소와 로그인 하는 과정
📌 References
'🚗 Backend Toy Project > 스프링 부트 게시판' 카테고리의 다른 글
[스프링부트 게시판] 19. 회원정보 수정 (0) | 2022.05.15 |
---|---|
[스프링부트 게시판] 18. 게시글(추가, 상세보기, 삭제, 수정, 페이징) (0) | 2022.05.14 |
[스프링부트 게시판] 16. 스프링 시큐리티 체험해보기 (0) | 2022.05.12 |
[스프링부트 게시판] 15. 로그인 기능 구현하기 (0) | 2022.05.10 |
[스프링부트 게시판] 14. 회원가입 기능 구현하기 (0) | 2022.05.07 |