Hayden's Archive

[Spring] 스프링에서 클라이언트 정보 가져오는 객체 만들기 본문

Study/Java & Kotlin

[Spring] 스프링에서 클라이언트 정보 가져오는 객체 만들기

_hayden 2020. 12. 29. 10:35

요즘 사용자 정보를 가져와서 로그를 남기는 작업을 하고 있다.

거의 모든 로그를 남기다 보니 사용자 아이피, 현재 시간, 사용자 아이디를 계속해서 가져와야 할 일이 생겼다.

아무래도 계속해서 공통적으로 쓰이는 코드라면 모듈화하는 것이 좋다.

 

따라서 프로젝트 내에 ClientInfo.java라는 클래스를 따로 만들었고, Bean으로 사용할 수 있도록 @Component로 어노테이션을 붙여줬다.

전체 코드 : github.com/devyeony/smart-data-tracing-management/blob/main/src/main/java/kr/com/inspect/util/ClientInfo.java

 

devyeony/smart-data-tracing-management

Contribute to devyeony/smart-data-tracing-management development by creating an account on GitHub.

github.com

 

작성된 코드를 하나씩 정리해보자. 

 

1) 사용자 아이피

아이피를 가져오기 위해서는 HttpServletRequest 객체가 필요하다.

보통 사용자로부터 요청을 받는 Controller에서 파라미터로 많이 받아오지만, 매번 파라미터를 받아오는 것도 번거로운 일이라 생각되어 파라미터 없이 HttpServletRequest 객체를 가져왔다.

이를 위해서는 먼저 RequestContextFilter 뒤에 CSRF 공격을 막아주는 CsrfFilter를 추가한다.

(CsrfFilter 관련 설명은 fenderist.tistory.com/345 를 참고, 스프링 시큐리티의 커스텀 필터 등록과 관련한 설명은 kimchanjung.github.io/programming/2020/07/02/spring-security-02/ 의 커스텀 필터 등록을 참고)

 

나는 xml 대신 Java Config를 사용하였으므로 HttpSecurity를 파라미터로 받는 configure 메소드에 아래와 같이 추가하였다.

(코드 전문 : github.com/devyeony/smart-data-tracing-management/blob/main/src/main/java/kr/com/inspect/config/SecurityConfig.java )

http.addFilterAfter(new RequestContextFilter(), CsrfFilter.class);

 

이제 ClientInfo에 본격적으로 클라이언트 IP 주소를 가져오는 코드를 작성해보자.

( RequestContextHolder와 관련된 설명 참고 : gompangs.tistory.com/entry/Spring-RequestContextHolder )

 

/**
 * 클라이언트의 ip 주소를 가져옴
 * @return ip 주소
 */
public String getIpAddr() {
	String ip_addr = null;
	ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
	HttpServletRequest request = sra.getRequest();
	
	ip_addr = request.getHeader("X-Forwarded-For");
	if (ip_addr == null) {
		ip_addr = request.getHeader("Proxy-Client-IP");
	}
	if (ip_addr == null) {
		ip_addr = request.getHeader("WL-Proxy-Client-IP"); 
	}
	if (ip_addr == null) {
		ip_addr = request.getHeader("HTTP_CLIENT_IP");
	}
	if (ip_addr == null) {
		ip_addr = request.getHeader("HTTP_X_FORWARDED_FOR");
	}
	if (ip_addr == null) {
		ip_addr = request.getRemoteAddr();
	}
	return ip_addr;
}

 

이제 아이피를 제대로 가져왔는지 확인해보는데 문제가 생겼다.

원격 아이피 주소는 IPv4로 잘 찍히는데 로컬 아이피 주소가 IPv4 방식으로 찍히지 않고 IPv6 방식으로 찍히는 것이다.

 

IPv4와 IPv6 방식을 모두 사용할 수 있다면 IPv6 방식의 아이피가 우선하여 제공된다.

언젠가는 IPv4 방식으로 감당이 안 되어서 가지수가 훨씬 많은 IPv6 방식으로 전환되겠지만, 현재 진행 중인 프로젝트에서는 IPv4 방식이 요구되어 변경할 필요가 있었다. 그러므로 다음을 참고하여 설정을 해줬다.

hane-1.tistory.com/42

 

JAVA에서 IPv4 사용하기

안녕하세요. 빅한입니다. 서론 오늘은 작업하는 도중에 로컬에서 테스트할 경우 request.getRemoteAddr() 호출시 주소가 "0:0:0:0:0:0:0:1"형식으로 나올경우가 있어서 포스팅합니다. 사실 "0:0:0:0:0:0:0:1"이

hane-1.tistory.com

 

위를 참고하여 tomcat의 bin으로 가서 setenv.sh를 찾는데 없다.. 서치해보니 setenv.sh는 tomcat 구동시 실행 환경 설정 파일로 기본적으로 제공되는 파일이 아니다. ( wiper2019.tistory.com/226 참고 )

그러니까 tomcat의 bin 폴더에 쉘 파일을 직접 생성하면 된다.

 

아래와 같이 입력~!

 

나노 편집기로 작성했으므로 Ctrl+O로 저장 후 엔터, Ctrl+X로 나가면 된다.

이제 설정을 반영해주기 위해서 tomcat의 bin 폴더의 shutdown.sh와 startup.sh를 실행하여 tomcat 서버를 재시작한다.

 

로컬 아이피가 IPv4 방식으로 잘 찍힌다~!

 

 

2) 현재 시간 

자바에서는 TimeZone의 getTimeZone() 메소드를 사용하여 각 나라의 타임존 ID를 인자값으로 넣으면 해당되는 TimeZone을 만들 수 있다.

SimpleDataFormat 생성자에 날짜 패턴을 입력해서 DateFormat 객체를 만들고, 이 객체의 TimeZone을 세팅해주면 해당 지역의 시간이 세팅된다.

System.currentcurrentTimeMillis()로 현재 시간을 밀리초로 가져오고, 이것을 DataFormat의 format() 메소드에 인자값으로 넣어주면 현재 시간을 도출할 수 있다.

public String getTime() {
	TimeZone zone = TimeZone.getTimeZone("Asia/Seoul");
	DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	format.setTimeZone(zone);
	String currentTime = format.format(System.currentTimeMillis());
	return currentTime;
}

 

 

3) 스프링 시큐리티에서 로그인한 사용자의 아이디와 암호화된 비밀번호

스프링 시큐리티에서 사용자가 로그인하면, Authentication 인터페이스(인증 정보)를 구현한 객체가 생성된다.

실제 회원에 해당하여 인증에 성공하면 Authentication에 접근 주체(Principal)와 비밀번호(Credentials), 권한(Authorities) 정보를 담는다.

이 Authentication은 SecurityContext에서 보관하게 되고, SecurityContext는 SecurityContextHolder에서 보관하게 된다.

Principal 객체를 가져와서 UserDetails 타입으로 캐스팅하고 UserDetails의 getUsername(), getPassword()를 사용한다면 사용자의 아이디, 암호화된 비밀번호를 가져올 수 있다.

(물론 이를 위해서는 UserDetails를 구현한 클래스가 선행되어야 한다.)

/**
* 스프링 시큐리티에서 로그인한 사용자의 아이디를 가져옴
 * @return 로그인한 사용자의 아이디
 */
public String getMemberId(){
	Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 
	UserDetails userDetails = (UserDetails)principal; 
	return userDetails.getUsername();
}
	
/**
 * 스프링 시큐리티에서 로그인한 사용자의 암호화된 비밀번호를 가져옴
 * @return 로그인한 사용자의 암호화된 비밀번호
 */
public String getPwd() {
	Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 
	UserDetails userDetails = (UserDetails)principal; 
	return userDetails.getPassword();
}

 

UserDetails의 또 다른 메소드들은 아래에 잘 나타나 있다. 필요에 따라 추가하면 되겠다.

docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetails.html