목차
- [이론] 스프링 시큐리티 1
- [이론] 스프링 시큐리티2
- [실습] 스프링 시큐리티 Form Login
- [추가] CustomAuthenticationProvider vs DaoAuthenticationProvider
- [이론] 스프링 시큐리티3
- [이론] 스프링 시큐리티4
- [추가] AuthorizeReqeusts vs AuthorizeHttpRequests
- [실습] 스프링 시큐리티 Json data Login 처리
- [실습] 스프링 시큐리티 JWT 설정
- [실습] 스프링 시큐리티 JWT 처리
- OAuth2
스프링 시큐리티 기본 동작 흐름도
이번에는 AuthenticationProvider, AuthenticationManger SecurityContext에 대해서 알아본다
AuthenticationProvider
Authentication: Principal 확장
인증 프로세스의 필수 인터페이스로 인증 요청 이벤트를 나타냄
요청한 엔티티의 세부정보를 담음
요청하는 사용자: Principal
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); // 권한 반환
Object getCredentials(); // 암호 반환
Object getDetails();
Object getPrincipal();
boolean isAuthenticated(); // 인증 종료 여부
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
AuthenticationProvider
- 인증 논리 담당, 요청을 허용할지 결정하는 조건 명령 발견
- AuthenticationManager 로부터 책임을 위임 받음
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
//인증 실패시 예외 투척
//인증 성공시 Authentication 객체 반환 - isAuthenticated: true
//인증 실패시 null 반환 - 형식은 Authentication 이지만 세부정보가 일치 하지 않음
boolean supports(Class<?> authentication);
//Authentication 객체로 제공된 형식이면 true
}
authenticate()
는 UserDetailsService, PassworEncoder를 가지고 해당 유저가 존해하는지 여부를 판별하도록 재정의 하면된다
@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final 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()
);
}
throw new BadCredentialsException("Something went wrong");
}
@Override
public boolean supports(Class<?> authentication) {
//사용자 이름과 암호를 이용하여 인증 요청을 나타냄
// Authentication 구현의 한 종류
return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
}
}
메소드 설명
- supports - 집 문을 열 수 있는 방법이 열쇠, 카드, 비밀번호, 지문 인지 확인
- authenticate - 집 문을 열 때 알맞은 정보로 열었는지 (ex) 지문이 인식이 되어있다, 비밀번호가 일치한다
AuthencicationManager
Filter로 부터 받은 요청을 Provider를 이용해 요청을 인증한다
재정의한 CustomAuthenticationProvider를 AuthencicationManager가 가질 수 있도록 설정을 해줘야한다
// 설정파일
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder.authenticationProvider(authenticationProvider);
return builder.build();
}
SecurityContext
- 인증 로직이 완료된 후 Authentication 객체를 저장 관리하는 역할
- SecurityContext에 접근하여 사용자 정보를 사용할 수 있다
public interface SecurityContext extends Serializable{
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
관리 전략
SecurityContextHolder 사용하여 관리자 역할
- MODE_THREADLOCAL: 각 스레드가 저장 가능, 일반적인 방법
- MODE_INHERITABLETHREADLOCAL: 비동기 메서드경우 스레드 복사하여 사용
- MODE_GLOBAL: 모든 스레드가 같은 컨텍스트 사용
HttpBasic, FormLogin
HttpBasic
특정 resource에 대한 접근을 요청할때 브라우저가 사용자에게 username과 password를 확인해 인가를 제한하는 방법
전체 과정
- Client는 Server에게 Resource를 요청한다.
- Client가 요청한 Resource를 이용하기 위해서는 인증이 필요하다. 따라서 Server는 Client에게 WWW-Authenticate Header를 통해 인증 필요성을 Client에게 전달한다. Basic 문자열을 통해 Basic 인증 과정이 필요하다는걸 Client에게 알린다.
- 인증 요청을 받은 Client는 ID:Password 문자열을 Base64로 Encoding한 String을 Authorize Header에 추가한뒤 다시 한번 Server에게 Resource를 요청한다.
- Encoding된 ID:Passowrd 문자열을 받은 Server는 자신이 Encoding한 값과 일치하는지 확인한 후 일치하면, 요청한 Resource를 전달한다.
FormLogin
사용자가 로그인 하지 않았으면 로그인 양식으로 redirect된다
http.formLogin()
사용
- defaultSuccessUrl 설정
- AuthenticationSuccessHandler 구현 -
.successHandler()
- AuthenticationFailureHandler 구현 -
.failureHandler()
Handler를 통해 추후 행동에 대해 명시할 수 있다
추가 코드 삽질내용
추가로 어떠한 과정으로 Spring Security에 AuthenticationManager, AuthenticationProvider, UserDetailsService
등록되는지 궁금하면 아래 코드를 확인해보세요
더보기
ProviderManager(AuthenticationManager) -> AuthenticationProvider -> UserDetailsService
ProviderManager생성시 Bean으로 등록된 UserDetailsService, PasswordEncoder를 가진 AuthenticationProvider 후 리스트로 가지고 있음
- 내가 Bean 등록한 AuthenticationProvider 자동 등록
class InitializeAuthenticationProviderManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) {
if (auth.isConfigured()) {
return;
}
AuthenticationProvider authenticationProvider =
getBeanOrNull(AuthenticationProvider.class);
if (authenticationProvider == null) {
return;
}
auth.authenticationProvider(authenticationProvider);
}
}
등록하는 과정에서 보면 AuthenticationProvider객체를 Bean 등록을 해두면 자동으로 사용할 수 있게 등록을 하는 것 같다
대신 AuthenticationProvider가 두개 이상일 경우 등록이 불가하다
- DaoAuthenticationProvider default 등록
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsPasswordService(passwordManager);
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
}
기본적으로 DaoAuthenticationProvider를 등록을 해주는데 이때 bean으로 등록 된 UserDetailsService와 PasswordEncoder를 찾아와 설정해준다
- AuthenticationProvider리스트를 가진 ProviderManager 생성
public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder> implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
@Override
public AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
return null;
}
ProviderManager providerManager = new ProviderManager(this.authenticationProviders, this.parentAuthenticationManager);
return providerManager;
}
}
- ProviderManager에서 해당 Authentication을 담당하는 AuthenticationProvider 선택 후 인증 로직 수행
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication); // 검증
if (result != null) {
copyDetails(authentication, result); break;
}
} catch (Exception ex) {
lastException = ex;
}
}
}
}
'공부기록 > Spring Security' 카테고리의 다른 글
[이론] 스프링 시큐리티4 (0) | 2022.12.28 |
---|---|
[이론] 스프링 시큐리티3 (0) | 2022.12.26 |
[추가]CustomAuthenticationProvider vs DaoAuthenticationProvider (0) | 2022.12.22 |
[실습] 스프링시큐리티 로그인처리 (2) | 2022.12.21 |
[이론] 스프링 시큐리티1 (1) | 2022.12.18 |