Saturday, July 14, 2012

使用openid4java實作Open ID RP (2)

本文接續上一篇,主要在實作程式碼的部份。

首先使用NetBeans建立一個新的Apache Wicket專案,並命名為myrp。依據原文所寫,作者將Web Application中負責和Open ID OP進行通訊的相關功能,放在model的套件中,其中分為兩個項目:



  1. model#RegistrationModel:實作各項需要Open ID OP所提供資料的bean類別。

    //model#RegistrationModel.java
    package model;
    
    import java.io.Serializable;
    import java.util.Date;
    
    public class RegistrationModel implements Serializable {
    
        //這是自訂的使用者資訊類別
        //未來可用在寫入資料庫的部份
        private String openId;
        private String fullName;
        private String emailAddress;
        private String zipCode;
        private Date dateOfBirth;
        private String favoriteColor;
    
        public String getOpenId() {
            return openId;
        }
    
        public void setOpenId(String openId) {
            this.openId = openId;
        }
    
        public String getFullName() {
            return fullName;
        }
    
        public void setFullName(String fullName) {
            this.fullName = fullName;
        }
    
        public String getEmailAddress() {
            return emailAddress;
        }
    
        public void setEmailAddress(String emailAddress) {
            this.emailAddress = emailAddress;
        }
    
        public String getZipCode() {
            return zipCode;
        }
    
        public void setZipCode(String zipCode) {
            this.zipCode = zipCode;
        }
    
        public Date getDateOfBirth() {
            return dateOfBirth;
        }
    
        public void setDateOfBirth(Date dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
        }
    
        public String getFavoriteColor() {
            return favoriteColor;
        }
    
        public void setFavoriteColor(String favoriteColor) {
            this.favoriteColor = favoriteColor;
        }
    
    
    }
    

  2. model#RegistrationService:將會使用到的openid4java函式庫中的各項方法以一類別來實作,並且提供static的呼叫方式。

    //model#RegistrationService.java
    package model;
    
    import java.util.HashMap;
    import java.util.List;
    import org.apache.wicket.request.IRequestParameters;
    import org.apache.wicket.request.mapper.parameter.PageParameters;
    import org.apache.wicket.request.mapper.parameter.PageParameters.NamedPair;
    import org.joda.time.YearMonthDay;
    import org.openid4java.consumer.ConsumerManager;
    import org.openid4java.consumer.InMemoryConsumerAssociationStore;
    import org.openid4java.consumer.InMemoryNonceVerifier;
    import org.openid4java.consumer.VerificationResult;
    import org.openid4java.discovery.DiscoveryException;
    import org.openid4java.discovery.DiscoveryInformation;
    import org.openid4java.discovery.Identifier;
    import org.openid4java.message.AuthRequest;
    import org.openid4java.message.AuthSuccess;
    import org.openid4java.message.MessageExtension;
    import org.openid4java.message.ParameterList;
    import org.openid4java.message.sreg.SRegMessage;
    import org.openid4java.message.sreg.SRegRequest;
    import org.openid4java.message.sreg.SRegResponse;
    
    public class RegistrationService {
    
        private static ConsumerManager consumerManager;
    
        //進行OpenID的相關探索動作
        @SuppressWarnings("unchecked")
        public static DiscoveryInformation performDiscoveryOnUserSuppliedIdentifier(String userSuppliedIdentifier) {
            DiscoveryInformation ret = null;
            ConsumerManager consumerManager = getConsumerManager();
            try {
                // Perform discover on the User-Supplied Identifier
                List<DiscoveryInformation> discoveries = consumerManager.discover(userSuppliedIdentifier);
                // Pass the discoveries to the associate() method...
                ret = consumerManager.associate(discoveries);
    
            } catch (DiscoveryException e) {
                String message = "Error occurred during discovery!";
                System.out.println(message);
                throw new RuntimeException(message, e);
            }
            return ret;
        }
    
        //建立OP進行驗證所需的AuthRequest物件
        public static AuthRequest createOpenIdAuthRequest(DiscoveryInformation discoveryInformation, String returnToUrl) {
            AuthRequest ret = null;
            try {
                // Create the AuthRequest object
                ret = getConsumerManager().authenticate(discoveryInformation, returnToUrl);
                // Create the Simple Registration Request
                SRegRequest sRegRequest = SRegRequest.createFetchRequest();
                sRegRequest.addAttribute("email", false);
                sRegRequest.addAttribute("fullname", false);
                sRegRequest.addAttribute("dob", false);
                sRegRequest.addAttribute("postcode", false);
                ret.addExtension(sRegRequest);
    
            } catch (Exception e) {
                String message = "Exception occurred while building AuthRequest object!";
                System.out.println(message);
                throw new RuntimeException(message, e);
            }
            return ret;
        }
    
        //1.3.3的PageParameters具有Map功能
        //1.5.3並沒有,所以要自己撰寫轉換程式
        public static ParameterList toParameterList(PageParameters p) {
            HashMap<String, String> h = new HashMap<String, String>();
            for (NamedPair pair : p.getAllNamed()) {
                h.put(pair.getKey(), pair.getValue());
            }
            return new ParameterList(h);
        }
    
        public static ParameterList toParameterList(IRequestParameters rP) {
            HashMap<String, String> h = new HashMap<String, String>();
            for (String name : rP.getParameterNames()) {
                h.put(name, rP.getParameterValue(name).toString());
            }
            return new ParameterList(h);
        }
    
        //處理OP回傳的資訊
        public static RegistrationModel processReturn(DiscoveryInformation discoveryInformation, PageParameters pageParameters, String returnToUrl) {
            RegistrationModel ret = null;
            // Verify the Information returned from the OP
            /// This is required according to the spec
            ParameterList response = new ParameterList(toParameterList(pageParameters));
            try {
                VerificationResult verificationResult = getConsumerManager().verify(returnToUrl, response, discoveryInformation);
                Identifier verifiedIdentifier = verificationResult.getVerifiedId();
                if (verifiedIdentifier != null) {
                    AuthSuccess authSuccess = (AuthSuccess) verificationResult.getAuthResponse();
                    if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
                        MessageExtension extension = authSuccess.getExtension(SRegMessage.OPENID_NS_SREG);
                        if (extension instanceof SRegResponse) {
                            ret = new RegistrationModel();
                            ret.setOpenId(verifiedIdentifier.getIdentifier());
                            SRegResponse sRegResponse = (SRegResponse) extension;
                            String value = sRegResponse.getAttributeValue("dob");
                            if (value != null) {
                                ret.setDateOfBirth(new YearMonthDay(value).toDateMidnight().toDate());
                            }
                            value = sRegResponse.getAttributeValue("email");
                            if (value != null) {
                                ret.setEmailAddress(value);
                            }
                            value = sRegResponse.getAttributeValue("fullname");
                            if (value != null) {
                                ret.setFullName(value);
                            }
                            value = sRegResponse.getAttributeValue("postcode");
                            if (value != null) {
                                ret.setZipCode(value);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                String message = "Exception occurred while verifying response!";
                System.out.println(message);
                throw new RuntimeException(message, e);
            }
            return ret;
        }
    
        //取得openid4java API中進行一連串驗證動作的物件
        private static ConsumerManager getConsumerManager() {
    
            if (consumerManager == null) {
                consumerManager = new ConsumerManager();
                consumerManager.setAssociations(new InMemoryConsumerAssociationStore());
                consumerManager.setNonceVerifier(new InMemoryNonceVerifier(10000));
            }
    
            return consumerManager;
        }
    
        //如果OP驗證使者成功,要導向的頁面(導向bookmarkable頁面)。
        public static String getReturnToUrl() {
            return "http://localhost:8080/myrp/wicket/bookmarkable/myopenid.InformationReview?is_return=true";
        }
    }
    



接下來就是網頁的部份,由Netbeans的Apache Wicket plugin所建立的wicket專案會自動將頁面進行一致化的編排,也就是會有共同的header及footer,因此所有的wicket page均要繼承BasePage此類別。

首先是本專案的Appilcation類別及MyRPSession類別,主要用來定義專案的相關屬性以及相關的資訊如何儲存在Session中。

//Application.java
package myopenid;

import org.apache.wicket.Session;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
public class Application extends WebApplication {

    public Application() {
    }

    public Class getHomePage() {
        return HomePage.class;
    }

    @Override
    public void init() {
        super.init();
        this.getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
    }

    @Override
    public Session newSession(Request request, Response response) {
        //傳回自訂的Session物件,才能進行正確轉型的動作
        return new MyRPSession(request);
    }
}

//MyRPSession.java
package myopenid;

import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.request.Request;
import org.openid4java.discovery.DiscoveryInformation;

public class MyRPSession extends WebSession {

    //open-id DiscoveryInformation
    private DiscoveryInformation discoveryInformation;
    //是否已將DiscoveryInformation資訊寫入Session中    
    private boolean discoveryInformationStoredInSession;
    //指定存在Session中的key值
    public static final String DISCOVERY_INFORMATION = "openid-disc";

    public MyRPSession(Request request) {
        super(request);
    }

    //此方法指定DiscoveryInformation不存在Session中
    public void setDiscoveryInformation(DiscoveryInformation discoveryInformation) {
        setDiscoveryInformation(discoveryInformation, false);
    }

    //可指定DiscoveryInformation是否儲存在Session中
    public void setDiscoveryInformation(DiscoveryInformation discoveryInformation, boolean storeInSession) {
        this.discoveryInformation = discoveryInformation;
        if (storeInSession) {
            //將DiscoveryInformtion存在Session中
            setAttribute(DISCOVERY_INFORMATION, discoveryInformation);
            discoveryInformationStoredInSession = true;
        }
    }

    public DiscoveryInformation getDiscoveryInformation() {
        DiscoveryInformation ret = discoveryInformation;
        //如果有設定將DiscoveryInformation存在Session中
        //就回傳Session中的DiscoveryInfomation
        if (discoveryInformationStoredInSession) {
            ret = (DiscoveryInformation) getAttribute(DISCOVERY_INFORMATION);
        }
        return ret;
    }
}


再來則是HomePage.html,首頁的html碼部份,它包含了輸入openid的表單(form)。

<!--HomePage.html-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"  
      xmlns:wicket="http://git-wip-us.apache.org/repos/asf/wicket/repo?p=wicket.git;a=blob_plain;f=wicket-core/src/main/resources/META-INF/wicket-1.5.xsd;hb=master"  
      xml:lang="en"  
      lang="en"> 
    <head> 
        <!--
        Apache Wicket 1.5x以上的xml schema
        xmlns:wicket="http://git-wip-us.apache.org/repos/asf/wicket/repo?p=wicket.git;a=blob_plain;f=wicket-core/src/main/resources/META-INF/wicket-1.5.xsd;hb=master"  
        -->
        <wicket:head> 
            <title>Open ID Relying Party</title> 
        </wicket:head> 
    </head> 
    <body> 
        <wicket:extend> 
            <form id="OpenIdRegistrationForm" wicket:id="form">
                <h1>請輸入你的OpenID以進入本站台!</h1>
                <br />
                <table border="0">
                    <tr>
                        <td><label>你的Open ID:</label></td>
                        <td>
                            <input class="biginput" type="text" wicket:id="openId"/>
                            <input type="submit" wicket:id="confirmOpenIdButton" value="確認OpenID"/>
                        </td>
                    </tr>
                </table>
            </form>
            <br/><br/>
        </wicket:extend> 
    </body> 
</html>

接下來則是其相對應的java原始碼,HomePage.java。

//HomePage.java
package myopenid;

import model.RegistrationModel;
import model.RegistrationService;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.pages.RedirectPage;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.AuthRequest;

public class HomePage extends BasePage {

    private String returnToUrl;

    public HomePage() {
        this(new PageParameters());
    }

    public HomePage(PageParameters parameters) {
        returnToUrl = RegistrationService.getReturnToUrl();
        //returnToUrl = urlFor(InformationReview.class, new PageParameters("is_return=true")).toString();
        add(new OpenIdRegistrationForm("form", this, returnToUrl));
        //add(new BookmarkablePageLink("mylink",InformationReview.class));
    }

    public static class OpenIdRegistrationForm extends Form {

        public OpenIdRegistrationForm(String id, final HomePage owningPage, String returnToUrl) {
            this(id, owningPage, returnToUrl, new RegistrationModel());
        }

        @SuppressWarnings("serial")
        public OpenIdRegistrationForm(String id, final HomePage owningPage, final String returnToUrl, final RegistrationModel formModel) {

            super(id);
            setModel(new CompoundPropertyModel(formModel));
            TextField openId = new RequiredTextField("openId");
            openId.setLabel(new Model("你的Open ID"));
            add(openId);
            Button confirmOpenIdButton = new Button("confirmOpenIdButton") {

                @Override
                public void onSubmit() {
                    String userSuppliedIdentifier = formModel.getOpenId();
                    DiscoveryInformation discoveryInformation = RegistrationService.performDiscoveryOnUserSuppliedIdentifier(userSuppliedIdentifier);
                    MyRPSession session = (MyRPSession) owningPage.getSession();
                    session.setDiscoveryInformation(discoveryInformation, true);
                    AuthRequest authRequest = RegistrationService.createOpenIdAuthRequest(discoveryInformation, returnToUrl);
                    //在Wicket 1..5版本以上只要呼叫setResponsePage即可進行redirect的動作
                    setResponsePage(new RedirectPage(authRequest.getDestinationUrl(true)).getPage());
                }
            };
            add(confirmOpenIdButton);
        }
    }
}

最後則是OpenID OP驗證過後,會將資料傳回returnToUrl中所指定的網頁InformationReview.html,它包含了一個表單,可以讓使用者檢視資料是否正確。

<!--InformationReview.html-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:wicket="http://git-wip-us.apache.org/repos/asf/wicket/repo?p=wicket.git;a=blob_plain;f=wicket-core/src/main/resources/META-INF/wicket-1.5.xsd;hb=master">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>InformationReview</title>
        <link rel="stylesheet" type="text/css" href="style.css"/>
    </head>
    <body>
        <wicket:extend>
            <form id="OpenIdRegistrationSuccessForm" wicket:id="form">
                <h1>Please review your registration info below.</h1>
                <br />
                <table border="0">
                    <tr>
                        <td><label>Your Open ID:</label></td>
                        <td><input class="biginput" type="text" wicket:id="openId"/></td>
                    </tr>
                    <tr>
                        <td><label>Full Name:</label></td>
                        <td><input class="biginput" type="text" wicket:id="fullName"/></td>
                    </tr>
                    <tr>
                        <td><label>Email Address:</label></td>
                        <td><input class="biginput" type="text" wicket:id="emailAddress"/></td>
                    </tr>
                    <tr>
                        <td><label>Zip Code:</label></td>
                        <td><input class="biginput" type="text" wicket:id="zipCode"/></td>
                    </tr>
                    <tr>
                        <td><label>Date of Birth:</label></td>
                        <td><input class="biginput" type="text" wicket:id="dateOfBirth"/></td>
                    </tr>
                    <tr>
                        <td>&nbsp;</td>
                        <td><input class="bigbutton" type="submit" wicket:id="saveButton" value="Save"/></td>
                    </tr>
                </table>
            </form>
        </wicket:extend>
    </body>
</html>

相對應的InformationReview.java,裡面預留了將資料儲存在資料庫中的method。

//InformationReview.java
package myopenid;

import java.util.Date;
import model.RegistrationModel;
import model.RegistrationService;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.openid4java.discovery.DiscoveryInformation;

public final class InformationReview extends BasePage {

    public InformationReview() {
        this(new PageParameters());
    }

    public InformationReview(PageParameters pageParameters) {
        RegistrationModel registrationModel = new RegistrationModel();
        if (!pageParameters.isEmpty()) {
            String isReturn = pageParameters.get("is_return").toString();
            if (isReturn.equals("true")) {
                MyRPSession session = (MyRPSession) getSession();
                DiscoveryInformation discoveryInformation = session.getDiscoveryInformation();
                registrationModel = RegistrationService.processReturn(discoveryInformation, pageParameters, RegistrationService.getReturnToUrl());
                if (registrationModel == null) {
                    error("Open ID Confirmation Failed. No information was retrieved from the OpenID Provider. You will have to enter all information by hand into the text fields provided.");
                }
            }
        }
        add(new OpenIdRegistrationInformationDisplayForm("form", registrationModel));
    }

    public static class OpenIdRegistrationInformationDisplayForm extends Form {

        @SuppressWarnings("serial")
        public OpenIdRegistrationInformationDisplayForm(String id, RegistrationModel registrationModel) {
            super(id, new CompoundPropertyModel(registrationModel));
            TextField openId = new TextField("openId");
            openId.setEnabled(false);
            add(openId);
            TextField fullName = new RequiredTextField("fullName");
            add(fullName);
            TextField emailAddress = new RequiredTextField("emailAddress");
            add(emailAddress);
            TextField zipCode = new TextField("zipCode");
            add(zipCode);
            TextField dateOfBirth = new RequiredTextField("dateOfBirth", Date.class);
            add(dateOfBirth);
            Button saveButton = new Button("saveButton") {

                public void onSubmit() {
                    if (saveRegistrationInfo()) {
                        info("Registration Info saved.");
                    } else {
                        error("Registration Info could not be saved!");
                    }
                }
            };
            add(saveButton);
        }

        private boolean saveRegistrationInfo() {
            return true;
        }
    }
}

No comments:

Post a Comment