728x90
서론
springboot로 프로젝트를 진행하면서 jwt를 사용하였고, 이 과정에서 refresh 토큰도 구현하였다.
access token은 탈취 당해도 만료기간이 지나면 사용할 수 없지만, refresh 토큰은 access 토큰에 비해 만료기간이 길기 때문에 탈취당하면 위험하다. 그래서 이를 보완하기 위한 방법으로 refresh token 재발급 요청이 왔을 때 refresh 토큰 디비에 저장된 ip값과 요청 ip값을 비교하여 다른 경우에는 발급하지 않거나, 원래 사용자에게 알림을 보내는 방식이 사용될 수 있다고 한다.
그 외에도 로그 찍기, ip whitelist등 사용자의 ip는 쓸 곳이 많다.
HttpServletRequest
Spring MVC 기반의 서버에서 개발할 때, HttpServletRequest 객체를 사용하면 쉽게 ip주소를 얻을 수 있다.
@GetMapping("/login")
public ResponseEntity<LoginResponse> login(HttpServletRequest request) {
return ResponseEntity.ok(loginResponse);
}
- 다음과 같이 컨트롤러의 매개변수로 HttpServletRequest를 받을 수 있다.
public interface HttpServletRequest extends ServletRequest {
String BASIC_AUTH = "BASIC";
String FORM_AUTH = "FORM";
String CLIENT_CERT_AUTH = "CLIENT_CERT";
String DIGEST_AUTH = "DIGEST";
String getAuthType();
Cookie[] getCookies();
long getDateHeader(String name);
String getHeader(String name);
Enumeration<String> getHeaders(String name);
...
}
- HttpServletRequest 코드를 살펴보면 ServletRequest를 상속받고
public interface ServletRequest {
Object getAttribute(String name);
Enumeration<String> getAttributeNames();
...
String getProtocol();
String getScheme();
String getServerName();
int getServerPort();
BufferedReader getReader() throws IOException;
String getRemoteAddr();
String getRemoteHost();
...
}
- ServletRequest 코드를 보면 다음과 같다.
- ip를 가져오기 위해 사용할 메서드는 getRemoteAddr()와 getHeader이다.
getHeader
Returns the value of the specified request header as a String. If the request did not include a header of the specified name, this method returns null. If there are multiple headers with the same name, this method returns the first head in the request. The header name is case insensitive. You can use this method with any request header.Params:name – a String specifying the header nameReturns:a String containing the value of the requested header, or null if the request does not have a header of that name
지정된 요청 헤더의 값을 문자열로 반환합니다. 요청에 지정된 이름의 헤더가 포함되지 않은 경우 이 메서드는 null을 반환합니다. 이름이 같은 헤더가 여러 개 있는 경우 이 메서드는 요청의 첫 번째 헤드를 반환합니다. 헤더 이름은 대소문자를 구분하지 않습니다. 이 메서드는 모든 요청 헤더와 함께 사용할 수 있습니다. Params:name – 헤더 이름을 지정하는 문자열Returns:a 요청된 헤더의 값을 포함하는 문자열 또는 요청에 해당 이름의 헤더가 없는 경우 null
- 요청 헤더 값을 받아오는 메서드
getRemoteAddr
Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. For HTTP servlets, same as the value of the CGI variable REMOTE_ADDR. Returns: a String containing the IP address of the client that sent the request
요청을 보낸 클라이언트 또는 마지막 프록시의 인터넷 프로토콜(IP) 주소를 반환합니다. HTTP 서블릿의 경우 CGI 변수 REMOTE_ADDR의 값과 동일합니다. 반환값: 요청을 보낸 클라이언트의 IP 주소를 포함하는 문자열
- ip 주소를 받아오는 메서드
IP주소 받아오기1 - 간단, 문제있음
public class Helper {
public static String getClientIp(HttpServletRequest request) {
String clientIp = request.getRemoteAddr();
return clientIp;
}
}
- Helper라는 클래스를 만들었고, 그 안에 getClientIp 메서드를 넣었다.
- 그냥 request의 getRemoteAddr()을 사용한다면 쉽게 ip를 얻을 수 있다.
- 이 방식은 IPv6 형식의 ip주소를 얻을 수 있다.
- 0:0:0:0:0:0:0:1
- IPv4 형식으로 얻으려면 서버 구동 시 vm arguments로 -Djava.net.preferIPv4Stack=true 를 주면 된다고 한다
- (참고할 수 있는 블로그)
- 하지만 이 방식만으로 ip를 얻어올 경우에는 다음과 같은 문제가 있을 수 있다
- 프록시를 거쳐 들어오는 접속의 경우 ip주소가 해당 서버의 주소로 나옴
- WAS가 2차 방화벽 안에 있고, 클라이언트에서 웹서버를 통해 호출하는 경우, 웹서버의 ip정보를 가져옴
- 로드밸런서에서 호출되는 경우, 로그밸런서의 ip정보를 가져옴
- 이 문제를 해결하기 위해서, Http Header를 확인 후 분기를 통해 처리할 수 있다.
IP주소 받아오기2 - 프록시 서버 등을 고려함
public class Helper {
public static String getClientIp(HttpServletRequest request) {
String clientIp = null;
boolean isIpInHeader = false;
List<String> headerList = new ArrayList<>();
headerList.add("X-FORWARDED-FOR");
headerList.add("HTTP_CLIENT_IP");
headerList.add("HTTP_X_FORWARDED_FOR");
headerList.add("HTTP_X_FORWARDED");
headerList.add("HTTP_FORWARDED_FOR");
headerList.add("HTTP_FORWARDED");
headerList.add("Proxy-Client-IP"); //프록시 환경일 경우
headerList.add("WL-Proxy-Client-IP"); //웹로직 서버일 경우
headerList.add("HTTP_VIA");
headerList.add("IPV6_ADR");
for (String header : headerList) {
clientIp = request.getHeader(header);
if (StringUtils.hasText(clientIp) && !clientIp.equals("unknown")) {
isIpInHeader = true;
break;
}
}
if (!isIpInHeader) {
clientIp = request.getRemoteAddr();
}
return clientIp;
}
}
- headerList에는 clientIp가 들어있을 수 있는 헤더의 목록을 넣는다.
- 보통은 getRemoteAddr() 메서드로 ip를 가져오지만, headerList를 사용하는 이유는
- WAS 서버 앞에 프록시 서버나 로드 밸런싱 서버 등의 다른 서버가 존재하는 경우, 클라이언트의 ip가 아닌 해당 서버의 ip를 가져온다고 한다
- X-Forwarded-For(XFF): Http 프록시나 로드 밸런서를 통해 웹 서버에 접속하는 클라이언트의 원 IP주소를 식별하는 표준 헤더
참고자료
https://wildeveloperetrain.tistory.com/148
728x90
'Backend > Spring' 카테고리의 다른 글
The server timezone is <???ѹα? ǥ?ؽ> that's unknown, trying to use system default timezone (4) | 2024.04.21 |
---|---|
[Spring] jwt 로그아웃을 구현하는 방법 (0) | 2023.08.07 |
[Spring] REST API 버전을 관리하는 이름짓기 (0) | 2023.07.24 |
[Spring] 인프런 tdd 수업3 - 상품조회 기능 tdd (0) | 2023.07.15 |
[Spring] 인프런 tdd 수업2 - api테스트로 변환 (0) | 2023.07.15 |