simDev1234
심플하고 차분하게
simDev1234
전체 방문자
오늘
어제
  • 분류 전체보기
    • Computer Science
      • Basic Math
      • Data Structure
      • Algorithm
      • Database
      • OS
    • Language
      • Java
      • Kotlin
      • SQL
    • Framework
      • Spring
      • Orm&Mapper
      • 프로젝트로 스프링 이해하기
      • 스프링 라이브러리
    • Infra
      • Cloud
      • Docker
      • Redis
      • AWS, Azure
      • Device
    • Etc
      • CleanCoding
    • Git,Github

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바메모리구조
  • 참조변수
  • 스프링
  • scanner #next() #nextLine()
  • JVM메모리구조
  • 자바프로그래밍
  • 자바프로그램
  • 참조타입
  • null
  • controllerTest
  • 404
  • 컨트롤러
  • 자바

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
simDev1234

심플하고 차분하게

Framework/Spring

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

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

'Framework > Spring' 카테고리의 다른 글

[스프링] @AutoWired 동작 원리 및 DI injection 관련 설명 모음  (0) 2022.10.20
[HTTP] @RequestParam vs @RequestBody  (0) 2022.10.19
[Validation] 데이터 검증, 비즈니스 로직 검증  (1) 2022.09.21
[스프링] Entity 객체를 생성 : 영속성의 개념 + 자동 Auditing  (0) 2022.09.15
[스프링] 개발을 시작하기 전에 - 요구 사항 분석, 기본 구조 잡기  (1) 2022.09.15
    'Framework/Spring' 카테고리의 다른 글
    • [스프링] @AutoWired 동작 원리 및 DI injection 관련 설명 모음
    • [HTTP] @RequestParam vs @RequestBody
    • [Validation] 데이터 검증, 비즈니스 로직 검증
    • [스프링] Entity 객체를 생성 : 영속성의 개념 + 자동 Auditing
    simDev1234
    simDev1234
    TIL용 블로그. * 저작권 이슈가 있는 부분이 있다면 댓글 부탁드립니다.

    티스토리툴바