Spring Security에서 제공하는 폼 로그인에 대한 이해가 부족해서 문제가 발생한 것 같다.
사용자가 이메일과 패스워드를 입력하면 Spring Security가 HTTP 요청을 가로채 login 요청을 처리하게 되는데 이 과정을 정리해두고자 한다.
<form th:action="@{/login}" method="post">
<div class="form-group">
<label for="email">이메일</label>
<input type="text" id="email" name="email" class="form-control" placeholder="이메일을 입력하세요" required></div>
<div class="form-group">
<label for="password">비밀번호</label>
<input type="password" id="password" name="password" class="form-control" placeholder="패스워드를 입력하세요" required></div>
<button type="submit" class="btn btn-primary btn-block">로그인</button>
</form>
👉왜 Spring Security 폼 로그인이 안됐던 걸까?
- 현재 폼에서 넘겨주는 값은
password
다. 근데 이- 그러나 아래
FormLoginConfigurer
코드를 보면 기본적으로usernameParameter
은 username이고passwordParameter
은 password이다.- 나의 프로젝트에서는 이메일을 아이디로 사용하기에 바꿔줘야했다.
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), (String)null);
this.usernameParameter("username");
this.passwordParameter("password");
}
// ...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests(
auth -> auth
.requestMatchers(
new AntPathRequestMatcher("/js/**"),
new AntPathRequestMatcher("/img/**"),
new AntPathRequestMatcher("/css/**"),
new AntPathRequestMatcher("/auth/login"),
new AntPathRequestMatcher("/auth/signup"),
new AntPathRequestMatcher("/login"),
new AntPathRequestMatcher("/signup"),
new AntPathRequestMatcher("/"))
.permitAll().anyRequest().authenticated())
.formLogin(formLogin -> formLogin.loginPage("/auth/login")
.usernameParameter("email")
.loginProcessingUrl("/login")
.permitAll()
.defaultSuccessUrl("/board/posts"))
.logout(logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/auth/login")
.invalidateHttpSession(true))
.oauth2Login(oauth -> oauth.userInfoEndpoint(
oauthService -> oauthService.userService(principalOauthDetailsService))
.defaultSuccessUrl("/board/posts"))
.requestCache((cache) -> cache.requestCache(nullRequestCache));
return http.build();
}
👉과정 정리
- 사용자는 인가되지않은 리소스에 대해 비인증 요청을 보낸다.
- Spring Security 인증 필터는
AccessDeniedException
을 발생시켜 인증되지 않은 요청이 거부되었음을 알린다.- 사용자가 인증이 되지 않았으므로
ExceptionTranslationFilter
가 인증 과정을 시작하도록 하면서AuthenticationEntryPoint
를 사용하여 로그인 페이지로 리다이렉션한다.AuthenticationEntryPoint
는LoginUrlAuthenticationEntryPoint
의 인스턴스이다.- 브라우저는 리디렉션된 로그인 페이지로 요청을 보낸다.
- 로그인 페이지가 렌더링된다.
👉과정 정리
- 사용자가 사용자 이름과 패스워드를 제출하면
UsernamePasswordAuthenticationFilter
는HttpServletRequest
인스턴스에서 사용자 이름과 패스워드를 추출하여 인증 유형인UsernamePasswordAuthenticationToken
을 생성한다.UsernamePasswordAuthenticationToken
을AuthenticationManager
인스턴스에 전달하여 인증한다.
- failure) 만약 인증에 실패하면
SecurityContextHolder
가 비워진다.- success) 만약 인증에 성공하면
SecurityContextHolder
에 인증 정보가 설정된다. 그리고 나서AuthenticationSuccessHandler
가 호출된다. 커스텀으로도 구성이 가능하다.