::SpringBoot:: 텍스트 기반 captcha 구현하기

MinJeongKim·7일 전
0

SpringBoot

목록 보기
4/4
post-thumbnail

1. 자동화 공격 방지

Intro

<설명>
웹 서비스에서 제공하는 기능을 이용하여 자동화 공격이 가능한 취약점이다. 공격자는 웹 서비스의 데이터 등록, SNS/메일 발송 등의 기능을 자동으로 수행하는 도구를 이용하여 서비스 거부, 스펨 데이터 전송 등의 가능하다.

<대응방안>
특정 시간 내 동일 프로세스를 반복 실행하지 못하도록 시간제한 설정을 하거나, 자동화 공격에 의한 시스템 과부하를 방지하기 위해 다량 패킷 유입 시 해당 접속을 차단하는 것을 권장한다. 또한, 캡챠 기법 등을 이용하여 입력값에 대한 검증을 함으로써 자동화 도구를 통한 공격을 방어해야 한다.

1.1. captcha library 추가

* 제일 최신 버전이 java6 참고
https://simplecaptcha.sourceforge.net/

1.1.1. lib 폴더에 jar 파일 추가

1.1.2. gradle 파일 수정

dependencies {

    implementation project(':dev-core')

    implementation files("lib/simplecaptcha-1.2.1.jar")
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    ...
}

1.1.3. gradle reload


1.2. source 구현

1.2.1. dao 구현

package com.captcha.dao;

import nl.captcha.text.producer.TextProducer;

public class CaptchaDao implements TextProducer {
    private String str;

    public void SetTextProducer(String getAnswer) {
        this.str = getAnswer;
    }

    @Override
    public String getText() {
        return this.str;
    }
}

1.2.2. controller 구현

@Slf4j
@Controller
@RequestMapping("/captcha")
public class CaptchaController {

    @GetMapping
    public ModelAndView Captcha(@RequestParam Map<String, Object> param) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addAllObjects(param);
        modelAndView.setViewName("empty:/captchaValid");

        return modelAndView;
    }

    @GetMapping("/captchaImg")
    @ResponseBody
    public void captchaImg(HttpServletRequest req, HttpServletResponse res) throws Exception {
        new CaptchaService().getImageCaptcha(req, res);
    }

    // 사용자가 입력한 문자열 check
    @PostMapping("/checkAnswer")
    @ResponseBody
    public String checkAnswer(HttpServletRequest req, HttpServletResponse res) {
        String result = "";

        Captcha captcha = (Captcha) req.getSession().getAttribute(Captcha.NAME);
        String answer = req.getParameter("answer");

        if(answer != null && !"".equals(answer)) {
            if(captcha.isCorrect(answer)) {
                req.getSession().removeAttribute(Captcha.NAME);
                result = "200";
            } else {
                result = "400";
            }
        }

        return result;
    }

}

1.2.3. service 구현

public class CaptchaService {
    // 이미지 가로, 세로 크기
    private static int width = 150;
    private static int height = 50;

    /* captcha 이미지 생성 */
    public void getImageCaptcha(HttpServletRequest req, HttpServletResponse res) {
        /* font & color */
        List<Font> fontList = new ArrayList<Font>();
        fontList.add(new Font("", Font.HANGING_BASELINE, 40));
        fontList.add(new Font("Courier", Font.ITALIC, 40));
        fontList.add(new Font("", Font.PLAIN, 40));

        List<Color> colorList = new ArrayList<Color>();
        colorList.add(Color.BLACK);

        Captcha captcha = new Captcha.Builder(width, height)
                .addText(new NumbersAnswerProducer(6), new DefaultWordRenderer(colorList, fontList))
                .addNoise().addBorder()
                .addBackground(new GradiatedBackgroundProducer())
                .build();

        /* session 객체에 저장 */
        req.getSession().setAttribute(Captcha.NAME, captcha);
        CaptchaServletUtil.writeImage(res, captcha.getImage());
    }
}

1.2.4. view page 구현

<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<section class="join-contents is-small has-large-gap-top">
  <div class="join-completed-wrap">
    <div class="join-completed-contents">
      <svg class="icon is-blue-check is-medium"><use href="#check_blue"></use></svg>

      <h2 class="page-title is-large">자동 입력 방지</h2>
      <div style="overflow: hidden">
        <div>
          <img id="captchaImg" title="CaptchaImg" src="">
        </div>
      </div>
      <div style="padding:3px">
        <input id="reload" type="button" onclick="javaScript:getImage()" value="새로고침">
      </div>
      <div style="padding:3px">
        <input id="answer" type="text" value="">
        <input id="check" type="button" value="확인">
      </div>
    </div>
  </div>
</section>

<script type="text/javascript">
    $(document).ready(function (e) {
        getImage();
    });

    function getImage() {
        let rand = Math.random();
        let url = "/captcha/captchaImg?rand=" + rand;

        $("#captchaImg").attr('src', url);
    }

    $(document).on('click', '#check', function (e) {
      $.ajax({
        url : '/captcha/checkAnswer',
        type : 'POST',
        async: false,
        data: {answer: $("#answer").val()}
      }).done(function(result) {
        if(result == 200) {
          let gbCd = `${gbCd}`;
          let queryString = null;

          if(gbCd == "T1") {
            queryString = `busiNo=${busiNo}&personNm=${personNm}&personEmail=${personEmail}`;

            $.ajax({
              url : '/company/getFindId',
              type : 'POST',
              data: queryString
            }).done(function(result) {
              if(!result || !Object.keys(result).length){
                goPost('/company/findFail','type','idFail');
              } else {
                goPost('/company/findComplete','loginId',result.email);
              }
            }).fail(function(jqXHR) {
              goPost('/company/findFail','type','idFail');
              return;
            });
          } else if(gbCd == "T2") {
            let queryString = `findLoginId=${findLoginId}&busiNo=${busiNo}&personNm=${personNm}&personEmail=${personEmail}`;

            $.ajax({
              url : '/company/getFindPwd',
              type : 'GET',
              data: queryString
            }).done(function(result) {
              if(!result || !Object.keys(result).length){
                goPost('/company/findFail','type','pwdFail');
              } else {
                goPost('/company/findComplete','email',result.email);
              }
            }).fail(function(jqXHR) {
              goPost('/company/findFail','type','pwdFail');
              return;
            });
          } else {
            alert('오류가 발생했습니다.');
            return;
          }
        } else {
          alert('입력값이 일치하지 않습니다.');
          getImage();
          $("#answer").val("");
        }
      });
    });


    function goPost(action,name,val){
        let form = document.createElement('form');
        let objs;
        objs = document.createElement('input');
        objs.setAttribute('type', 'hidden');
        objs.setAttribute('name', name);
        objs.setAttribute('value', val);

        form.appendChild(objs);
        form.setAttribute('method','post');
        form.setAttribute('action', action);
        document.body.appendChild(form);
        form.submit();
    }
</script>

1.3. 결과

출처: 헹창 2020.1.23, SimpleCaptcha를 이용한 이미지 보안문자 생성하기, https://haenny.tistory.com/106
profile
웹 개발자 & DA

0개의 댓글

관련 채용 정보