막상 인증과 인가를 짜보았는데 Spring Security로 진행하니까 정확한 과정을 모르는느낌을 받았다. 과정을 상세하게 알아볼까?
우선 내가 사용한 방식으로 순서를 대략적으로 설명하자면
이 과정을 코드와 함께 설명해볼까?
로그인폼에 입력하고 URL로 POST하는 과정
@GetMapping("/login")
public String login() {
return "loginForm";
}
엥? 컨트롤러에는 @GetMapping
밖에없는데? 라고할수있다. 실제 POST처리하는건
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().requestMatchers(
...
.and()
.formLogin()
.loginPage("/user/login")
.defaultSuccessUrl("/")
...
여기 formLogin()
의 loginPage()
부분에서 이루어졌다. url을 /user/login 으로 설정하여 해당 POST 요청을 가로챈다. 그다음 loadUserByUsername
메소드를 호출하게되는데 이것을 사용자에 맞게 정의하기위해서
public class UserSecurityService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
Optional<Member> _member = this.userRepository.findByusername(username);
if(_member.isEmpty()){
throw new UsernameNotFoundException("사용자를 찾을 수 없습니다.");
}
Member member = _member.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if("admin".equals(username)){
authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
} else {
authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
}
return new User(member.getUsername(), member.getPassword(), authorities);
}
}
UserDetailsService
의 loadUserByUsername
을 재정의하면된다.
UserDetails
: 사용자의 정보를 담는 인터페이스. ID, PW, 권한정보 등이 담겨있음
UserDetailsService
: UserDetails 객체를 제공하는 인터페이스
public class SecurityConfig {
...
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
근데 왜
@Bean
씀?Bean으로 등록함으로써 authenticationManager가 반환하는 객체를 다른 객체에서 사용하기위함
authenicationManager
빈을 통해 로그인을 검증하여 인증이 성공적으로 이루어진다면 Authentication
를 반환하여 토큰을 생성하고 인증이 처리됨
인증된 사용자 정보를 세션에 저장하는 부분은 spring security에서 자동적으로SecurityContextHolder
라는 클래스의 ThreadLocal 변수에 저장하여 처리한다. 이거 불러오려면 SecurityContextHolder.getContext().getAuthentication()
으로 불러오면됨 현 사용자 닉네임같은거 불러올때 유용할듯?
ThreadLocal
: java에서 각 스레드 내부에서만 사용되는 변수를 선언하기위한 클래스 이걸로 여러 스레드를 사용할 때 발생하는 동기화문제 해결함
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().requestMatchers(
...
.and()
.formLogin()
.loginPage("/user/login")
.defaultSuccessUrl("/")
...
로그인이 성공하면 defaultSuccessUrl("/")
으로 이동하고 끝!
스프링이 해주는거에 비하면 그저 커스텀으로 숟가락만 끼얹는수준? 😅 고맙다! 스프링!