4/10 Issue #17 스프링 시큐리티 설정

wook2·2023년 4월 11일
0

spring

목록 보기
8/8

스프링 시큐리티

API 서버에 대해 인증과 인가처리를 위해 스프링 시큐리티를 사용하기로 하였다.
현재 개발 도메인의 정책에서는 인가에 대해서는 논의가 없기 때문에 인증을 위주로 개발하였다.

인증에 대한 구현을 스프링 시큐리가 많이 제공하기 때문에,
어떤 방식을 사용할지, 어떻게 사용자를 가져올지등의 로직만 작성하면 된다.

  • 인증방식
    폼 기반 인증, HTTP 기본 인증, HTTP 다이제스트 인증, OAuth2 및 OpenID Connect와 같은 인증 방식이 있다.
    API 서버 구현이기 때문에 JWT토큰을 통한 인증을 채택하였다.

시큐리티 설정

스프링 부트를 이용하는 경우, SecurityFilterChain을 빈으로 등록하여 설정하면 된다.
설정은 다음과 같다.

  • http.csrf().disable(): CSRF 공격 방지를 비활성화한다.

  • http.httpBasic().disable(): HTTP 기본 인증을 비활성화한다.

  • http.authorizeHttpRequests(): HTTP 요청에 대한 인증 및 권한 부여 설정을 시작한다.
    -http.requestMatchers(HttpMethod.GET,"/email/authcode").permitAll(): GET 메서드로 /email/authcode 경로로 요청이 오면 인증 없이 허용한다.

  • http.anyRequest().authenticated(): 그 외 모든 요청은 인증을 요구한다.

  • http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class): JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 실행되도록 추가한다.

  • http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS): Stateless 세션을 사용하도록 설정한다. JWT 사용

  • PasswordEncoderFactories.createDelegatingPasswordEncoder()를 사용하면 여러 종류의 패스워드 인코딩 방식을 지원할 수 있습니다.

@RequiredArgsConstructor
@Configuration
public class SecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf().disable()
                .httpBasic().disable()
                .authorizeHttpRequests()
                .requestMatchers(HttpMethod.GET,"/email/authcode").permitAll()
                .requestMatchers(HttpMethod.POST,"/login","/signup").permitAll()
                .requestMatchers(HttpMethod.GET,"/ex").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .build();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}

UserDetailsService 인터페이스 구현

이 인터페이스를 구현하면 스프링 시큐리티에서 사용자 정보를 가져올 수 있다.
1. UserDetailsService는 로그인 시 사용자의 아이디를 이용하여 사용자 정보를 가져온다.
2. 이후 인증을 위해 입력된 비밀번호와 가져온 사용자 정보의 비밀번호를 비교하여 인증을 수행한다.
따라서 UserDetailsService를 구현함으로써 사용자 인증을 처리할 수 있다.

그렇다면, 구현해야 할 로직은 다음과 같다.

사용자의 아이디를 이용하여 사용자 정보를 어디서 가져올지?

사용자 정보를 가져오는 방식은 비즈니스마다 다 다르기 때문에,
이 로직을 loadUserByUsername에서 구현해주어야 한다.

그러면 다음과 같은 의문이 있다.
사용자를 가져오긴 했는데, 비즈니스마다 제각각 모양이 다 다르네.. ?
=> 그렇다면 통일된 양식으로 맞춰서 가져오도록 해야겠다.

이를 해결하기 위해 Security에선 UserDetails 인터페이스를 구현한 객체를 인증객체로 가지고 있다.
사용자 정보를 UserDetails 객체로 변환하여 인증 및 권한 부여 작업에 사용할 수 있다.

@RequiredArgsConstructor
@Service
public class CustomUserDetailsService implements UserDetailsService{

    private final UserAccountRepository userAccountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return  userAccountRepository.findById(username)
                .map(UserAccountDto::from)
                .map(UserAccountPrincipal::from)
                .orElseThrow(() -> new UsernameNotFoundException("해당 유저를 찾을 수 없습니다." + username));
    }
}
  1. UserAccountRepository를 통해 username에 해당하는 사용자 정보를 조회한다.
  2. UserAccountDto 객체로 변환한다.
  3. UserDetails 객체로 변환한다.
  4. 객체가 있다면 반환, 없다면 UsernameNotFoundException 던진다.
profile
꾸준히 공부하자

0개의 댓글