[Spring Security] Login에서 사용자 정의 입력 값 파라미터로 전달하기
개요
기본적인 스프링 시큐리티에서는 로그인 시, 기본 파라미터로 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;
}
}