인증 구현

  • 요청하는 엔티티가 인증되면

    • 권한 부여에 이용할 수 있고

    • 세부 정보가 SecurityContext에 저장된다.

  • 요청하는 엔티티가 인증되지 않으면

    • 권한 부여 프로세스에 위임하지 않고 요청을 거부한다.

    • 일반적으로 401 권한 없음 응답이 반환된다.

5.1. AuthenticationProvider의 이해

  • 인증은 암호 뿐 아니라 sms로 코드를 받거나 특정 키를 제공해야 하는 시나리오가 있을 수 있다.

  • AuthenticationProvider로 맞춤형 인증 로직을 정의할 수 있다.

5.1.1. 인증 프로세스 중 요청 나타내기

  • Authentication

    • 인증 프로세스의 필수 인터페이스

    • Principal을 확장한다.

    • 인증 요청 이벤트를 의미

    • 접근 요청한 엔티티의 세부 정보를 담는다.

  • Principal

    • 접근을 요청하는 사용자

    • Authentication이 확장한 덕분에 시큐리티로 더 쉽게 마이그레이션 할 수 있는 유연함을 제공한다.

Authentication의 메서드는 다음과 같다.

  • isAuthenticated()

    • 인증 프로세스가 끝났으면 true, 진행 중이면 false를 반환한다.

  • getCredentials()

    • 인증 프로세스에 사용된 암호나 비밀을 반환한다.

  • getAuthorities()

    • 인증된 요청에 허가된 권한의 컬렉션을 반환한다.

5.1.2. 맞춤형 인증 논리 구현

  • AuthenticationProvider

    • authenticate()에 인증 로직 처리

      • 실패하면 AuthenticationException을 던진다.

      • 지원되지 않는 인증 객체를 받으면 null을 반환한다.

      • 인증 후에는 암호 등을 제거한 세부 정보 Authentication을 반환한다.

    • supports()로 제공된 형식을 지원하는지 확인한다.

    • UserDetailsService에 사용자를 찾는 책임을 위임한다.

    • PasswordEncoder로 암호를 관리한다.

5.1.3. 맞춤형 인증 논리 적용


@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (passwordEncoder.matches(password, userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(
                    username,
                    password,
                    userDetails.getAuthorities()
            );
        } else {
            throw new BadCredentialsException("Something went wrong!");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
  • @Component

    • 스프링에서 관리하는 컨텍스트에 포함되도록 한다.

  • UsernamePasswordAuthenticationToken

    • 사용자 이름과 암호를 이용하는 표준 인증 요청을 지원한다.

  • UserDetailsService

    • userDetails를 가져오기 위해 사용한다.


@Configuration
public class WebAuthorizationConfig {

    @Autowired
    private CustomAuthenticationProvider authenticationProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .authenticationProvider(authenticationProvider);
        return http.build();
    }
}
  • 만든 프로바이더를 연결해준다.

5.2. SecurityContext 이용

  • 인증 프로세스가 완료된 후에도 사용자의 이름이나 권한을 참조하고 싶을 때 사용한다.

  • Authentication 객체를 저장하는 인스턴스를 보안 컨텍스트라고 한다.

  • MODE_THREADLOCAL

    • 시큐리티의 기본 전략

    • 각 스레드가 각자의 세부 정보를 저장한다.

  • MODE_INHERITABLETHREADLOCAL

    • 새 스레드가 보안 컨텍스트를 상속하게 한다.

  • MODE_GLOBAL

    • 모든 스레드가 같은 보안 컨텍스트 인스턴스를 보게 한다.

5.2.1. 보안 컨텍스트를 위한 보유 전략 이용

  • MODE_THREADLOCAL는 기본 전략이라 설정하지 않아도 된다.

  • 파라미터로 Authentication을 이용해 정보를 얻을 수 있다.

5.2.2. 비동기 호출을 위한 보유 전략 이용

  • MODE_THREADLOCAL는 @Async로 새로운 스레드를 만들면 세부 정보가 전달되지 않는다.

  • @EnableAsync 같은 애너테이션은 책임을 분리해 @Configuration과 함께 두자.

  • MODE_INHERITABLETHREADLOCAL 전략으로 새 스레드에 세부 정보를 복사할 수 있다.

  • 단, 코드에서 자체적으로 만든 스레드에는 적용되지 않는다.

5.2.3. 독립형 애플리케이션을 위한 보유 전략 이용

  • 독립형 애플리케이션에서는 MODE_GLOBAL이 좋은 선택일 수 있다.

5.2.4. DelegatingSecurityContextRunnable로 보안 컨텍스트 전달

  • 코드가 새 스레드를 시작해서 스프링이 관리하지 않는 자체 관리 스레드에는 보안 컨텍스트를 전파할 수 없다.

  • Runnable을 DelegatingSecurityContextRunnable로 데코레이트 해서 사용할 수 있다.

  • 반환값이 있는 작업에는 DelegatingSecurityContextCallable을 사용할 수 있다.

import java.util.concurrent.Executors;

public String ciao() {
    Callable<String> task = () -> {
        SecurityContext context = SecurityContextHolder.getContext();
        return context.getAuthentication.getName();
    };

    ExecutorService e = Executors.newCachedThreadPool();

    try {
        var contextTask = new DelegatingSecurityContextCallable<>(task);
        return e.submit(contextTask.get());
    } finally {
        e.shutdown();
    }
}

5.2.5. DelegatingSecurityContextExecutorService로 보안 컨텍스트 전달

  • 스레드 풀에서 보안 컨텍스트를 관리하도록 전파할 수도 있다.

  • ExecutorService를 DelegatingSecurityContextExecutorService로 데코레이트 한다.

Last updated

Was this helpful?