Spring Bootを使ってXMLなしでkaptchaを使う


## 概要
* [Spring Boot](http://projects.spring.io/spring-boot/) で [kaptcha](https://code.google.com/p/kaptcha/) を使う
* kaptchaは通常XML設定ファイルを使うけど、せっかくSpring Bootを使うのでXMLファイルなしでkaptchaを使いたい
* アプリケーションはGroovy + Gradleで

## build.gradle
ポイントは、kaptchaがMavenリポジトリにないので、jarをlib以下に入れて依存関係に追加しているとこ。lib以下の全jarを対象にしているのは手抜き。

```gradle
（省略）

dependencies {
  compile fileTree(dir: 'lib', include: '*.jar')
}

（省略）
```

## Bean定義のクラスを作る
kaptchaの例だと、web.xmlでservletに kaptcha.jpg をマッピングしているのを、 **@Configuration** で指定したクラスで設定する。

```groovy
@Configuration
public class AppConfig {
  @Bean
  public ServletRegistrationBean kaptcha() {
   new ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg")
  }

}
```

## 入力データクラスを作る
画面から受け取るのは、kaptchaの文字列だけ。
かつ、Groovyなのでsetter/getterも不要。ただし、属性を **def** で宣言しちゃうと、Object型になって実行時にエラーになっちゃう。

```groovy
public class KaptchaFormData {
	@NotNull
	@Size(min = 5, message = "入力した文字が短すぎます")
	String kaptcha
}
```

## kaptcha解決のサービスクラスを作る
といっても、文字列同士を大文字/小文字関係なく比較するだけ。

```groovy
@Service
public class SolveKaptchaService {

  public boolean solve(String expected, String actual) {
    actual.equalsIgnoreCase(expected)
  }
}
```

## Controllerクラスを作る
kaptchaActionメソッドでリクエストを受ける。入力値が短いとか、入力されてない、のはデータクラスに指定したアノテーション通りにチェック。

kaptchaActionメソッドは、HttpSessionオブジェクトも受け取るようになっていて、セッションからkaptchaの正解文字列を取得してくる。

あとは、入力文字列と正解文字列をServiceクラスに渡して完了。

```groovy
@Controller
public class KaptchaController {
  
  @Autowired
  SolveKaptchaService solveKaptcha
  
	@ModelAttribute("kaptchaFormData")
	KaptchaFormData setUp() {
		new KaptchaFormData()
	}
	
  @RequestMapping(value = "/", method = RequestMethod.GET)
  public String kaptcha() {
    "index"
  }
  
  @RequestMapping(value = "/", method = RequestMethod.POST)
  public String kaptchaAction(@Validated KaptchaFormData kaptchaFormData, BindingResult bindingResult, 
      Model model, HttpSession session) {
	  
	  if (bindingResult.hasErrors()) {
		  return "index"
	  }
	  
	  def expectedKaptcha = (String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY)
	  
	  if (!solveKaptcha.solve(expectedKaptcha, kaptchaFormData.kaptcha)) {
        model.addAttribute("exception", new Exception("入力した文字が画像と違います。"))
        return "index"
	  }
	  
    "success"
  }
}
```

## 画面
画面は、Thymeleafを使ってテンプレート化してる。

ポイントになるのは、kaptchaをクリックするたびに画像が変更されるようにしているとこ。Thymeleafの **th:inline"javascript"** を使ってるとこ。

```html
<!DOCTYPE html>
<html xmlns:th="http:///www.thymeleaf.org">
<head>
  <meta charset="UTF-8" />
  <title>kaptcha Login</title>
  <script th:src="@{/jquery-1.11.1.min.js}" />
</head>
<body>
<h1>ログイン</h1>
<form name="kForm" method="post" th:action="@{/}" th:object="${kaptchaFormData}">
  <div>
    <span th:if="${#fields.hasErrors('kaptcha')}" th:errors="*{kaptcha}" style="color: red;">error!</span>
    <span th:if="${exception != null}" th:text="${exception.message}" style="color: red;">error!</span>
  </div>
  <div><img th:src="@{/kaptcha.jpg}" width="200" id="kaptchaImage" /></div>
  <script th:inline="javascript">
    var kaptchaUrl = /*[[@{/kaptcha.jpg}]]*/ "/kaptcha.jpg";
    $(function(){
      $('#kaptchaImage').on('click', function() {
        $(this).attr('src', kaptchaUrl + '?' + Math.floor(Math.random()*100) );
      })
    });
  </script>
  <div>
    <input type="text" name="kaptcha" value="" th:field="*{kaptcha}" />
  </div>
  <div>
    <button id="loginButton" onCLick="document.kForm.submit();">ログイン</button>
  </div>
</form>
</body>
</html>
```

## 完成した画面
こんな感じで画像が表示される。

![kobito.1417687627.261864.png](https://qiita-image-store.s3.amazonaws.com/0/23683/6d064544-305e-b87d-276c-948ff77caed2.png "kobito.1417687627.261864.png")


## 参照
ここまでの全ソースコードは、以下のリポジトリに置いてあります。
* [SpringBootCaptcha](https://github.com/mas0061/SpringBootCapcha)