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

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

ภาพรวม

OpenId เป็นโปรโตคอลที่ใช้ในการยืนยันตัวบุคคล โดยองค์ประกอบของ OpenID นั้นมีด้วยกัน 2 ส่วน ได้แก่ เว็บผู้ให้บริการยืนยันตัวบุคคล (OpenID Provider) และเว็บผู้รับบริการ (OpenID Relying Party) ซึ่งขั้นตอนการทำงานมีดังนี้
  1. เว็บผู้รับบริการส่งคำร้องขอยืนยันตัวบุคคล(OpenID Request) ที่มีรายละเอียดของข้อมูลผู้ใช้ที่ต้องการ(Attribute) ไปยังผู้ให้บริการยืนยันตัวบุคคล
  2. เว็บผู้ให้บริการยืนยันตัวบุคคลแสดงหน้า Login ให้ผู้ใช้ทำการ Login
  3. เว็บผู้ให้บริการยืนยันตัวบุคคลส่งข้อมูลผู้ใช้ที่ Login ตาม Attribute ที่เว็บผู้รับบริการร้องขอ
  4. เว็บผู้รับบริการนำข้อมูลที่ได้ไปทำการ Login ให้ผู้ใช้ในเว็บของตน
ในปัจจุบันทาง สพร. ได้พัฒนาระบบยืนยันตัวบุคคลสำหรับเจ้าหน้าที่รัฐ (Government ID) ซึ่งทำหน้าที่เป็น OpenID Provider เพื่อให้ระบบบริการสำหรับเจ้าหน้าที่รัฐ (e-Service for Government Officer) ซึ่งในที่นี่เป็น OpenID Relying Party สามารถนำข้อมูลการยืนยันตัวตนนี้ ไปอนุญาตให้ผู้ใช้ที่ยีนยันตัวตนแล้วสามารถเข้าใช้ระบบบริการสำหรับเจ้าหน้าที่รัฐ (e-Service for Government Officer) ได้
รายละเอียดเพิ่มเติมสามารถดูได้ที่ OpenID.net

OpenID Request

ทางสพร. ได้ใช้มาตรฐานการรับ OpenID Request และ ส่ง OpenID Response ตามเอกสาร OpenID Authentication 2.0 - Final และใช้มาตรฐานการรับส่งข้อมูลผู้ใช้แบบ Attribute Exchange ตามเอกสาร OpenID Attribute Exchange 1.0 - Final โดยทางสพร. ได้เพิ่ม Attribute บางตัวจาก Attribute มาตรฐาน เรียกว่า "Attribute Alias" เพื่อให้ใช้งานได้ครอบคุมขึ้น ซึ่งมี Parameter และ Attribute ที่ใช้ในการทำ OpenID Request ดังนี้

http://localhost:1807/EGA.EGA_CAS.Security.eAuthen/server.aspx
?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select
&openid.identity=http://specs.openid.net/auth/2.0/identifier_select
&openid.assoc_handle=nixR!IAAAAB56GYx_ecAUdoR3qhyvqAi9MZxO-Ahl00nqoeUcrtnQQQAAAAGNVREK0g4S
_Zb8IgSlb96sYGjE4AYbskGBPXGmwQ3yrBvH2CmwGL6bIW_6m2Ibh0cN5Zt5rl0hhM4hvHj6HDlZ
&openid.return_to=http://localhost:4856/loginProgrammatic.aspx?dnoa.userSuppliedIdentifier=
http%3A%2F%2Flocalhost%3A1807%2FEGA.EGA_CAS.Security.eAuthen%2F
&openid.realm=http://localhost:4856/
&openid.mode=checkid_setup
&openid.ns=http://specs.openid.net/auth/2.0
&openid.ns.alias3=http://openid.net/srv/ax/1.0
&openid.alias3.required=alias1,alias2,alias3,alias4
&openid.alias3.mode=fetch_request
&openid.alias3.type.alias1=http://axschema.org/contact/email
&openid.alias3.count.alias1=1
&openid.alias3.type.alias2=http://axschema.org/namePerson
&openid.alias3.count.alias2=1
&openid.alias3.type.alias3=http://axschema.org/namePerson/friendly
&openid.alias3.count.alias3=1
                                
Parameter Description
Basic OpenId parameter :
openid.claimed_id (ไม่จำเป็นต้องมี) เป็น ID ของ OpenID ของผู้ใช้ ถ้าไม่ทราบให้กำหนดค่าเป็น “http://specs.openid.net/auth/2.0/identifier_select”
openid.identity (ไม่จำเป็นต้องมี) เป็น ID ของ OpenID ของผู้ใช้บนระบบยืนยันตัวบุคคลกลาง ถ้าไม่ทราบให้กำหนดค่าเป็น “http://specs.openid.net/auth/2.0/identifier_select”
openid.assoc_handle (ไม่จำเป็นต้องมี) เป็นค่าที่กำหนดขึ้นเพื่อให้ระบบยืนยันตัวบุคคลกลางใช้ในการ Sign OpenID Response โดยถ้าค่านี้ไม่ถูกกำหนด ระบบบริการสำหรับเจ้าหน้าที่รัฐต้องทำการตรวจสอบ OpenID Response กับระบบยืนยันตัวบุคคลกลางอีกครั้ง
openid.return_to (ไม่จำเป็นต้องมี) เป็น URL ที่ระบบยืนยันตัวบุคคลกลางจะส่งผู้ใช้กลับมาหลังจากผู้ใช้งานทำการลงชื่อเข้าใช้แล้ว
openid.realm (ไม่จำเป็นต้องมี) เป็นค่าที่แจ้งให้ระบบยืนยันตัวบุคคลกลางทราบว่าผู้ใช้ทำการ Login เข้าระบบบริการสำหรับเจ้าหน้าที่รัฐใด ต้องกำหนดถ้าไม่ได้กำหนด openid.return_to
openid.mode (ต้องมี) เป็นการบอกให้ OpenId Provider รู้ว่า OpenId Provider นี้สามารถติดต่อกับผู้ใช้ได้หรือไม่ โดยมีค่าดังนี้
  • “checked_immediate” (ไม่ให้ติดต่อกับผู้ใช้)
  • “checked_setup”(ให้ติดต่อกับผู้ใช้ได้)
openid.ns (ต้องมี) เป็นการบอก verison ของ OpenId Request โดยระบบยืนยันตัวบุคคลกลางใช้งานรองรับ OpenID 2.0 ระบบบริการสำหรับเจ้าหน้าที่รัฐจึงควรใช้ Relying Party ไลบรารี่ที่รองรับ OpenID 2.0 แล้วใส่ค่าเป็น “http://specs.openid.net/auth/2.0”
Attribute Exchange Extension :
openid.ns.<extension_alias> (ต้องมี) เป็นการบอกระบบยืนยันตัวบุคคลกลางให้ส่งค่าคืนมาในรูปแบบของ Attribute Exchange (ระบบยืนยันตัวบุคคลกลาง สนับสนุนการส่งค่าคืนในรูปแบบ Attribute Exchange เท่านั้น) ค่านี้ต้องถูกตั้งเป็น “http://openid.net/srv/ax/1.0”
openid.<extension_alias>.required (ต้องมี) เป็นการบอกระบบยืนยันตัวบุคคลกลางว่าระบบบริการสำหรับเจ้าหน้าที่รัฐต้องการ attribute อะไรคืนไปบ้าง โดยทุก attribute ต้องมีค่าจริงเพื่อให้ใช้งานได้ครอบคลุม
openid.<extension_alias>.mode (ต้องมี) เป็นค่าบังคับเพื่อใช้งาน Attribute Exchange ค่านี้ต้องถูกตั้งเป็น “fetch_request”
openid.<extension_alias>.type.<attribute_alias> (ต้องมี) เป็นการบอกระบบยืนยันตัวบุคคลกลางว่าระบบบริการสำหรับเจ้าหน้าที่รัฐมีการส่งชื่อ attribute อะไรบ้างโดยชื่อ attribute_alias ต้องถูกกำหนดใน openid.<extension_alias>.required ถ้าระบบบริการสำหรับเจ้าหน้าที่รัฐต้องการ attribute นั้น
Attribute ค่าที่ส่งคืน
http://axschema.org/contact/email e-mail ของผู้ใช้
http://axschema.org/namePerson ชื่อ-นามสกุล ของผู้ใช้
http://axschema.org/namePerson/friendly User name ในระบบ e-portal ของผู้ใช้
http://www.egov.go.th/2012/identifier/citizenid เลขประจำตัวประชาชน
http://www.egov.go.th/2012/identifier/juristicId เลขนิติบุคคล
http://www.egov.go.th/2012/identifier/passportnumber เลขที่หนังสือเดินทาง
http://www.egov.go.th/2012/identifier/usertype ประเภทของผู้ใช้ ในระบบ e-portal
  • ค่า 1 : บุคคลธรรมดา
  • ค่า 2 : นิติบุคคล
  • ค่า 3 : ชาวต่างชาติ
  • ค่า 4 : เจ้าหน้าที่ของรัฐ
http://www.egov.go.th/2012/identifier/identity รหัสยืนยัน โดยรูปแบบขึ้นอยู่กับประเภทของผู้ใช้ดังนี้
  • บุคคลธรรมดา : เลขประจำตัวประชาชน
  • นิติบุคคล : เลขนิติบุคคล
  • ชาวต่างชาติ : เลขที่หนังสือเดินทาง
  • เจ้าหน้าที่ของรัฐ : เลขประจำตัวประชาชน
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; 
using DotNetOpenAuth.OpenId.RelyingParty;
using OpenIdRelyingPartyWebForms;

public class SSOLogin : System.Web.UI.Page
{
    private const string OpenIdProviderURL = "https://testopenid.dga.or.th/";
    private static OpenIdRelyingParty relyingParty = new OpenIdRelyingParty();

    static SSOLogin()
    {
        // Configure the RP to only allow assertions from our trusted OP endpoint.
        EndpointSelector ep = new EndpointSelector(EndpointFilter);
        relyingParty.EndpointFilter = ep;
    }

    protected static bool EndpointFilter(IProviderEndpoint ipep) 
    {
        return ipep.Uri.AbsoluteUri == OpenIdProviderURL + "server.aspx";
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        //ทำการสร้าง url ของหน้าที่ต้องการให้ส่งกลับ โดยในที่นี้ให้ส่งกลับมายังหน้าเดิม
        string[] segments = Request.Url.Segments;
        string pathToPage = ""; 
        for(int i=0; i<segments.Length-1; i++) {
            pathToPage += segments[i]; 
        } 
        UriBuilder returnToBuilder = new UriBuilder(Request.Url);
        returnToBuilder.Path = pathToPage+"SSOLogin.aspx";
        returnToBuilder.Query = null;
        returnToBuilder.Fragment = null;
        Uri returnTo = returnToBuilder.Uri;
        returnToBuilder.Path = "/";

        //บันทึก Url ลงใน Realm
        Realm realm = returnToBuilder.Uri;
        
        try
        {
            IAuthenticationRequest request = relyingParty.CreateRequest(
            OpenIdProviderURL, realm, returnTo);

            // Attributes ที่ต้องการร้องขอจาก OpenID Provider
            // โดยใช้หลักการ AttributeExcahnge (AX)
            var ax = new FetchRequest();
            ax.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email, true));
            ax.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.FullName, true));
            ax.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.Alias, true));
            
            request.AddExtension(ax);

            //ส่ง Request ไปยัง OpenID Provider      
            request.RedirectToProvider();
        }
        catch (Exception ex)
        {
            Response.Write("ไม่พบ OpenID end point สาเหตุอาจมาจากการที่ท่านระบุ url ผิด หรือ ท่านไม่ได้ทำการเพิ่ม 
                            SSL Certificate ไปยัง Trusted Root Store บนเครื่อง Server");
        }
    }
} 
                        

OpenID Response

http://localhost:4856/loginProgrammatic.aspx
?dnoa.userSuppliedIdentifier=http://localhost:1807/EGA.EGA_CAS.Security.eAuthen/
&openid.claimed_id=http://localhost:1807/EGA.EGA_CAS.Security.eAuthen/user.aspx/Administrator
&openid.identity=http://localhost:1807/EGA.EGA_CAS.Security.eAuthen/user.aspx/Administrator
&openid.sig=P6+IZf5yVHeOTnngzycP7vw8BRLJiBHF4ZSOm1Mauxg=
&openid.signed=claimed_id,identity,assoc_handle,op_endpoint,return_to,response_nonce,ns.alias3
,alias3.mode,alias3.type.alias1,alias3.value.alias1,alias3.type.alias2,alias3.value.alias2
,alias3.type.alias3,alias3.value.alias3,alias3.type.alias4,alias3.value.alias4
&openid.assoc_handle=nixR!IAAAAB56GYx_ecAUdoR3qhyvqAi9MZxO-Ahl00nqoeUcrtnQQQAAAAGNVREK0g4S
_Zb8IgSlb96sYGjE4AYbskGBPXGmwQ3yrBvH2CmwGL6bIW_6m2Ibh0cN5Zt5rl0hhM4hvHj6HDlZ
&openid.op_endpoint=http://localhost:1807/EGA.EGA_CAS.Security.eAuthen/server.aspx
&openid.return_to=http://localhost:4856/loginProgrammatic.aspx?dnoa.userSuppliedIdentifier
=http%3A%2F%2Flocalhost%3A1807%2FEGA.EGA_CAS.Security.eAuthen%2F
&openid.response_nonce=2012-05-15T10:45:54ZrrKt7tDX
&openid.mode=id_res
&openid.ns=http://specs.openid.net/auth/2.0
&openid.ns.alias3=http://openid.net/srv/ax/1.0
&openid.alias3.mode=fetch_response
&openid.alias3.type.alias1=http://axschema.org/contact/email
&openid.alias3.value.alias1=test@gits.net.th
&openid.alias3.type.alias2=http://axschema.org/namePerson
&openid.alias3.value.alias2=Administrator eGov-Portal
&openid.alias3.type.alias3=http://axschema.org/namePerson/friendly
&openid.alias3.value.alias3=administrator
&openid.alias3.type.alias4=http://www.egov.go.th/2012/identifier/citizenid
&openid.alias3.value.alias4=
                                
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; 
using DotNetOpenAuth.OpenId.RelyingParty;
using OpenIdRelyingPartyWebForms;

public class SSOLogin : System.Web.UI.Page
{
    private const string OpenIdProviderURL = "https://testopenid.dga.or.th/";
    private static OpenIdRelyingParty relyingParty = new OpenIdRelyingParty();

    static SSOLogin()
    {
        // Configure the RP to only allow assertions from our trusted OP endpoint.
        EndpointSelector ep = new EndpointSelector(EndpointFilter);
        relyingParty.EndpointFilter = ep;
    }

    protected static bool EndpointFilter(IProviderEndpoint ipep) 
    {
        return ipep.Uri.AbsoluteUri == OpenIdProviderURL + "server.aspx";
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            IAuthenticationResponse response = relyingParty.GetResponse();

            switch (response.Status)
            {
                case AuthenticationStatus.Canceled:
                    // ผู้ใช้ยกเลิก
                    break;
                case AuthenticationStatus.Failed:
                    // ผู้ใช้ Authenticate ไม่ผ่าน
                    break;
                case AuthenticationStatus.Authenticated:

                    // ทำการดึงข้อมูลที่ได้รับมาจาก OpenID Provider ออกมา
                    FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
                    State.FetchResponse = fetchResponse;
                    string claimed = response.FriendlyIdentifierForDisplay;
                    string userName = State.FetchResponse.Attributes[WellKnownAttributes
                    .Name.Alias].Values[0];
                    string fullName = State.FetchResponse.Attributes[WellKnownAttributes
                    .Name.FullName].Values[0];
                    string email = State.FetchResponse.Attributes[WellKnownAttributes
                    .Contact.Email].Values[0];
                    string identifier = State.FetchResponse
                    .Attributes["http://www.egov.go.th/2012/identifier/citizenid"].Values[0];
                    string userType = State.FetchResponse
                    .Attributes["http://www.egov.go.th/2012/identifier/usertype"].Values[0];

                    UserMappingAndAuthorization(claimed, userName, fullName, email, identifier, userType); 

                    break;
                default:
                    break;
            }
        }
        catch (Exception ex)
        {
            
        }
    }
}
                        

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

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

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


สงวนลิขสิทธิ์ พ.ศ. 2554 ตามพระราชบัญญัติลิขสิทธิ์ 2537 สำนักงานพัฒนารัฐบาลดิจิทัล (องค์การมหาชน) (สพร.)