[Spring Security] Login에서 사용자 정의 입력 값 파라미터로 전달하기

2 분 소요

개요

기본적인 스프링 시큐리티에서는 로그인 시, 기본 파라미터로 username(아이디)와 password를 넘겨준다.

근데, 나는 admin(ROLE_ADMIN)과 일반 user(ROLE_USER) 의 로그인 페이지(View)가 같기 때문에 사용자가 어떤 권한으로 로그인 요청을 하는지 알 수 있어야 했다.

그러기 위해 로그인 페이지에서 input 태그의 hidden 값으로 사용자가 원하는 권한을 전달했는데, 스프링 시큐리티에서 전달된 파라미터 값을 받는 것이 난관이었다. (시큐리티는 난관의 연속;;)

  • 참고 (1) : https://stackoverflow.com/questions/10074308/how-to-pass-an-additional-parameter-with-spring-security-login-page
  • 참고 (2) : http://andang72.blogspot.com/2017/06/spring-security-login.html

서치 결과 Spring Security 4.1 이상의 환경에서는 필터를 수정한 커스텀 필터 단이 아닌 RequestContextFilter를 사용하여 사용자가 직접 정의한 AuthenticationProvider 인터페이스 구현체에서 RequestContextHolder 를 사용하여 로그인 할 때, username과 password 외의 input 태그로 추가 전달된 request 파라미터 값을 낚아채서 사용해야 한다고 한다.

하지만 직접 시도해본 결과 AuthenticationProvider 인터페이스 구현체 뿐만 아니라 UserDetailsService 인터페이스 구현체에서도 request 파라미터값을 받아올 수 있었다.

시큐리티 세션 내부에 => Authentication가 있고, Authentication 내부에 UserDetails가 있으니 당연한 건가…ㅎㅎ 아직 헷갈리는 시큐리티다~~~

web.xml

web.xml 에 RequestContextFilter 필터 설정을 추가한다.

	<!-- spring request context filter -->
	<filter>
	  <filter-name>requestContextFilter</filter-name>
	  <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
	</filter>
	<filter-mapping>
	  <filter-name>requestContextFilter</filter-name>
	  <url-pattern>/*</url-pattern>
	</filter-mapping>     

spring-security.xml (시큐리티 설정파일)

requestContextFilter 객체를 주입한다.

<beans:bean id="requestContextFilter" class="org.springframework.web.filter.RequestContextFilter"/>

AuthenticationException 구현체

package kr.kro.globalpay.security;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import kr.kro.globalpay.admin.dao.AdminDAO;
import kr.kro.globalpay.member.dao.MemberDAO;

@Service
public class UserLoginAuthenticationProvider implements AuthenticationProvider  {
	
	@Autowired
	PrincipalDetailsService principalDetailsService;
	
	@Autowired
	private MemberDAO dao;
	
	@Autowired
	private AdminDAO aDao;
	
	@Autowired
	private BCryptPasswordEncoder pwEncoder;
	
	
	// 실제 인증을 구현하는 로직 
	/*
	 * 파라미터로 받은 Authentication에는 사용자가 입력한 ID/패스워드 정보가 담겨 있음
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		
		System.out.println("---------------------------------------------authenticate 실행중----------------------------------------");
		
		
		ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();  
		if( attr!=null && attr.getRequest() != null ) {
		   System.out.println("사용자 정의 파라미터 : " + attr.getRequest().getParameter("temp"));
		}else{  
			System.out.println("사용자 정의 파라미터 없음!!! ");
		}
		
		String id = authentication.getName();
		String pw = (String) authentication.getCredentials();

		System.out.println("입력한 id : " + id);
		System.out.println("입력한 pw : " + pw);
		
		/* DB에서 가져온 정보 */
		UserDetails userDetails = (UserDetails) principalDetailsService.loadUserByUsername(id);
		
		System.out.println("---------------------------------------------authenticate 실행중----------------------------------------");
		
		/* 인증 진행 */
		if (userDetails == null || !id.equals(userDetails.getUsername()) || !pwEncoder.matches(pw, userDetails.getPassword())) {
			System.out.println("로그인 실패!!!!!!!!!!!!!!!!!");
			System.out.println("userDetails : " + userDetails);
			System.out.println("userDetails.getUsername() : " + userDetails.getUsername());
			System.out.println("비밀번호 일치여부 : " + pwEncoder.matches(pw, userDetails.getPassword()));
			
			throw new BadCredentialsException(id);
		} 
		


		/* 최종 리턴 시킬 새로만든 Authentication 객체 */
		Authentication newAuth = new UsernamePasswordAuthenticationToken(
				userDetails, null, userDetails.getAuthorities());
		
		System.out.println("newAuth : " + newAuth);

		return newAuth;
		
	}

	@Override
	public boolean supports(Class<?> authentication) {
		// 스프링 Security가 요구하는 UsernamePasswordAuthenticationToken 타입이 맞는지 확인
		return authentication.equals(UsernamePasswordAuthenticationToken.class);
	}

}

UserDetailsService 구현체

package kr.kro.globalpay.security;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.request.ServletRequestAttributes;

import kr.kro.globalpay.member.dao.MemberDAO;
import kr.kro.globalpay.member.vo.MemberVO;


/*
 *  login 요청이 오면 자동으로 UserDetailService 타입으로 IoC 되어있는 loadUserByUsername이 호출됨
 */

@Service
public class PrincipalDetailsService implements UserDetailsService {

	@Autowired
	private MemberDAO dao;
	
	@Autowired
	private BCryptPasswordEncoder pwEncoder;
	
	
	// 시큐리티 세션 내부에 => Authentication / Authentication 내부에 => UserDetails
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		System.out.println("---------------------------------loadUserByUsername 실행 중-----------------------------------------");
		
		HttpServletRequest request  =
	             ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
	                    .getRequest();
		
		System.out.println("로그인 페이지에서 넘겨준 파라미터 : " + request.getParameter("temp"));
		
		System.out.println("입력한 username : " + username);
		
		MemberVO vo = new MemberVO();
		vo.setId(username);
		
		MemberVO member = dao.login(vo); // db에서 사용자 정보 select
		
		System.out.println("db에 저장된 정보 : " + member);
		
		if(member != null) {
			return new PrincipalDetails(member);
		}
		
		return null;
	}

}