SpringSecurity 로 로그인 구현

Legday_Dev·2023년 9월 9일
0

Spring

목록 보기
8/14
post-thumbnail

Spring Securiy 를 사용하면 따로 로그인 컨트롤러를 구현하지 않고 스프링 시큐리티에게 양도한다.
SecurityConfig 파일에 있는

@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception{
    //생략 ...
    http.authorizeRequests()
            .antMatchers("/","/user/**","/image/**","/subscribe/**", "/comment/**").authenticated()
            .loginPage("/auth/signin") //get
            .loginProcessingUrl("/auth/signin") //POST -> 스프링 시큐리티가 로그인 프로세스 진행
            .defaultSuccessUrl("/");
        return http.build();
}

코드에 따라 "/", "/user/**" 등으로 오는 요청들은 시큐리티가 가로채서 authenticated() 메서드를 실행하고 인증이 안된 사용자면 "/auth/signin" 로 보내서 로그인을 하게 만든다. 단, HTTP 메서드가 Get 방식일 때 사용하는 루틴이다.

POST 방식일 때 스프링 시큐리티 로직

  • Spring Security 를 사용하지 않는다면 개발자가 로그인 컨트롤러를 직접 짜야한다.

  • Spring Security 를 사용하면 .loginProcessingUrl("/auth/signin") 이 로직이 POST 방식일 때 실행이 된다.

  • 그리고 IoC 컨테이너에 있는 UserDetailsService 가 실제 로그인 기능을 실행한다.

  • 개발자가 따로 옵션이나 권한같은걸 주기 위해 UserDetailsService 를 implement 한 PrincipalDetailsService 를 만든다

    @Service
    public class PrincipalDetailsService implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            return null;
        }
    }
  • username 만 가져온 이유는 클라이언트가 username , password 를 서버에게 요청하면 시큐리티가 password 를 자체적으로 비교해주기 때문이다.

  • loadUserByUsername() 메서드는 정상적으로 return 이 되면 username 과 password로 UserDetails 타입으로 세션을 만들어준다.

  • loadUserByUseranem() 메서드는 UserDetails 타입이기 때문에 UserDetails 타입으로 리턴하면 되지만, UserDetails 는 인터페이스이기 때문에 익명함수를 생성하게 된다. 그래서 UserDetails 를 implement 한 PrincipalUserDetails 를 만들어준다.

  • PrincipalDetails 은 실제 로그인 로직이 있는 UserDetails 를 implement 했기 때문에 여러 로직을 Override(재정의) 됐다. 실제 서비스 목적에 따라 수정해주면 된다.

  • 중요한 것은 한개라도 false 가 있으면 안된다!!

    public class PrincipalDetails  implements UserDetails {
    
        private User user;
    
        public PrincipalDetails(User user){
            this.user=user;
        }
    
        // 권한 : 권한이 한개가 아닐수 있다.(3개 이상의 권한)
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() { //권한을 가져오는 메서드
    
            Collection<GrantedAuthority> collector = new ArrayList<>();
            collector.add(()->{return user.getRole();});
    
            return collector;
        }
    
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @Override
        public String getUsername() {
            return user.getUsername();
        }
    
        @Override
        public boolean isAccountNonExpired() { // 계정이 만료되었는지 ?
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() { // 계정이 잠겻니 ?
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() { // 비밀번호가 1년이 지낫는데 변경안한거 아니니 ?
            return true;
        }
    
        @Override
        public boolean isEnabled() { //활성화 되었는지 ?
            return true;
        }
    
        // 하나라도 false 면 로그인이 안된다.
    }
  • 다시 PrincipalDetailsService 로 돌아와서 위에 만든 PrincipalDetails 을 리턴해주면 된다.

    @RequiredArgsConstructor
    @Service
    public class PrincipalDetailsService implements UserDetailsService {
    
        private final UserRepository userRepository;
    
        // 1. 패스워드는 스프링시큐리티가 자체적으로 체크해준다.
        // 2. return 이 잘되면 자동으로 UserDetails 타입을 세션으로 만든다.
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User userEntity = userRepository.findByUsername(username);
    
            if(userEntity == null){
                return null;
            }else{
                return new PrincipalDetails(userEntity);
            }
        }
    }

    동작순서
    1) 클라이언트가 username,password 데이터를 /auth/signin URL 로 POST 요청을 한다.
    2) 서버를 감싸고 있는 시큐리티가 요청을 가로채서 username 은 PrincipalDetailsService 에 있는 loadUserByUsername()에게 넘겨준다.
    3) loadUserByUsername() 에서 인증이 성공하면 UserDetails 타입으로 세션에 저장해서 로그인 성공을 응답한다.

스프링 시큐리티 세션

보통 웹 브라우저 통신을 할때 세션을 저장하면 Key : Value 타입으로 저장된다. 하지만 스프링 시큐리티는 세션 안에있는 SecurityContextHolder 라는 곳에 Authentication 객체로 감싸서 저장한다 !!

  • 컨트롤러에서 session 을 가져와서 다룰려면 @AuthenticationPrincipal 어노테이션을 활용하면 된다.
@GetMapping("/user/{id}/update")
    public String update(@PathVariable int id,
                         @AuthenticationPrincipal PrincipalDetails principalDetails){
             System.out.println("세션 정보 : " + principalDetails.getUser());
        return "user/update";
    }
profile
백엔드개발자

0개의 댓글