Framework/Spring

[HTTP] User IP와 Agent(Device) 정보 가져오기

simDev1234 2022. 10. 15. 05:30

|  User IP와 User Agent 정보란?

- 클라이언트가 HTTP를 통해 어떤 요청을 보내면 HTTP header에 사용자 IP주소와 기기정보(Agent)가 담기게 된다.

- User IP 주소는, 다양한 종류의 proxy를 고려하여 각 header를 전부 확인하는 것이 필요하다.

- 만약 IPv4 형식으로만 IP주소를 얻길 원한다면 [Run]-[Configuration] Arguments VM에 설정을 걸어줄 수 있다.

-Djava.net.preferIPv4Stack=true

- 사이트 정책 : 로그인 시 히스토리 내역 저장한 후 메인화면으로 이동한다.

- 위 정책에 따라

 1) 히스토리를 저장할 Entity를 만들고,

 2) 스프링 시큐러티를 이용해 로그인 성공시 사용자 IP주소와 Agent정보를 DB에 저장한다.

- 스프링 시큐러티 레퍼런스 : https://atin.tistory.com/585

 

1. Entity

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LoginHistory {
    @Id
    @GeneratedValue
    private Long id;

    private String userId;
    private String userIp;
    private String userAgent;
    private LocalDateTime lastLoginDate;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

}

 

2. WebUtils에 사용자 IP와 Agent 정보를 가져오는 메소드를 만들었다.

package com.example.fastlms.util;

import javax.servlet.http.HttpServletRequest;

public class WebUtils {

    public static String getUserAgent(HttpServletRequest request) {
        return request.getHeader("User-Agent");
    }

    public static String getClientIp(HttpServletRequest request) {

        String ip = request.getHeader("X-Forwarded-For");

        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("x-real-ip");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("x-original-forwarded-for");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_X_FORWARDED");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_X_CLUSTER_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_FORWARDED");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("HTTP_VIA");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getHeader("REMOTE_ADDR");
        }
        if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }
}

 

3. 스프링 시큐러티를 통해 userId, userIP, userAgent를 LoginHistory(DB)에 저장

(1) SecurityConfig에서 Config(HttpSecurity http)의 defaultSuccessUrl() 사용 

- 로그인 Success Handler를 만드는 방법도 있다고 하는데, 메소드 사용이 너무 복잡해서, 

  대신 httpSecurity API 메소드의 defaultSuccessUrl()을 사용했다.

- defaultSuccessUrl()을 통해 로그인 성공 시 특정 경로로 이동하여 로그인 히스토리를 저장할 것이다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final MemberService memberService;

    @Bean
    UserAuthenticationFailureHandler getFailureHandler() {
        return new UserAuthenticationFailureHandler();
    }

    @Bean
    PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * Http Security
     * 모두 접근 가능 : 홈, 회원 가입, 메일 인증
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();

        http.headers().frameOptions().sameOrigin();

        http.authorizeRequests()
                .antMatchers(
                        "/",
                        "/member/register",
                        "/member/email-auth",
                        "/member/find/password",
                        "/member/reset/password"
                )
                .permitAll();

        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasAuthority("ROLE_ADMIN");

        http.formLogin()
                .loginPage("/member/login")
                //.successForwardUrl("/member/login-success-handler")  // 로그인이 성공한 후 보내는 URL
                //.successHandler(getSuccessHandler())                 // 로그인 핸들링 URL
                .defaultSuccessUrl("/member/login-success")            // 로그인이 성공한 후 보내는 디폴트 URL
                .failureHandler(getFailureHandler())
                .permitAll();

        http.logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true);

        http.exceptionHandling()
            .accessDeniedPage("/error/denied");

        super.configure(http);
    }

}

(2) 컨트롤러에서 사용자 정보를 가져온다.

- Principal로 userId를 가져오고,

- WebUtils를 통해 Request Header의 IP와 Agent 정보를 가져온다.

@Controller
@RequiredArgsConstructor
@Slf4j
public class MainController {

    private final LoginHistoryService loginHistoryService;

    @RequestMapping(value = "/")
    public String index() {

        return "index";
    }

    @RequestMapping(value = "/member/login-success")
    public String saveLoginHistory(HttpServletRequest request, Principal principal){

        String userId = principal.getName();
        String userIp = WebUtils.getClientIp(request);
        String userAgent = WebUtils.getUserAgent(request);

        loginHistoryService.saveLoginHistory(userId, userIp, userAgent);

        return "index";
    }

}

(3) 로그인 서비스를 통해 히스토리를 DB에 저장한다. 

@Service
@RequiredArgsConstructor
public class LoginHistoryImpl implements LoginHistoryService {

    private final LoginHistoryRepository loginHistoryRepository;

    @Override
    public void saveLoginHistory(String userId, String userIp, String userAgent) {

        loginHistoryRepository.save(
                LoginHistory.builder()
                        .userId(userId)
                        .userIp(userIp)
                        .userAgent(userAgent)
                        .lastLoginDate(LocalDateTime.now())
                        .build()
        );

    }
}

 

 

 

[ 참고 및 출처 ]

https://recordsoflife.tistory.com/248

https://galid1.tistory.com/698

https://all-record.tistory.com/168

https://atoz-develop.tistory.com/entry/Spring-Security-Web%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-IP-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0