มุมนักพัฒนา > พัฒนาหน้า SSORegister ด้วยโปรโตคอล OAuth

การพัฒนาหน้า SSORegiser ด้วยโปรโตคอล OAuth

ภาพรวม

OAuth เป็นโปรโตคอลที่ใช้ในการร้องขอข้อมูลจาก Remote Server ซึ่งไม่มีการจำกัดประเภทของข้อมูล สามารถเป็นได้ทั้งรูปภาพ, ไฟล์เสียง หรือข้อมูลที่เป็น Text โดยทางสพร. ได้เอาโปรโตคอลนี้มาปรับใช้ในการส่งข้อมูลส่วนตัวของผู้ใช้งานระบบยืนยันตัวบุคคลกลาง (e-Authentication Service) กลับมาในรูปแบบของ XML ดังนั้นระบบบริการภาครัฐ (e-Service) ต้องทำการพัฒนาชุดคำสั่งหรือระบบ OAuth Consumer เพื่อใช้ในการร้องขอข้อมูลผู้ใช้จากระบบยืนยันตัวบุคคลกลาง (ซึ่งทำหน้าที่เป็น OAuth Provider) ซึ่งมีขั้นตอนการทำงานดังนี้
  1. ระบบบริการภาครัฐร้องขอ "Request Token" จาก OAuth Provider
  2. OAuth Provider ส่ง "Request Token" กลับมา
  3. ผู้ใช้ตัดสินใจว่าจะอนุญาติให้ระบบบริการภาครัฐสามารถเข้าถึงข้อมูลของผู้ใช้ได้หรือไม่
  4. OAuth Provider ส่งผู้ใช้กลับไปยังระบบบริการภาครัฐ
  5. ระบบบริการภาครัฐร้องขอ “Access Token”
  6. OAuth Provider แลก “Request Token” เป็น “Access Token” และส่งกลับมา
  7. ระบบบริการภาครัฐใช้ “Access Token” ดึงข้อมูลของผู้ใช้

การขอ Request Token จาก OAuth Provider

ที่มา : http://oauth.net/core/1.0/

Parameter Description
1. ระบบบริการภาครัฐร้องขอ "Request Token" จาก OAuth Provider
oauth_consumer_key "key" ที่ทางสพร.ส่งให้
oauth_signature_method วิธีการ Sign Request (ควรใช้ค่าเป็น HMAC-SHA1)
oauth_signature ค่าที่ในการ Sign Request ตาม oauth_signature_method (โดยทั่วไป parameter นี้จะถูกตั้งค่าให้อัตโนมัติในขั้นตอนสร้าง Request ของแต่ละไลบรารี่)
oauth_timestamp เวลาที่ทำการ Request
oauth_nonce เป็นชุดของตัวหนังสือภาษาอังกฤษที่ถูกสุ่มขึ้นมาให้ไม่ซ้ำกันในแต่ละ Request ของแต่ละระบบบริการภาครัฐ เพื่อเอาไว้ตรวจสอบว่า Request นี้เป็น Request ที่ไม่เคยถูกใช้มาก่อน และป้องกันการโจมตีผ่าน HTTP
oauth_version เวอร์ชั่นของ OAuth
oauth_callback URL ที่จะให้ส่ง “Request Token” กลับไป
2. OAuth Provider ส่ง "Request Token" กลับมา
oauth_token “Request Token” จากระบบข้อมูลผู้ใช้
oauth_token_secret เป็นค่าที่ระบบข้อมูลผู้ใช้ส่งมาพร้อมกับ “Request Token” เพื่อใช้ในการตรวจสอบ “Request Token” โดยค่านี้จะไม่ซ้ำกันในแต่ละ “Request Token”
oauth_callback_confirmed เป็น True ถ้าได้รับการยืนยันจาก OAuth Provider
3. ผู้ใช้ตัดสินใจว่าจะอนุญาติให้ระบบบริการภาครัฐสามารถเข้าถึงข้อมูลของผู้ใช้ได้หรือไม่
oauth_token “Request Token” จากระบบข้อมูลผู้ใช้ (ได้มาจากขั้นตอนที่ 2)
4. OAuth Provider ส่งผู้ใช้กลับไปยังระบบบริการภาครัฐ
oauth_token “Request Token” จากขั้นตอน B (ในขั้นตอนนี้ “Request Token” ได้รับการอนุญาตให้ใช้งานได้จากระบบข้อมูลผู้ใช้แล้ว)
oauth_verifier เป็นค่าที่ระบบข้อมูลผู้ใช้ส่งมาพร้อมกับ “Request Token” โดยค่านี้จะมีความเชื่อมโยงกับระบบบริการภาครัฐ ค่านี้ถูกใช้ในขั้นตอน E เพื่อยืนยันว่าระบบบริการภาครัฐที่จะขอ “Access Token” นั้นเป็นระบบบริการภาครัฐเดียวกับที่ขอ “Request Token”
5. ระบบบริการภาครัฐร้องขอ “Access Token”
oauth_consumer_key “key” ที่ทางสพร.ส่งให้
oauth_token “Request Token” ในขั้นตอน D
oauth_signature_method วิธีการเข้ารหัส Request (ต้องใสค่าเป็น HMAC-SHA1)
oauth_signature ค่าที่ได้จากขั้นตอนการเข้ารหัส ตาม oauth_signature_method ค่าในขั้นตอนนี้จะไม่เหมือนค่าในขั้นตอน A (โดยทั่วไป parameter นี้จะถูกตั้งค่าให้อัตโนมัติในขั้นตอนสร้าง Request ของแต่ละไลบรารี่)
oauth_timestamp เวลาที่ทำการ Request
oauth_nonce เป็นชุดของตัวหนังสือภาษาอังกฤษที่ถูกสุ่มขึ้นมาให้ไม่ซ้ำกันในแต่ละ Request ของแต่ละระบบบริการภาครัฐ เพื่อเอาไว้ตรวจสอบว่า Request นี้เป็น Request ที่ไม่เคยถูกใช้มาก่อน และป้องกันการโจมตีผ่าน HTTP
oauth_version เวอร์ชั่นของ OAuth
oauth_verifier ค่าที่ได้จากระบบข้อมูลผู้ใช้ในขั้นตอน D
6. OAuth Provider แลก “Request Token” เป็น “Access Token” และส่งกลับมา
oauth_token “Access Token” ที่ได้รับจากระบบเว็บไซต์กลาง
oauth_token_secret เป็นค่าที่ระบบข้อมูลผู้ใช้ส่งมาพร้อมกับ “Access Token” เพื่อใช้ในการตรวจสอบ “Access Token” โดยค่านี้จะไม่ซ้ำกันในแต่ละ “Access Token”
7. ระบบบริการภาครัฐใช้ “Access Token” ดึงข้อมูลของผู้ใช้
นำ “Access Token” ที่ได้ไปเข้าถึงข้อมูลของผู้ใช้ โดยทางสพร.ได้ปรับวิธีการเข้าถึงข้อมูลเพื่อให้ได้ข้อมูลกลับมาในรูปแบบของ xml โดยผู้พัฒนาสามารถเข้าถึง xml ผ่าน URL : “http://www.egov.go.th/XmlUserInfo.aspx?AccessToken={Access Token ที่ระบบบริการภาครัฐได้รับ}” โดย “Access Token” มีอายุการใช้งาน 10 นาที
<? Xml version=“1.0” encoding="UTF-8"?>
<Member type="Citizen">
    <UserID>a4d8b6a0-37db-4f44-b598fb0e6550a31f</UserID>
    <UserName>TestVender3</UserName>
    <Title VerifiedLevel="Unverified"/>
    <FullName VerifiedLevel="Unverified">Vender3 Test</FullName>
    <FirstName VerifiedLevel="Unverified">Vender3</FirstName>
    <LastName VerifiedLevel="Unverified">Test</LastName>
    <DateOfBirth VerifiedLevel="Unverified"/>
    <Gender VerifiedLevel="Unverified"/>
    <Identification>
        <Code VerifiedLevel="Unverified"/>
        <IssueBy VerifiedLevel="Unverified"/>
        <IssueDate VerifiedLevel="Unverified"/>
        <ExpireDate VerifiedLevel="Unverified"/>
    </Identification>
    <Nationality VerifiedLevel="Unverified"/>
    <Occupation VerifiedLevel="Unverified"/>
    <Address>
        <HouseNumber VerifiedLevel="Unverified"/>
        <VillageName VerifiedLevel="Unverified"/>
        <Moo VerifiedLevel="Unverified"/>
        <Soi VerifiedLevel="Unverified"/>
        <Road VerifiedLevel="Unverified"/>
        <SubDistrict VerifiedLevel="Unverified"/>
        <District VerifiedLevel="Unverified"/>
        <Province VerifiedLevel="Unverified"/>
        <PostCode VerifiedLevel="Unverified"/>
        <GeoCode VerifiedLevel="Unverified"/>
    </Address>
    <ContactInfo>
        <Telephone VerifiedLevel="Unverified"/>
        <Mobilephone VerifiedLevel="Unverified"/>
        <EMail VerifiedLevel="Unverified">vender3@test.com</EMail>
    </ContactInfo>
</Member>

                                
using GITSSSO;
using System.Xml.XPath;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; 
using DotNetOpenAuth.OpenId.RelyingParty;
using OpenIdRelyingPartyWebForms;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth;
using DotNetOpenAuth.ApplicationBlock;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
using EGA.EGA_CAS.Util;
using EGA.EGA_CAS.Util.Entity;
using EGA.EGA_CAS.SSO.Library;

public class SSORegister : System.Web.UI.Page
{
    //แก้ 2 ตัวนี้ตามชื่อ e-service ต่างๆ
    private const string consumerKey = "";
    private const string consumerSecret =  SecretUtil.encodeSecret("");

    #region ตรงนี้เป็น code ที่ไว้ใช้จัดการกับเรื่องการทำ OpenID นะครับ ไม่จำเป็นอย่าแก้
    
    private const string oAuthUrl = "http://testopenid.dga.or.th/OAuth.ashx";
    private const string xmlUrl = "http://www.egov.go.th/XmlUserInfo.aspx";
    
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            //OAuth
            if (Session["WcfTokenManager"] != null)
            {
                WebConsumer consumer = this.CreateConsumer();
                AuthorizedTokenResponse accessTokenMessage = consumer.ProcessUserAuthorization();
                if (accessTokenMessage != null)
                {
                    Session["WcfAccessToken"] = accessTokenMessage.AccessToken;
                    Session.Remove("WcfTokenManager");
                    string path = String.Format("{0}?AccessToken={1}", xmlUrl
                    , Server.UrlEncode(accessTokenMessage.AccessToken));
                    
                    SSOUserInfo ssoUI = new SSOUserInfo(path);

                    // ฟังก์ชั่นสำหรับนำข้อมูลที่ได้ไปใช้งาน ขึ้นอยู่กับแต่ละ Service
                    this.bindValueToPage(ssoUI);
                }
                else
                {
                    WebConsumer consumer = this.CreateConsumer();
                    UriBuilder callback = new UriBuilder(Request.Url);
                    callback.Query = null;

                    Dictionary<string, string> requestParams = new Dictionary<string, string>();
                    requestParams.Add("scope", "");

                    UserAuthorizationRequest response = consumer.PrepareRequestUserAuthorization(
                                                        callback.Uri, requestParams, null);
                    consumer.Channel.Send(response);
                }
            }
            else
            {
                WebConsumer consumer = this.CreateConsumer();
                UriBuilder callback = new UriBuilder(Request.Url);
                callback.Query = null;
                
                Dictionary<string, string> requestParams = new Dictionary<string, string>();
                requestParams.Add("scope", "");

                UserAuthorizationRequest response = consumer.PrepareRequestUserAuthorization(
                                                    callback.Uri, requestParams, null);
                consumer.Channel.Send(response);
            }

            //ของระบบเดิม
            //string userIdentifier = State.ProfileFields.Nickname;
            //int userType = this.ConvertToInt(State.ProfileFields.PostalCode);
            //SSOUserInfo ssoUI = new SSOUserInfo(userIdentifier, userType);

            //this.bindValueToPage(ssoUI);
        }
    }

    private int ConvertToInt(string input)
    {
        int result = 0;
        try
        {
            result = Convert.ToInt32(input);
        }
        catch
        {
            result = 1;
        }
        return result;
    }

    private WebConsumer CreateConsumer()
    {
        InMemoryTokenManager tokenManager = Session["WcfTokenManager"] as InMemoryTokenManager;
        if (tokenManager == null)
        {
            tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
            Session["WcfTokenManager"] = tokenManager;
        }
        MessageReceivingEndpoint oauthEndpoint = new MessageReceivingEndpoint(
            new Uri(oAuthUrl),
            HttpDeliveryMethods.PostRequest);

        ServiceProviderDescription spd = new ServiceProviderDescription();
        spd.RequestTokenEndpoint = oauthEndpoint;
        spd.UserAuthorizationEndpoint = oauthEndpoint;
        spd.AccessTokenEndpoint = oauthEndpoint;
        spd.ProtocolVersion = DotNetOpenAuth.OAuth.ProtocolVersion.V10a;
        spd.TamperProtectionElements = new DotNetOpenAuth.Messaging
            .ITamperProtectionChannelBindingElement[] {
                new HmacSha1SigningBindingElement(),
            };

        WebConsumer consumer = new WebConsumer(spd, tokenManager);
        return consumer;
    }

    #endregion
}
                        

บทความที่เกี่ยวข้อง

1. คุณสมบัติขององค์ประกอบที่ต้องได้รับการพัฒนาในระบบ e-Service

2. การพัฒนาหน้า SSOLogin ด้วยโปรโตคอล OpenID