사용자의 크리덴셜(Credential)이란
해당 사용자를 증명하기 위한 구체적인 수단을 의미합니다. 일반적으로는 사용자의 패스워드가 크리덴셜에 해당
doFilter()
라는 메서드를 구현해야 하며, doFilter()
doFilter()
라는 메서드를 구현해야 하며, doFilter()
서블릿 필터와 연결되는 Spring Security만의 필터를 ApplicationContext에 Bean으로 등록한 후에 이 Bean들을 이용해서 보안과 관련된 여러가지 작업들을 처리한다.
DelegatingFilterProxy
와FilterChainProxy
클래스는 Filter 인터페이스를 구현하기 때문에 서블릿 필터로써의 역할을 합니다.
@Order
애너테이션을 추가하거나 Orderd
인터페이스를 구현해서 순서를 지정FilterRegistrationBean
을 이용해 Filter의 순서를 지정가능/api/**
패턴의 Filter Chain이 있고, /api/message
URL 요청이 전송하는 경우/api/**
패턴과 제일 먼저 매칭되므로, 디폴트 패턴인 /**
도 일치하지만 가장 먼저 매칭되는 /api/**
패턴과 일치하는 Filter Chain만 실행합니다.public class FirstFilter implements Filter {
// (1) 초기화 작업
public void init(FilterConfig filterConfig) throws ServletException {
}
// (2)
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// (2-1) 이 곳에서 request(ServletRequest)를 이용해 다음 Filter로 넘어가기 전처리 작업을 수행한다.
// 해당 Filter가 처리하는 실질적인 로직을 구현
chain.doFilter(request, response);
// (2-3) 이 곳에서 response(ServletResponse)를 이용해 response에 대한 후처리 작업을 할 수 있다.
}
// (3)
public void destroy() {
// (5) Filter가 사용한 자원을 반납하는 처리
}
}
Servlet Filter는 FilterRegistrationBean의 생성자로 Filter 인터페이스의 구현 객체를 넘겨주는 형태로 등록가능
- FilterRegistrationBean의
setOrder()
메서드로 Filter 순서를 지정할 수 있다. 적은숫자일수록 먼저 실행
PasswordEncoder
구현 객체를 생성해주는 컴포넌트PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
{id}encodedPassword
UsernamePasswordAuthenticationFilter
이다.UsernamePasswordAuthenticationToken
은 Authentication
인터페이스를 구현한 구현 클래스이며, 여기서의 Authentication
은 아직 인증이 되지 않은 Authentication
이다.AuthenticationManager
는 인증 처리를 총괄하는 매니저 역할을 하는 인터페이스이고, AuthenticationManager
를 구현한 구현 클래스가 ProviderManager
UserDetails
는 사용자의 자격을 증명해주는 크리덴셜(Credential)
즉, 데이터베이스 등의 저장소에 저장된 사용자의 Username과 Password, 그리고 사용자의 권한 정보를 포함하고 있는 컴포넌트UserDetails
load하여 제공UsernamePasswordAuthenticationFilter
가 생성하는 Authentication은 인증을 위해 필요한 사용자의 로그인 정보를 가지고 있지만, AuthenticationProvider
가 생성한 Authentication은 인증에 성공한 사용자의 정보(Principal, Credential, GrantedAuthorities)를 가지고 있다.SecurityContextHolder
를 이용해 SecurityContext
에 인증된 Authentication을 저장SecurityContext
는 다시 HttpSession에 저장되어 사용자의 인증 상태를 유지doFilter()
메서드가 없음 → 상위클래스가doFilter()
메서드 포함AntPathRequestMatcher
: 클라이언트의 URL에 매치되는 매처attemptAuthentication()
메서드 :클라이언트에서 전달한 username과 password 정보를 이용해 인증을 시도하는 메서드doFilter()
메서드에서 호출됨doFilter()
이다.UsernamePasswordAuthenticationToken
을 생성AuthenticationManager
의 authenticate()
메서드를 호출해 인증 처리를 위임AuthenticationFailureHandler
를 호출unauthenticated()
메서드는 인증에 필요한 용도의 UsernamePasswordAuthenticationToken 객체를 생성하고,authenticated()
메서드는 인증에 성공한 이후 SecurityContext에 저장될 UsernamePasswordAuthenticationToken 객체를 생성ProviderManager
가 해당 Credentials를 삭제AuthenticationProvider
에 의해 부여된 사용자의 접근 권한 목록GrantedAuthority
인터페이스의 구현 클래스는 SimpleGrantedAuthority
이다.List<AuthenticationProvider>
객체를 DI 받는다DaoAuthenticationProvider
AbstractUserDetailsAuthenticationProvider
이고, DaoAuthenticationProvider는 AbstractUserDetailsAuthenticationProvider
를 상속한 확장 클래스AbstractUserDetailsAuthenticationProvider
추상 클래스의 authenticate()
DaoAuthenticationProvider
는 UserDetailsService로부터 전달 받은 UserDetailsadditionalAuthenticationChecks()
메서드에서 PasswordEncoder를 이용해 사용자의 패스워드를 검증DaoAuthenticationProvider
와 AbstractUserDetailsAuthenticationProvider
가 호출되는 순서AbstractUserDetailsAuthenticationProvider
의 authenticated() 메서드 호출DaoAuthenticationProvider
의 retrieveUser() 메서드 호출DaoAuthenticationProvider
의 additionalAuthenticationChecks() 메서드 호출DaoAuthenticationProvider
의 createSuccessAuthentication() 메서드 호출AbstractUserDetailsAuthenticationProvider
의 createSuccessAuthentication() 메서드 호출ProviderManager
에게 리턴크리덴셜(Credential)
SecurityContext를 관리하는 역할
SecurityContextHolder
를 통해 인증된 Authentication을 SecurityContext에 설정할 수 있고,
인증된 Authentication 객체에 접근가능
SecurityContextHolder 기본 전략은 ThreadLocalSecurityContextHolderStrategy
getContext()
메서드를 통해 현재 실행 쓰레드에서 SecurityContext를 얻을 수 있다.
setContext()
메서드는 현재 실행 쓰레드에 SecurityContext를 연결
ThreadLocal : 쓰레드 간에 공유되지 않는 쓰레드 고유의 로컬 변수 같은 영역
AuthorizationManager
에게 전달check()
메서드를 호출해 적절한 권한 부여 여부를 체크AuthorizationManager
를 구현하는 구현체RequestMatcher
를 통해 매치되는 AuthorizationManager
구현 클래스에게 위임만 한다AuthorizationManager
구현 클래스가 사용자의 권한을 체크AccessDeniedException
이 throw되고 ExceptionTranslationFilterAccessDeniedException
을 처리check()
메서드의 내부에서 루프를 돌면서 RequestMatcherEntry
정보를 얻은 후RequestMatcher
객체를 얻는다.MatchResult.isMatch()
가 true이면 AuthorizationManager
객체를 얻은 뒤, 사용자의 권한을 체크RequestMatcher
는 SecurityConfiguration에서 .antMatchers("/orders/**").hasRole("ADMIN")
와 같은 메서드 체인 정보를 기반으로 생성된다표현식 | 설명 |
---|---|
hasRole(Stirng role) | - 현재 보안 주체(principal)가 지정된 역할을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다.- hasRole(’admin’)처럼 파라미터로 넘긴 role이 ROLE_ 로 시작하지 않으면 기본적으로 추가한다.(DefaultWebSecurityExpressionHandler의 defaultRolePrefix를 수정하면 커스텀할 수 있다.) |
hasAnyRole(String… roles) | - 현재 보안 주체가 지정한 역할 중 1개라도 가지고 있으면 true를 리턴한다.(문자열 리스트를 콤마로 구분해서 전달한다.)- ex) hasAnyRole(’admin’, ‘user’) |
hasAuthority(String authority) | - 현재 보안 주체가 지정한 권한을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다.- ex) hasAuthority(’read’) |
hasAnyAuthority(String… authorities) | - 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true를 리턴한다.- ex) hasAnyAuthority(’read’, ‘write’) |
principal | - 현재 사용자를 나타내는 principal 객체에 직접 접근할 수 있다. |
authentication | - SecurityContext로 조회할 수 있는 현재 Authentication 객체에 직접 접근할 수 있다. |
permitAll | - 항상 true로 평가한다. |
denyAll | - 항상 false로 평가한다. |
isAnonymous() | - 현재 보안 주체가 익명 사용자면 true를 리턴한다. |
isRememberMe() | - 현재 보안 주체가 remember-me 사용자면 true를 리턴한다. |
isAuthenticated() | - 사용자가 익명이 아닌 경우 true를 리턴한다. |
isFullyAuthenticated() | - 사용자가 익명 사용자나 remember-me 사용자가 아니면 true를 리턴한다. |
hasPermission(Object target, Object permission) | - 사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다.ex) hasPermission(domainObject, ‘read’) |
hasPermission(Object targetId, String targetType, Object permission) | - 사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다.ex) hasPermission(1, ‘com.example.domain.Message’, ‘read’) |