Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Java로 OpenID Consumer 서비스 구현하기 #2 : OpenID4Java로 인증요청하기

(이 글은 Java로 OpenID Consumer 서비스 구현하기 #1에 이은 글입니다.)

OpenID는 직접 인증을 하지 않고 아이디를 던져주면 인증결과만 돌려받는다는 아주 기초적인 개념만을 가지고 하자고 덤볐는데 생각보다 Consumer쪽에서 구현할께 많았다. 하긴 무식한게 용감하다고 조금만 깊게 생각해 보아도 절대 그럴리가 없는 것인데 왜 쉽게 구현할 수 있다고만 생각한건지....

어쨌든 막상 적용할려고 하니까 멀 해야할지 상당히 막막했고 이것저것 자료를 찾아보기 시작했다. 혼자 처음부터 구현한다는 건 말도 안되고 예상대로 라이브러리들이 많이 만들어져 있었다. 내 환경은 Java환경이었기 때문에 나는 Java 라이브러리를 골라야 했고 위의 사이트에서 3개의 라이브러리가 있다는 걸 알게 되었다. Sxip에서 만든 OpenID4Java와 Verisign에서 만든 joid, WSO2에서 만든 WSO2 OpenID가 있었다.

나같은 초보한테는 라이브러리뿐만 아니라 라이브러리를 사용할 수 있는 튜토리얼이나 강좌들이 절대적으로 필요했기 때문에 고르기가 쉽지 않았다. 이것저것 검색을 많이 해봤지만 라이브러리의 특징을 비교해 놓은 자료는 찾기가 쉽지 않아서 선택하기가 어려웠다. 그나마 제일 오래된 것처럼 보이는 OpenID4Java가 관련 자료가 제일 많아 보였는데 그러다가 "Daum 오픈ID 서비스의 시작을 조금 늦춥니다."라는 글에

Daum 오픈 ID 서비스는 java-openid-sxip.jar 오픈소스 라이브러리를 이용합니다. SXIP 사에서 주도적으로 개발하고 있는 오픈ID 라이브러리 입니다. Daum 내부적으로 java 를 많이 사용하고 있어서 채택된 점도 있습니다. Verisign 의 java 라이브러리도 있고, IDprism 도 있는데요. 많은 고민하지 않고 이 라이브러리를 그냥 선택했습니다.

오픈ID 컨슈머 (RP) 와 오픈ID Provider는 서명 검증을 위해서 mac key 를 교환합니다. 이 라이브러리는 mac key 와 관련된 Association 정보들을 DB 에 저장할 수 있는 인터페이스를 제공합니다. 오픈ID 서버의 서버 확장성을 위해서는 이 기능이 꼭 필요합니다. 그런데, 이 라이브러리가 좀 문제를 가지고 있더군요. Association Handle 을 지나치게 많이 새로 만들고 있습니다. 재 사용이 가능하다면 재사용하는 것이 맞는데요. DB 에 association record 가 자꾸 쌓이게 되더군요. 오픈ID Provider 의 시스템을 보호하기 위한 부분이 미흡해 보여서, 수정했습니다.

또, 이 라이브러리가 오픈ID 2.0 스펙을 구현하면서, 1.1 스펙에서 디폴트로 정의되어 있는 필드들에 대해서 전달이 되지 않으면 오류 처리하는 부분이 있더군요. 역시 수정했습니다. 대표적으로 Associate 요청에서 요청 필드는 "openid.mode" 만 필수 입니다. 그런데, "openid.mode" 와 "openid.session_type" 까지 모두 필수 항목으로 요청하더군요. openid.session_type은 default 값이 정해져 있거든요.


라는 내용이 있어서 OpenID4Java를 사용하기로 맘을 먹었다. 난 서버를 만들건 아니고 Consumer를 만들거긴 하지만 Daum에 대한 신뢰와 여러 자료의 지원으로 인하여.. ㅎㅎㅎ

일단 Consumer를 구현하는데 있어서 Rath님J2EE 환경에서 OpenID 지원 사이트 구축해보기라는 강좌가 아주 큰 도움이 되었다. 이 강좌만큼 잘 정리된 내용은 거의 찾아볼 수가 없었다. (Rath님 감사~ ㅎㅎ)




OpenID4Java에 대해서 얘기하기 전에 일단 로그인 폼부터 보자. 아이디를 넘겨받아야 하니까....


<!-- loginOpenid.jsp -->
<form name="openidLoginForm" action="loginOpenidProc.jsp" method="post" >
    <input id="openid" name="openid_identifier" type="text" style="background:transparent url(../images/openid_icon.gif) no-repeat left center; border: 1px solid #2B87DD; height:20px; padding-left: 18px; padding-top: 2px" />
    <input type="submit" name="login" value="로그인" />
</form>

위의 모습이 OpenID의 기본적인 로그인폼이다. 비번을 받지 않기 때문에 id란만 있는 일반적인 form태그이다. 오픈아이디의 input text의 name은 openid_identifier라는 이름을 사용한다. OpenID 1.1에서는 openid_url이라는 이름을 사용했지만 2.0에서는 openid_identifier라는 이름을 사용하고 있다. 이렇게 함으로써 사용자들은 자동완성기능을 사용할 수 있고 로그인 박스에  오픈아이디 로고도 필수사항으로 적용하도록 하고 있다. 이것은 기능적인 부분은 아니지만 위에 말했듯이 사용자들의 UX를 지켜주기 위함이다.

Openid 로그인


로그인 입력폼에 대해서는 OpenID 적용사이트 권장/표준 스타일 가이드에 잘 정리되어 있다.(권장UI) 보다시피 이 페이지는 오픈아이디를 입력받아 다음페이지로 넘겨주는 역할만을 할 뿐이다. 기본적인 준수사항만 지키면 다른 UI적인 디자인은 사이트에 맞게 해도 상관없다.






이제 OpenID인증을 위해서 Sample Consumer를 만들어보자. 위에 말했듯이 주요골자는 rath님의 강좌를 보고 만들었다. rath님은 기본적인 지식을 가지고 있다고 생각하고 강좌를 쓰셨기 때문에 기본적인 환경셋팅에 대해서 OpenID 빌드하기라는 글에 정리된 내용도 있다. 자세한 설명은 rath의 글을 참고하시기를... 난 메서드까지 다 이해하고 구현한 것은 아니기 때문에.....


내 환경은 JDK 1.5에 Tomcat 5.5였다. ant는 깔긴했는데 없어도 그다지 상관이 없는 것 같다. 하면 더 좋겠지만 ant는 쓸 줄을 몰라서.... 어쨌든 시작부터 막혔다. openid4java 공식 사이트가 있기는 하지만 생긴지가 오래 안되었는지 대부분의 링크는 Google Code를 가르키고 있었다. 그래서 나도 구글코드에 갔는데 거기엔 소스가 없었다. 이전 버전의 소스만 있기는 했는데 deprecate가 찍혀 있어서 왠지 다운받기가 꺼름칙했다. ㅡ..ㅡ 그래서 별수 업이 공식사이트에서 Openid4java 0.9.4.339버전을 다운받았다. 따라하는 만큼 강좌와 버전을 맞추고 싶었는데 별수없이... 그냥 다운받아서 압축을 풀면된다. 난 윈도우니까 tar.gz라서 2번풀어줬다.

openid4java


OpenID4java를 사용하기 위한 환경셋팅에도 엄청 헤맸다. openid4java를 위한 많은 jar파일들이 필요했는데 위의 폴더에서 lib폴더에 들어 있다. 난 그냥 lib안에 있는 모든 jar 32개와 java-openid-sxip-0.9.4.jar를 다 복사해서 톰캣폴더\common\endorsed 나 openid를 사용하는 폴더의 WEB-INF안에 lib안에 넣었다. 어느쪽에 넣던지 작동하는데는 문제가 없다.(사실log4j-1.2.8.jar가 에러가 나길래 요것만 빼버렸다. ㅡ..ㅡ) NoClassDefFoundError가 떴었는데 그냥 다 넣어버리니까 괜찮아졌다.(이런식으로 개발하면 안되는데.. ㅡ..ㅡ)

build.xml이 있어서 소스 수정해서 ant로 컴파일해도 되지만 나는 그럴 능력이 안되서 있는대로 갔다 붙혔다. 필요한건 그냥 새로 만들고... ㅡ..ㅡ

이제 Sample Consumer를 만들어야 한다. Sample Consumer는 오픈아이디를 받아서 가공하고 처리한 뒤에 OP측에 인증요청을 하는 authRequest()와 OP측에서 End-User가 인증을 받아서 돌아오면 인증확인을 하는 verifyResponse()로 이루어져있다. 이것만으로도 인증처리를 할 수 있다. 정신없이 찾아보다가 괜찮아 보이는 소스를 발견하고 열심히 타이핑해서 만들었는데 java-openid-sxip-0.9.4.jar안에 들어 있다. 정확히는 위의 폴더구조의 src안에 들어 있다. 더 충격적인 것은 원래 있는 Sample Consumer는 작동되지 않는단다.(뭐 샘플이.. ㅡ..ㅡ 이것때문에도 엄청 시간소비를.. 크게 바꾼것도 없긴 하지만..)

어쨌든 내 SampleConsumer의 authRequest부분이다. 넣어놓은 jar는 그대로 두고 Sample Consumer 클래스를 따로 만들어서 사용했다. 전체 소스는 나중에 올리고 일단...


// SampleConsumer.java
package kr.test.openid;

import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.Identifier;
import org.openid4java.discovery.Discovery;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.*;
import org.openid4java.server.RealmVerifier;
import org.openid4java.OpenIDException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.io.IOException;


public class SampleConsumer{   
    public ConsumerManager manager;   
    public SampleConsumer() throws ConsumerException    {               
        manager = new ConsumerManager();   
    }   

    public String authRequest(String userSuppliedString, HttpServletRequest httpReq, HttpServletResponse httpResp) throws IOException {       
        try {                       
            String returnToUrl = "http://localhost:8090/OpenId/openid/resultOpenid.jsp";   
            String trustRoot  = "http://localhost:8090/OpenId/";

            Identifier identifier = Discovery.parseIdentifier(userSuppliedString);
            userSuppliedString = identifier.getIdentifier();

            List discoveries = manager.discover(userSuppliedString);                       
            DiscoveryInformation discovered = manager.associate(discoveries);

            RealmVerifier rv = new RealmVerifier();
            rv.setEnforceRpId(false);
            manager.setRealmVerifier(rv);

            httpReq.getSession().setAttribute("openid-disc", discovered);         

            AuthRequest authReq = manager.authenticate(discovered, returnToUrl, trustRoot);

            if (! discovered.isVersion2() ) {       
                httpResp.sendRedirect(authReq.getDestinationUrl(true));               
                return null;           
            } else {               
            }       
        } catch (OpenIDException e) {           
            // present error to the user       
        }       
        return null;   
    } 
}

자세한 내용은 rath님의 글을 참고하시길.. 대부분의 내용은 openid4java의 jar파일안에 들어있는 SampleConsumer와 비슷하다. returnToUrl은 OP로부터 인증을 맏은 후에 돌아올 곳의 링크이고 이곳에서 인증확인처리를 해주어야 한다. trustRoot는 내 사이트의 루트디렉토리라고 할 수 있다. returnToUrl은 반드시 trustRoot밑에 있어야 한다. *.outsider.ne.kr같은 와일드카드도 먹고 blog.outsider.ne.kr로 trustRoot를 주면 returnToUrl도 같은 주소로 시작해야 한다.

함수의 대한 설명은 내 능력은 다하기가 좀 어렵다. 괜한 잘못된 정보를 주게될까봐... ㅎㅎㅎ 37~39줄은 0.9.4에서는 꼭 필요한 부분이다. 그 이전버전에서는 안그랬다는 것 같은데 0.9.4에서는 trustRoot에 대한 확인과정을 거친다.(openid4java에서는 trustRoot를 Realm이라고 부른다.) rath님의 포스팅에 따르면 trustRoot에서 xrds를 찾는다고 하는데 세부내용은 잘 모르겠다. 어쨌든 Realm확인을 하면서 확인을 못하고 아래와 같은 에러가 나온다.

사용자 삽입 이미지

이 문제를 해결하려면 RealmVerifier를 하지 않게 하는 다음의 3줄 코드를 넣어야 한다.


RealmVerifier rv = new RealmVerifier();
rv.setEnforceRpId(false);
manager.setRealmVerifier(rv); 






이제 authRequest를 사용하자.


// loginOpenidProc.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<% request.setCharacterEncoding("utf-8"); %>
<% response.setContentType("text/html; charset=utf-8"); %>
<%@ page import="kr.test.openid.SampleConsumer" %>
<%
    String userInput = request.getParameter("openid_identifier");

    SampleConsumer sc = new SampleConsumer();
    application.setAttribute("cm", sc);
    sc.authRequest(userInput, request, response);
%>

위 소스는 loginOpenid.jsp의 폼서밋을 받아서 처리하는 페이지이다. 아이디 받아오고 SampleConsumer객체를 만들어서 authRequest를 호출하면서 받아온 openid와 현재의 request, response를 파라미터로 넘겨준다. 만든 SampleConsumer 객체를 application에 넣어준 것은 추후에 다시 설명하도록 하겠다.

이렇게 만들어 놓은 뒤에 loginOpenid.jsp파일에서 openid를 입력하고 로그인 버튼을 누르면 OP측으로 연결이 되면 제대로 된 것이다.

사용자 삽입 이미지

일반적으로 볼수 있는 OP의 인증화면이 나타난다. 어설프게나마 내가 짠 소스로 연결되었을 때의 기쁨은... ㅎㅎㅎㅎㅎ

사용자 삽입 이미지



trustRoot로 넣은 URL이 뜨면서 인증요청페이지가 나타난다. OpenID의 방식에 대해서 설명했듯이 OP가 페이지를 바꾸는 것이 아니라 End-User가 인증을 받아서 다시 trustRoot의 페이지로 접속을 하는 것이기 때문에 localhost여도 상관이 없다. 승인버튼을 누르면 returnToUrl로 지정된 곳으로 이동이 된다. 이 페이지에서 인증확인처리를 해주어야 한다.


글이 길어진 관계로... 인증확인 부분은 다음편에....

2008/07/13 03:42 2008/07/13 03:42