Backend 개발/Spring Security 6

Spring Security 6 폼 인증 formLogin()

수달리즘 2025. 5. 20. 11:38
반응형

폼(Form) 인증

웹/앱 서비스에서 많이 사용되는 인증 방식으로 다음과 같이 HTTP 기반 form 에 사용자가 아이디/패스워드를 입력하면 로그인 처리를 어떻게 할지 정의할 수 있다.

스프링 시큐리티 기본 제공 로그인 화면

사용자가 로그인 폼 화면을 따로 만들지 않더라도 스프링 시큐리티에서 제공하는 화면을 사용할 수는 있지만 실무에서 이런 경우는 거의 드물다.

formLogin() API

스프링 시큐리티를 사용할지라도 실무에서는 스프링 시큐리티 기본 보안 설정을 그대로 사용하는 것이 아니라 사용자가 커스텀하게 변형하는 것이 99.99999% 일 것이다. 이를 위해서 formLogin() API에 대한 대략적인 것은 알아둬야 한다.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            // http 통신에 대한 인가 정책 설정
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            //.formLogin(Customizer.withDefaults()); → 폼 로그인 방식을 기본 default 방식으로 설정

            // formLogin 방식을 커스텀하게 설정
            .formLogin(form -> form
                    // 사용자가 만든 로그인 페이지로 이동(스프링 시큐리티가 제공하는 로그인 페이지 무시)
                    .loginPage("/loginPage")
                    // 로그인 요청을 처리할 url
                    // 해당 url로 요청이 오면 스프링 시큐리티가 로그인 처리 수행하며 로그인 폼 엔드포인트(form action)도 이와 동일하게 맞춰야 함
                    .loginProcessingUrl("/loginProc")
                    // 두번째 파라미터인 always use를 false 로 주면 인증 전에 가려고 했던 url로 리다이렉트(기본값: false)
                    .defaultSuccessUrl("/", true)
                    // 인증 실패했을 때 사용자에게 보내질 url 지정(기본값: /login?error)
                    .failureUrl("/failed")
                    // 사용자가 보낼 id HTTP 매개변수명(기본값: username)
                    .usernameParameter("userId")
                    // 사용자가 보낼 패스워드 HTTP 매개변수명(기본값: password)
                    .passwordParameter("pwd")
                    // 인증 성공 시 사용할 핸들러 지정
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                            log.info("authentication : {}", authentication);
                            response.sendRedirect("/home");
                        }
                    })
                    // 인증 실패 시 사용할 핸들러 지정
                    .failureHandler(new AuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                            log.info("exception {}", exception.getMessage());
                            response.sendRedirect("/login");
                        }
                    })
                    // 위의 loginPage(), failureUrl(), loginProcessingUrl() 등에 설정한 url 에 접근 허용
                    .permitAll()
            );
    return http.build();
}

FormLoginConfigurer

formLogin을 설정할 수 있는 FormLoginConfigurer는 AbstractAuthenticationFilterConfigurer를 상속받고 있으며 이 부모 클래스가 실제로 많은 역할을 수행하고 있다. 부모 클래스의 init과 configure 메소드가 중요하니 해당 메소드들을 대충이라도 보는 것을 권장한다. (스프링 시큐리티 전체적인 설정과 구조를 이해하는 데 도움이 될 것)

package org.springframework.security.config.annotation.web.configurers;

..
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");
    }
   ..
    public void init(H http) throws Exception {
        super.init(http);
        this.initDefaultLoginFilter(http);
    }
    ..
}

 

package org.springframework.security.config.annotation.web.configurers;
..

public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<T, B> {
    ..
    public void init(B http) throws Exception {
        this.updateAuthenticationDefaults();
        this.updateAccessDefaults(http);
        this.registerDefaultAuthenticationEntryPoint(http);
    }
   ..
    public void configure(B http) throws Exception {
        PortMapper portMapper = (PortMapper)http.getSharedObject(PortMapper.class);
        if (portMapper != null) {
            this.authenticationEntryPoint.setPortMapper(portMapper);
        }

        RequestCache requestCache = (RequestCache)http.getSharedObject(RequestCache.class);
        if (requestCache != null) {
            this.defaultSuccessHandler.setRequestCache(requestCache);
        }

        this.authFilter.setAuthenticationManager((AuthenticationManager)http.getSharedObject(AuthenticationManager.class));
        this.authFilter.setAuthenticationSuccessHandler(this.successHandler);
        this.authFilter.setAuthenticationFailureHandler(this.failureHandler);
        if (this.authenticationDetailsSource != null) {
            this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
        }
       ..
    }
    ..
}
728x90
반응형