지난 정해진 시간 동안 FAILURE나 ILLEGAL이 동일한 ip에서 정해진 횟수만큼 나왔다면 그 ip를 차단하는 코드를 짜보겠다
console에서
어떤 ip를 차단하겠다는 규칙. 그 규칙이 언제 만들어졌고, 언제까지 유효하며, 현재 유효한가에 대한 여부, 그리고 차단할 ip.
테이블 생성
SystemBannedIpEntity 생성. 게터세터까지
SystemService
차단은 언제 이루어질까?
SystemService의 putActivityLog메서드가 실행될 때.
checkPast 과거를 확인하라는 boolean 타입
checkActivityAndBan 메서드에서는 전달받은 request가 가지고 있는 Ip 주소를 활용해서 후, 그 ip주소가 지난 몇 분 혹은 몇 시간 동안 몇번의 FAILURE나 ILLEGAL을 발생시켰는지 확인하고 그 값을 넘어섰다면 banned_ips에다 insert를 하는 작업을 할 것.
ISystemMapper 에서 selectBadActivityCountByIp추가
xml에서 추가
selectBadActivityCountByIp
전달받은 ip와 result가 실패나 불법.
lookBackSeconds :지난 몇초 내지 몇시간 안에 갯수가 ~개를 초과하면 차단시키겠다,
차단한계가 3이고 (lookBackSeconds = 600)이면 : 지난 10분 내에 3번의 실패나 불법을 일으키면 차단을 시킨다.
createdAt > SELECT DATE_SUB(NOW(), INTERVAL 600 SECOND);
현재 시간에서 ~만큼(600초)의 시간을 뺀 것 보다 미래인 것. 그 중에 FAILURE, ILLEGAL을 일으킨 ip를 가져온다.
현재 시간에서 600초만큼 빼고 그것보다 더 큰 레코드의 갯수만 가져오면 된다. 그리고 그것을 3이랑 비교해서 3 이상이다, 그럼 차단을 시키는 로직.
전달받은 lookBackSeconds만큼 현재시간에서 뺀 그 값보다 일시가. createdAt 값이 더 큰 레코드를 선택해야한다.
SystemService 에서 checkActivityAndBan메서드 작성. 맨 위 상수 생성
ISystemMapper 에서 selectBannedIpCountByIpAll
여태 차단된 이력이 없는 ip라면 0이 나오겠고, 악질적인 ip였다면 그 이상이 나오겠다.
xml에서 selectBannedIpCountByIpAll
SystemService의 checkActivityAndBan 에 차단 구현
public static final int BAN_MINUTES = 10; 추가 후
초범은 기본 10분 차단. 2범이면 40분 차단. 3범이면 270분 차단.
ISystemMapper 에서 insertBannedIp 추가 후
SystemService 에서
xml에서 insertBannedIp 쿼리 작성
가용성을 넓히려면 index제외 모든 걸 명시해주는게 좋다. default를 놔두게 되면 다른 값이 들어가길 원할 때 따로 쿼리를 짜줘야 하고 그럼 귀찮으니까.
로그인 5번 틀리고 밴드 들어갔을때 아이피가 차단되면 된다. 아무것도 안됨 아직까지는
SystemService 에서 isIpBanned 메서드 생성
ISystemMapper 에서 selectBannedIpCountByIp
xml에서 selectBannedIpCountByIp
expires_at > NOW() 만료되는 일자가 지금보다 미래일 것.
expired_flag = FALSE (현재 유효한가에 대한 여부. 기본이 FALSE)
즉 만료되지 않은 규칙만 가져온다는 것.
SystemService
0을 초과하면 차단이 되었다는 것
banned.html
차단되었음을 알린다.
RootController
StandardController를 상속 후 @Autowired
이 사람이 차단을 당했는지 안 당했는지 확인이 필요하니 request는 항상 필요할 것이고, this.systemService.isIpBanned(request) 가 banned 상태라면 banned로 이동시켜주면 된다. return modelAndView.
모든 맵핑에 대해 이 구문을 일일히 추가하지 않기 위해 인터셉터를 사용해본다. 위 내용지우고
컨트롤러로 들어가기 전에 우리가 지정한 주소 패턴에 따라서 앞에 장막을 하나 만들어 놓는다.
인터셉터는 컨트롤러의 계속된 진행을 허가하거나 거절 할 수 있고,
세션 처리나 보안 처리 등에 이용한다
interceptors 패키지 만들고 SystemInterceptor 생성
^O 단축키
preHandle
: 컨트롤러로 넘기기 전에 취해야하는 조치를 하는 것.요청이 들어오면 컨트롤러보다 인터셉터가 먼저 받는데 인터셉터가 컨트롤러로 보내기 전에 실행하는게 pre.
boolean타입을 반환. 즉 true면 컨트롤러로 진행. false면 컨트롤러로 진행 X. 따라서 보안 결격 사유가 발생하면 return false를 해주면 컨트롤러로 넘어가지 않는다.
postHandle
: 컨트롤러까지 갔다가 다시 나올 때컨트롤러에서 필요한 조치를 다 취하고 빠져나갈 때 실행하는 건 post.
SystemInterceptor는 @Autowired를 쓸 수 있기는 하지만 구조 상 생성자를 통한 @Autowired를 쓰지는 못한다.
그래서 private SystemService로.
직접 객체화하지 못함. 결론적으로 @Autowired에 의해 객체화 되어야 하는데, 생성자가 아닌 멤버변수 자체에 @Autowired가 붙어있으면 된다.
prehandle은 request가 자동으로 있다. 시스템서비스에서 해당 아이피가 차단되었는지에 대한 여부를 확인하는 메서드 isIpBanned 에
request를 전달해서 만약 ip 차단이 확인되면 return false를 주어 컨트롤러로 넘어가지 않게 한다. 그런데 그냥 흰 화면만 보게 되기 때문에 sendRedirect를 사용하여 경로를 지정해준다. 그 외의 경우엔 return true를 해준다. 즉 정상적으로 진행해라.
인터셉터는 특정 주소 패턴에 대해 광범위하게 작동. 모든 주소에 대해 작동하지 않기 때문에 등록시켜주어야 하는데,
configs 패키지 만들고 WebMvcConfig 라는 클래스를 만든다.
얘는 설정 파일이다 라는 내용을 주기 위해 @Configuration 어노테이션을 주고 WebMvcConfigurer을 구현.
^O 단축키로 addInterceptors 선택.
SystemInterceptor systemInterceptor = new SystemInterceptor(); 를 하게 되면 이건 우리가 직접 객체화하는 것.
스프링이 직접 객체화하게 하려면 일단 SystemInterceptor()를 반환하는 메서드를 만들어주고, return new SystemInterceptor()라고 적는건 같은데. 우선 이렇게까지만 해도 우리가 직접 객체화 한 것이다.
그런데 이걸 내가 직접 객체화 하는게 아니라 스프링이 객체화하여 주어야 한다라는 의미를 부여하기 위해서는 메서드 위에 @Bean 어노테이션을 붙여주면 된다. @Bean을 붙이지 않으면 @Autowired에 경고가 들어올 것.
스프링이 인식 가능한 범위 내에 있는 클래스가 아니라는 것.
그리고 SystemInterceptor()를 전달받게 되면 깔끔하게 객체화가 되어 돌아온다.
모든 경우에 대해 ip 체크를 해야하기에 .addPathPatterns("/**")해주고 banned는 예외로 해준다. 또한 시스템 과부화를 막기 위해 resources로 시작하는 모든 경로도 다 예외 처리 해준다. css html js 전부 체크하게 되면 페이지 로드 속도가 너무 떨어지기 때문.
이제 주소가 차단된 상태라면 어느 주소로 들어가도 banned로 redirect가 될 것. banned 주소로 들어가면 해당 인터셉터가 작동하지 않을 것. 왜? 우리가 예외로 해두었으니까.
인터셉터 웹 mvc 설정.. 어렵다..
어떤 인터페이스를 구현해야 하는가
빈을 왜 등록해야하는가
어떤 메서드를 만들어야하는가 이런 것은 일단은 외워야 한다.
외우고 시작하자.
로그인 5번 실패하면 system_banned_ips 레코드가 추가되고
banned로 가진다.
SystemInterceptor 에서
response.sendRedirect("/banned"); 를
모델앤뷰로 가져가면
ModelAndView bannedModelAndView = new ModelAndView("/banned");
throw new ModelAndViewDefiningException(bannedModelAndView);
throw도 그 자리에서 바로 종료이다. throw 하면 return true 가 작동하지 않음.
redirect 방식으로 해도 상관은 없음.
주소를 유지한다.
8080앞에 어떤 경로를 붙여도 페이지가 유지된다.
오류를 발생시키는 임의의 메서드
RootController에 getRaiseError 추가
사이트에 접속하는 사람들에게 이런 페이지를 보여주면 안된다. 접근성이 상당히 떨어진다는 것도 문제고, 이런 시스템의 예외적인 오류 같은 내용들을 당연히 노출시키면 안된다. 이런 걸 보여주면 안되기 때문에 모든 맵핑에 또 마찬가지로 trycatch해서 캐치에 걸리는 모델앤뷰의 이름을 바꾸고 리턴해주고 그럴 수 없기 때문에
인터셉터랑 느낌 비슷하게 전역적으로 처리할 것이다.
StandardController 에서 handleException 메서드 만들고 Exception이라는 매개변수를 받도록 한다.
StandardController를 상속하는 모든 컨트롤러에서 발생하는, 혹은 그 이하 서비스나 모델에서 아니면 매퍼에서 발생하는 모든 예외는 이제 얘가 다 처리할 것. 그렇게 하기 위해는 ExceptionHandler라는 어노테이션을 추가해주어야 하고, 추가로 value로 이 메서드는 이러한 예외만 처리를 한다라고 정의 해줄 수 있다. 하지만 지금은 통합해서 Exception이다 라고 해주고, 뒤에 type of class를 넣어야 하기 때문에 .class까지 적어준다.
이 handleException이라는 메서드는 모든 예외에 대한 최후의 보류이기 때문에 handleException안에서는 예외가 밖으로 세어 나가서는 안된다.
고로 이 안에서 조치하는 모든 오류는 이 안에서 끝나야 한다.
catch에서는 안전하게 아무것도 하지 않겠다.
오류가 여기에서도 뜨지 않으면 개발이 불가능하니
오류 발생을 알기 위해 sout하여 getMessage해주고
printStackTrace를 해준다.
templates에 error.html 만들어주고 오류페이지라 지정.
예외가 발생해서 발생하는 문제들에 대해서는 얘가 처리하는 것이고, 404는 엄밀히 얘기하면 여기서 다룰게 아니다. 404는 예외가 아니다
어찌됐든 실제에서는 서비스 이용에 불편을 드려 죄송하다는 내용을 띄워주면 되겠다.
컨트롤러 별로 적용이 된다. 예를 들어 ExceptionHandler라는 내용이 UserController에 들어있었다면 RootController는 이 처리내용에 대해 적용받지 못할 것이다. 하지만 지금은 StandardController에 넣어두고 나머지 컨트롤러가 이를 상속받으니 똑같이 작동을 하는 것.
SystemService에서 putExceptionLog 추가. insert를 할 것.
Exception을 전달받는다.
자바가 가진 예외는 StackTrace를 가지는 문자열 값이 없기 때문에 가상의 화면에 찍는다, 가상으로 하나 더 만들어 거기다 찍어두고 찍은 내용을 스트링라이터로 가져온 다음 String으로 가져오는게 방법이었으나, DateUtils를 쓰기 위해 추가했던 의존성 안에 ExceptionUtils 라는 것이 있다. 사용
SystemExceptionLogEntity 만들어 필드 추가. 게터세터까지.
SystemService 로 돌아와 객체화
ISystemMapper에 insertExceptionLog
xml
StandardController
SystemService
MVC에서
모델 안에 서비스랑 다오가 있는 것처럼
컨트롤러 그룹 안에 컨트롤러랑 인터셉터가 있다.
Config는 톰캣 구동될 때 딱 한번 읽어들이고 적용시키는 내용일 뿐. 설정인 것. application.properties했던 것처럼.