1. Form Login
formLogin의 API 살펴보기
http.formLogin() 으로 작동시킬 수 있다. 그리고 chaining 방식으로 아래와 같이 상황에 따른 페이지 설정을 해줄 수 있다.
http.formLogin()
.loginPage(“/login.html") // 사용자 정의 로그인 페이지
.defaultSuccessUrl("/home) // 로그인 성공 후 이동 페이지
.failureUrl("/login.html?error=true“) // 로그인 실패 후 이동 페이지
.usernameParameter("username") // 아이디 파라미터명 설정
.passwordParameter(“password”) // 패스워드 파라미터명 설정
.loginProcessingUrl(“/login") // 로그인 Form Action Url
.successHandler(loginSuccessHandler()) // 로그인 성공 후 핸들러
.failureHandler(loginFailureHandler()) // 로그인 실패 후 핸들러
API 기능 확인
기본적으로 메서드의 이름대로 작동한다고 보면된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //요청에 대한 보안 설정을 시작함
.anyRequest().authenticated(); //어떤 요청이든 authentication(인가) 과정을 거치도록 한다.
http
.formLogin()
.loginPage("/loginPage")
.defaultSuccessUrl("/")
.failureUrl("/login")
.usernameParameter("userId")
.passwordParameter("passwd")
.loginProcessingUrl("/login_proc")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("authentication : " + authentication.getName());
response.sendRedirect("/");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("exception : " + exception.getMessage());
response.sendRedirect("/login");
}
})
.permitAll(); //로그인 페이지는 누구나 접근이 가능하도록 설정함
}
|
cs |
successHandler, failureHandler
그중 successHandler와 failureHandler는 인증에 성공, 실패했을 때 어떤 처리를 할 것인지를 정해줄 수 있다. 파라미터로 각각 AuthenticationSuccessHandler, AuthenticationFailureHandler 객체를 받고, 객체를 받겠다고 선언하면 intelliJ에서 자동으로 @Override가 필요한 메서드를 자동으로 추가해준다. 여기에 인증 성공과 실패 시 구현할 로직을 구현할 수 있다. 시험을 위해서 .loginPage() 메서드 부분을 주석처리한다. 이렇게 해야 아래와 같이 스프링부트에서 기본적으로 제공하는 로그인 페이지를 띄울 수 있다.
그러면 로그인 실패와 성공 시, 콘솔창에 아래와 같이 메시지가 출력되는 것을 확인할 수 있다.
usernameParameter, passwordParameter
이 항목들은 인증 시 입력받은 username과 password의 파라미터의 이름을 설정해주는 부분이다. 로그인 화면에서 개발자도구 부분을 확인해보면 아래와 같은 html 페이지로 구성된 것을 확인할 수 있는데, 여기서 각 input 요소의 name들이 작성한 코드에서 설정한대로 반영되어 있음을 확인할 수 있다. 나중에 로그인 상황별 페이지나 데이터를 맵핑해야하는 상황일 때, 이 파라미터의 이름이 변경될 수도 있다는 것을 인지하고 있어야 한다.
2. 인증 로직 파악해보기
1. 서블릿, MVC 복습
스프링 시큐리티는 위에서 보는 filter 부분들의 조합이라고 보면된다. 클라이언트의 요청이 WAS 서버에서 서블릿을 이용해서 처리되고 나면, 서블릿의 DelegationFilter부분에서 HttpServletRequest 객체가 전달되어 맨 처음 filter 부분으로 들어온다. 이 filter들에서 인증과정이 이루어진다.
기본적인 동작 방식은 "스프링 MVC 2편" 에서 학습한대로 여러 개의 filter 들이 chaining을 하면서 동작한다. '클라이언트 -> WAS -> 필터 -> 서블릿 -> 컨트롤러'의 순서로 정보가 전달되고 필터에서 doFilter 메서드를 실행하여 필터 체이닝이 일어나다가 더 이상 연결된 필터가 없으면 서블릿으로 요청 정보가 넘어간다. 스프링 시큐리티도 결국 filter들을 사용하는 방식으로 동작한다.
FilterChainProxy, doFilter
필터의 동작은 doFilter를 통해서 여러 필터들이 순차적으로 동작하는 방식이다. FilterChainProxy의 doFilter(ServletRequest, ServletResponse) 메서드를 확인해보면, currentPosition이라는 변수값과 함께 계속해서 필터들을 호출하는 구조임을 확인할 수 있다. 디버깅 화면에서도, FilterChainProxy에서 여러 필터들이 번호를 갖고 순차적으로 호출되는 것을 볼 수 있다.
너무 복잡하게 생각할 필요는 없다. 전체적으로는 이런 방식으로 작동한다는 것만 기억하고, 뒤쪽 아키텍처 분석 부분에서 정확히 어떤 방식으로 동작하는지 배우게 된다.
2. UsernamePasswordAuthenticationFilter
인증 로직의 기본 동작 원리는 아래와 같이 도식화 할 수 있다.
크게보면 UsernamePasswordAuthenticationFilter에서 username, password 정보를 가져오고 해당 사용자가 특정 자원(/login)에 접근할 권한이 있는지, username과 password가 메모리나 DB에 저장된 값과 일치하는지를 검사한다. Authorization과 Authentication을 하는 것이다. 실패 시에는 Exception을 던지고, 성공 시에는 username, password, authorities(사용자의 권한 정보) 등을 Authentication이라는 객체로 만들어서 SecurityContext라는 쓰레드의 저장소에 저장한다. 이 정도만 이해하고 넘어가도 된다.
아래 내용은 동작 원리를 파악하고자 혼자 공부했었던 내용이다. 나중에 아키텍처 부분에서 자세히 살펴볼 것이므로 깊이 알 필요는 없다. 대략 어떻게 흘러가는지 한 번 읽어보기만 하면 된다.
우선 usernamePasswordAuthenticationFilter의 부모인 AbstractAuthenticationProcessingFilter에서 다른 필터에서 호출된 doFilter 메서드가 실행된다. 여기에서 요청 정보(HttpServletRequest)를 바탕으로 인증이 필요한지 파악(requiresAuthentication)하고, 필요한 경우 attemptAuthentication을 호출한다. 다만 AbstractAuthenticationProcessingFilter의 attemptAuthentication 메서드는 abstract이다. 해당 메서드의 실제 구현체는 자식인 UsernamePasswordAuthenticationFilter에서 실행한다.
UsernamePasswordAuthenticationFilter를 보면 사용자의 요청정보(request)에서 username, password 값을 얻어오는 것을 알 수 있다. 그리고는 아래쪽 setUsernameParameter, setPasswordParameter에서 사용자가 입력한 username, password에 대한 파라미터명과 일치하는 정보가 없는지 검사하는 것을 확인할 수 있다.
추가로 요청의 Method가 POST인 것을 확인하는 부분도 볼 수 있는데, 스프링 시큐리티는 기본적으로 POST 메서드를 통해 요청하는 것을 원칙으로 하고 있다는 것도 기억하자. 물론 설정을 통해 바꿀 수는 있다.
다음으로 attemptAuthentication 메서드에서 POST 메서드가 맞는지 검사하고, else 문에서 username, password값을 검사해서 authRequest 객체를 생성한다. new UsernamePasswordAuthenticationToken(username, password) 구문에서 authRequest 객체를 만드는 것을 확인할 수 있다. 여기서 이 토큰의 정체가 뭘까?
UsernamePasswordAuthenticationToken
토큰의 정체 확인을 위해서 위에서 배운 로그인 화면에서 로그인을 하면서, 토큰이 상속하고 있는 AbstractAuthenticationToken의 getName() 메서드를 디버깅으로 잡아봤다.
AbstractAuthenticationToken의 getName()
생각보다 별거 없다. principal은 내가 입력한 사용자 ID, credentials는 비밀번호, 그리고 지금 과정에선 알 수 없는 authorities, IP와 SessionID가 담긴 details, authenticated 등의 정보를 담고 있는 것을 볼 수 있다. 아마 authorities는 해당 페이지에 접근 가능한 권한 목록? 이고 authenticated는 인증되면 true로 바뀔 것이리라.. 잘 모르지만 일단 Token이 뭔지 대략은 알 것 같다.
3. AntPathRequestMatcher
위 그림에서 UsernamePasswordAuthenticationFilter를 거친 후, AntPathRequestMatcher에서 처리가 된다. AntPathRequestMatcher는 서버에서 설정해준 자원(url)별로 매핑되어있는 권한 정보인 RequestMap과 클라이언트가 요청한 자원내용을 포함한 요청 정보인 FilterInvocation을 비교해주는 메서드이다. AntPath 외에도 AndPath, OrPath 의 Matcher가 존재하여 여러 조합으로 서버의 자원에 대한 권한과 사용자가 요청한 url을 비교분석하게 된다. 아래 pseudo code에서 볼 수 있듯이 먼저 url 정보가 있는지 검사하고 다음으로 권한 정책을 검사한다고 보면 된다.
요약하자면 이제 사용자가 입력한 principal과 credentials 정보를 기본으로 인증 객체(Authentication)을 만들었다고 이해하면 될 것 같다. 더 깊이있는 이해는 강의 뒷부분을 먼저 학습하고 다시 이해해보자.
참조
1) 인프런 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 정수원님 강의
2) 1) 정수원님 강의의 질의응답
'Programming-[Backend] > Spring Security' 카테고리의 다른 글
[스프링 시큐리티] [작성중] 6. 권한 설정, 표현식 (0) | 2022.01.08 |
---|---|
[스프링 시큐리티] [작성중] 5. 익명 사용자 인증 필터, 세션 제어 필터 (0) | 2022.01.07 |
[스프링 시큐리티] 4. Logout, Rember me 필터 (0) | 2022.01.05 |
[스프링 시큐리티] 3. Form Login 인증 방식 파악 계속 : AuthenticationManager, Provider와 SecurityContext (0) | 2021.12.29 |
[스프링 시큐리티] 1. 시작 : 프로젝트 생성, WebSecurityConfigurerAdapter - HttpSecurity 구조 이해하기 (0) | 2021.12.22 |