WinstoneでOpenID4Java
openid4javaライブラリは、自前でディスカバリを走らせるのでロジックだけのテストができない。しょうがないのでWinstoneを使って、JUnitからテスト前にOPを起動させてみた。*1
leathersole's openid4javatest at master - GitHub
http://github.com/leathersole/openid4javatest
eclipseで、自動ビルド先をプロジェクト/test-resources/WEB-INF/classes と指定して、OpMock#createProvider()から起動。
OPとRPのコードは、サンプルのsample-openidを編集している。Winstoneでjspをつかうことすら面倒だったので、全部Servlet化。
public class OpMock { public void createProvider() throws IOException { Map<String, String> prop = new HashMap<String, String>(); prop.put("webroot", "test-resources"); prop.put("httpPort", "28080"); Launcher.initLogger(prop); final Launcher winstone = new Launcher(prop); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { winstone.shutdown(); } })); } }
今のテストのターゲットは、RPの内部ロジックなので、RPはまだWinstone起動の対象にはしていない。SpringのMockHttpServletRequest/MockHttpServletResponseを使って、直にServletを呼び出している。とはいえ、簡単に起動できるだろう。
テストコードは最後に載せるが、長い。アサーションが多くて、テスト独立性のポリシーにも反している。途中までのテストを分割することはできるが、OPでなされた認証をRPで検証するためには、この一連の処理が必要だろうなぁ。
今回は、最初にOPが動くまで2時間程度。前Winstoneを試したときは、ちょっと制約が多い気がしてたけど、割り切ってしまえば簡単なものだな。
やるかわからない残タスク:
・ネガティブアサーション対応
・ax対応
・sreg対応
・pape対応
・RPもWinstoneで動かす
・antビルド対応
・war作る
・hudsonのように、Winstone内蔵のwarにする
参考サイト:
Parsing query strings in Java - Stack Overflow
Final: OpenID Authentication 2.0 - 最終版
Winstone Servlet Container
軽量サーブレットコンテナ winstone を開発用にサクッと使う - etc9
テストコード:
public class ConsumerServletTest { /** * ConsumerServletにて認証をテストする。 * * @throws Exception */ @Test public void authorizeTest() throws Exception { // OPを起動 OpMock opMock = new OpMock(); opMock.createProvider(); // ConsumerServlet用のMockを生成 MockServletConfig config = new MockServletConfig(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); // ConsumerServletをテスト ConsumerServlet servlet = new ConsumerServlet(); servlet.init(config); request .addParameter("openid_identifier", "http://localhost:28080/xrds"); servlet.doPost(request, response); AuthRequest authReq = servlet.getAuthReq(); assertEquals("http://localhost:28080/provider", authReq.getOPEndpoint()); assertEquals("checkid_setup", authReq.getParameterValue("openid.mode")); HttpSession session = request.getSession(); Map parameterMap = authReq.getParameterMap(); // OPにて認証を実行 String returnTo = authorizeByOp(authReq, parameterMap); // ConsumerServletに戻る String returnToUri = returnTo.substring(0, returnTo.indexOf("?")); String returnToQueryString = returnTo.substring( returnTo.indexOf("?") + 1, returnTo.length()); Map<String, String> queryMap = getQueryMap(returnToQueryString); MockHttpServletRequest request2 = new MockHttpServletRequest(); MockHttpServletResponse response2 = new MockHttpServletResponse(); for (String key : queryMap.keySet()) { request2.setParameter(key, URLDecoder.decode(queryMap.get(key), "UTF-8")); } request2.setSession(session); request2.setQueryString(returnToQueryString); servlet.doGet(request2, response2); } private String authorizeByOp(AuthRequest authReq, Map parameterMap) throws IOException, HttpException { HttpClient client = new HttpClient(); PostMethod post = new PostMethod(authReq.getOPEndpoint()); for (Object key : parameterMap.keySet()) { post.addParameter(key.toString(), parameterMap.get(key).toString()); } int statusCode = client.executeMethod(post); assertEquals(302, statusCode); Header locationHeader = post.getResponseHeader("location"); String location; location = locationHeader.getValue(); assertEquals("http://localhost:28080/provider_authorization", location); GetMethod get = new GetMethod(location + "?action=authorize"); get.setFollowRedirects(false); statusCode = client.executeMethod(get); assertEquals(302, statusCode); Header completeHeader = get.getResponseHeader("location"); String complete = completeHeader.getValue(); GetMethod get2 = new GetMethod(complete); get2.setFollowRedirects(false); statusCode = client.executeMethod(get2); Header authorizedHeader = get2.getResponseHeader("location"); String returnTo = authorizedHeader.getValue(); return returnTo; } public static Map<String, String> getQueryMap(String query) { String[] params = query.split("&"); Map<String, String> map = new HashMap<String, String>(); for (String param : params) { String name = param.split("=")[0]; String value = param.split("=")[1]; map.put(name, value); } return map; } }
*1:かなり(仕事的にも)今更なネタではある。