사용자 아이디/비밀번호를 인증처리하는 과정으로 Spring Security에서는 AuthenticationManager
, AuthenticationProvider
가 있다.
Manager은 쉽게 말해서 공장 안에서 작업 처리를 지시하는 매니저라 생각하자.
Provider은 Manager가 시켜서 일하는 작업자라 생각하자.
이렇게 컨셉을 잡고, Spring Security를 차근차근 파헤쳐 보자!
Manager가 일을 시키기 위해선 Provider가 누군지 알아야 겠죠.
WebSecurityConfigure
에서 AuthenticationProvider
을 등록해주죠.
WebSecurityConfigure.java
@Autowired public void configureAuthentication(AuthenticationManagerBuilder builder, JwtAuthenticationProvider jwtAuthenticationProvider) { builder.authenticationProvider(jwtAuthenticationProvider); }
WebSecurityConfigure
에서 다음과 같이 커스텀한 JwtAuthenticationProvider
을 등록했습니다.
👉 AuthenticationManagerBuilder
스프링의 ApplictaionContext가 만들어지는 과정에서 생성되고, 클래스 내부 메소드가 실행되면서 ProviderManager 클래스를 생성하여 add한
JwtAuthenticationProvider
가 주입된다.
1번에서 WebSecurityConfigure
에서 AuthenticationManager
에 AuthenticationProvider
를 추가했다.
이제 AuthenticationManager authenticate()
메소드를 실행시키면 어떻게 동작하는지 보자!
ProviderManager.java
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { // ... private List<AuthenticationProvider> providers = Collections.emptyList(); // ... public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException | InternalAuthenticationServiceException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw e; } catch (AuthenticationException e) { lastException = e; } }
AuthenticationManager
은 interface이다.
ProviderManager
은 AuthenticationManager
interface의 구현체 클래스이다.
따라서, ProviderManager
클래스 안에 authenticate()
에서 WebSecurityConfigure
에서 등록했던 AuthenticationProvider
들을 getProviders()
로 받아와 for문을 실행하며
모두 일 시킨다.
바로 이 부분이 Manager가 등록된 Provider에게 일을 시키는 부분이다.
Authentication
authenticate() 메소드에서 파라메터로 받는 Authentication은 스프링 시큐리티의 핵심이라고 할 수 있다. 그래서 다음 포스트에 내용을 깊게 정리할 것이다.
그럼 이제 AuthenticationProvider
가 일해야 하는 부분을 커스텀하여 만들어 줘야 한다.
왜냐하면 AuthenticationProvider
도 interface이다.
이 interface를 구현하는 구현체 클래스를 사용하고자하는 목적에 따라 커스텀하면 된다.
난 이번에 JWT 토큰을 사용해서 인증하기 때문에 JwtAuthenticationProvider
클래스를 작성했다.
AuthenticationProvider
인터페이스는 기본적으로 2개 메소드를 Override받는다.
public Authentication authenticate(Athentication authentication)
public boolean supports(Class<?> authentication)
JwtAuthenticationProvider.java
public class JwtAuthenticationProvider implements AuthenticationProvider { // ... @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { try { // 로그인 인증 전 JwtAuthenticationToken authenticationToken = (JwtAuthenticationToken) authentication; String principal = (String) authenticationToken.getPrincipal(); String credential = authenticationToken.getCredentials(); AuthResponseDto authResponseDto = userService.login(new AuthRequestDto(principal, credential)); // 로그인 인증 후 JwtAuthenticationToken authenticated = new JwtAuthenticationToken(authResponseDto.getUser().getId(), null, AuthorityUtils.createAuthorityList("ROLE_USER")); authenticated.setDetails(authResponseDto); return authenticated; } catch(DoNotExistException e) { throw new UsernameNotFoundException(e.getMessage()); } catch(IllegalArgumentException e) { throw new BadCredentialsException(e.getMessage()); } catch(DataAccessException e) { throw new AuthenticationServiceException(e.getMessage()); } } @Override public boolean supports(Class<?> authentication) { return ClassUtils.isAssignable(JwtAuthenticationToken.class, authentication); } }
JwtAuthenticationToken
은 Authentication
의 구현체 클래스이므로 Authentication(인증 정보)이라고 생각해주쇼.
userService.login()
이후 Authentication은 인증이 되고, Details에 로그인 결과값(User 객체, jwtToken)을 담아 return하게 된다.
그리고 1번에서 Manager가 일 처리할 때 for문
안에 if문
에 supports
메소드 사용하는 것을 볼 수 있다. JwtAuthenticationToken
이 Authentication
인터페이스를 사용하는 클래스인지 분별하는 함수로 커스텀에 맞게 작성해주면 된다.
Manager가 일을 시키면 Provider가 일한다.
스프링 시큐리티는 추상화된 부분이 너무 많아 document읽는 것도 중요하지만 직접 소스를 까보며 코드를 작성하는 것이 좋다.
물론 시간이 너무 많이 걸리지만 한번 이해하고 이해하는 것에 그치지 않고 정리해두면 다음에 볼 때 더 생산성있게 코드를 짤 수 있을 것 같다.
(프로그래머스) 단순 CRUD는 그만! 웹 백엔드 시스템 구현 온라인 스터디(Java반) 강의를 수강하고 제가 이해한대로 정리했습니다. 문제가 될시 삭제하겠습니다!