지난번은 간단한 스프링 시큐리티의 동작과정을 알아보았다. 이번시간은 동작과정에서 로그인을 확장해서 자신의 프로젝트에 대입하는 과정을 알아보도록하자
UserDetailsService
: 사용자 조회(R)
UserDetailsManager
: 사용자 CUD
PasswordEncoder
: 비밀번호 암호화
스프링 시큐리티의 인증/인가를 요약하자면 filter를 거친 다음 AuthenticationManager
가 알맞은 Provider
를 선택한다.UserDetailsService
와 PasswordEncoder
를 이용한다.
암호화 걍 안해도되는거아님? 하지만 스프링 시큐리티는 암호화를 권장한다. 앵간하면 쓰도록하자 😅
이 외에도 필터와 권한설정에 대한 확장방법도 매우매우매우많다. 대부분 스프링 시큐리티가 디폴트로 처리해주는건데 여기서는 로그인 방법에 대해 알아보는걸 주로 하자..!
이제 본격적으로 구현하기위해 UserDetailsService
의 loadUserByUsername()
을 커스텀해서 조회 시 UserDetails가 반환되도록 코드를 작성하면된다.
UserDetailsManager << 이건 안씀?
User는 정보가 부족하기때문에(닉네임, 주소, 나이 등등)
UserDetailsManager
보다 그냥 엔티티하나를 만들어쓴다. 고로 조회기능을 사용하기위해UserDetailsService
를 implement해주면된다.
@EnableWebSecurity
@Configuration
public class WebSecurityConfig {
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/members/login", "/").permitAll() //정의된 주소에 접근허용
// .anyRequest().authenticated() // 모든 url에 접근권한이 필요함
)
.csrf(AbstractHttpConfigurer::disable) // csrf 토큰 사용 안함
.formLogin(formLogin ->
formLogin
.loginPage("/members/login") //로그인 페이지 경로
.usernameParameter("username") //username 파라미터 명(폼에서 일치해야함)
.passwordParameter("password") //password 파라미터 명(폼에서 일치해야함)
.defaultSuccessUrl("/", true) //로그인 성공 시 Url
)
.logout((logout)-> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) //로그아웃 요청 경로
.logoutSuccessUrl("/") // 로그아웃 성공 시 Url
.invalidateHttpSession(true) //로그아웃 성공 시 모든 세션 삭제
)
;
return http.build();
}
}
스프링에서는 어떠한 사전정의를 하기위해 작성하는 클래스는 보통 @Configuration
을 선언한 곳에서 작성한다. 고로 webconfig에 대한 설정을 해주면된다. 여기서 주로 봐야할건
authorizeRequests
: 사용자의 요청을 인증하고 권한을 부여
csrf
: Cross-Site Request Forgery(CSRF) 공격을 방지하는 설정이지만 로컬스토리지를 사용하고있어서 껐음
formLogin
: 폼 기반 인증 활성화
logout
: 로그아웃 구성
이 네가지로 Spring Security를 설정해주었다. 이제 로그인기능인 조회를 할 수 있어야한다.
@RequiredArgsConstructor
@Service
public class MemberSecurityServiceImpl implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(member.getRole().equals(ADMIN) ? "ADMIN" : "USER"));
return new User(member.getUsername(), member.getPassword(), authorities);
}
}
만약 처음 로그인 구현을 무작정 따라해보면 이러한 의문이 들 수 있다.
"난 딱히 경로를 지정한 곳이 없는데 어떻게 알아서 로그인처리를 해주는거지?" 라는 생각이 들 수있는데 대부분 이런 의구심이 들때는 해당 기술에 대한 동작과정을 보는걸 추천한다. 문제점은 아마 기존에 있던 기능을 확장하는 방식으로 사용되어서 자동처리되기때문에 과정을 이해하지못한다면 알수없기때문이다!
스프링 시큐리티는 로그인 요청이 들어오면 UserDetailsService
의 loadUserByUsername()
을 자동으로 호출한다. 우리는 따로 DB를 사용하고있기때문에 DB에서 조회하게끔 메서드를 커스텀해야하는것이다.
UserDetails
는 인증여부를 확인하는 사용자정보 인터페이스인데 User
는 UserDetails
를 구현한 객체이다.
스프링 시큐리티를 처음배웠을 땐 되게어렵다.. 였고 배우다보니 어 쉽네? 였고 상세하게 배우다보니 이걸 언제 다 알아가냐.. 라는 생각이다. 다 배울생각보다는 과정에 대해 알아가고 기능구현시에 확장이 필요하다면 알아가는게 좋을거같다!