Spring Security Custom Request Parameter 사용하기

By | 2020년 9월 25일

username / password 이외에 추가 파라미터(ex: OTP 코드)를 처리하기 위한 방법이다.

간단히 요약하면

addFilterBefore 필터에 Custom UsernamePasswordAuthenticationFilter 를 추가하여 Custom WebAuthenticationDetails 로 변경하여 넘겨주는 방식
////////////////////////////////////////////////
// CustomUsernamePasswordAuthenticationFilter.java
////////////////////////////////////////////////

package com.mzc.megatoi.backoffice.web.auth;

import javax.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

@Getter
@Setter
@Component
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  @Autowired
  CustomWebAuthenticationDetailsSource customWebAuthenticationDetailsSource;

  @Override
  protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
    super.setDetails(request, authRequest);
    authRequest.setDetails(customWebAuthenticationDetailsSource.buildDetails(request));
  }
}


////////////////////////////////////////////////
// CustomWebAuthenticationDetailsSource.java
////////////////////////////////////////////////

package com.mzc.megatoi.backoffice.web.auth;

import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;

@Component
public class CustomWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
  @Override
  public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
    return new CustomWebAuthenticationDetails(context);
  }
}


////////////////////////////////////////////////
// CustomWebAuthenticationDetails.java
////////////////////////////////////////////////

package com.mzc.megatoi.backoffice.web.auth;

import javax.servlet.http.HttpServletRequest;
import lombok.Getter;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {

  @Getter
  private String otpSecret;

  public CustomWebAuthenticationDetails(HttpServletRequest request) {
    super(request);
    // request 파라미터에서 추가로 받을 항목
    otpSecret = request.getParameter("otpSecret");
  }
}


////////////////////////////////////////////////
// UserPassOtpAuthenticationProvider.java
////////////////////////////////////////////////

package com.mzc.megatoi.backoffice.web.auth;

import java.util.List;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.stereotype.Component;

@Log4j2
@Component
public class UserPassOtpAuthenticationProvider implements AuthenticationProvider {

  protected static final List<GrantedAuthority> ADMIN_ROLES = AuthorityUtils.createAuthorityList("ADMIN");

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    String username = authentication.getName();
    String password = authentication.getCredentials().toString();

    // 아래처럼 추가 항목들을 가져올 수 있음
    String otp = ((CustomWebAuthenticationDetails) authentication.getDetails()).getOtpSecret();

    // user / pass / otp 체크
    // 실패시
    // throw new BadCredentialsException("Invalid username/password/otp");

    return new UsernamePasswordAuthenticationToken(username, password, ADMIN_ROLES);
  }

  @Override
  public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
  }
}


////////////////////////////////////////////////
// SecurityConfig.java
////////////////////////////////////////////////

public class SecurityConfig extends WebSecurityConfigurerAdapter {

  final
  UserPassOtpAuthenticationProvider userPassOtpAuthenticationProvider;

  @Bean
  public CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() throws Exception {
    CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter = new CustomUsernamePasswordAuthenticationFilter();
    customUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
    return customUsernamePasswordAuthenticationFilter;
  }

  public SecurityConfig(  
    this.userPassOtpAuthenticationProvider = userPassOtpAuthenticationProvider;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests();

    http.formLogin()
      .loginPage(LOGIN_URI)
      .successHandler(megatoiAuthenticationSuccessHandler)
      .failureHandler(megaToIAuthenticationFailureHandler)
      .usernameParameter("username")
      .passwordParameter("password")
      .permitAll();

    http.logout()
      .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
      .logoutSuccessUrl(LOGIN_URI)
      .invalidateHttpSession(true)
      .deleteCookies()
      .permitAll();

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(this.userPassOtpAuthenticationProvider);
  }


}

위 코드는 일부를 발췌한 내용이라 보고 참고 정도만 하세요.