OAuth พร้อมการยืนยันใน. NET


103

ฉันกำลังพยายามสร้างแอปไคลเอนต์ที่ใช้. NET (ใน WPF - แม้ว่าในขณะนี้ฉันกำลังทำเป็นแอปคอนโซล) เพื่อรวมเข้ากับแอปพลิเคชันที่เปิดใช้งาน OAuth โดยเฉพาะ Mendeley ( http: // dev .mendeley.com ) ซึ่งเห็นได้ชัดว่าใช้ OAuth แบบ 3 ทาง

นี่เป็นครั้งแรกที่ฉันใช้ OAuth และฉันประสบปัญหาอย่างมากในการเริ่มต้นใช้งาน ฉันพบไลบรารี. NET OAuth หรือตัวช่วยหลายตัว แต่ดูเหมือนว่าจะซับซ้อนกว่าที่คิด สิ่งที่ฉันต้องการทำคือสามารถส่งคำขอ REST ไปยัง Mendeley API และรับคำตอบกลับมาได้!

จนถึงตอนนี้ฉันได้ลอง:

สิ่งแรก (DotNetOpenAuth) ดูเหมือนว่ามันอาจทำในสิ่งที่ฉันต้องการได้ถ้าฉันใช้เวลาหลายชั่วโมงในการพยายามหาวิธี อย่างที่สองและสามอย่างที่ดีที่สุดที่ฉันบอกได้คือไม่รองรับรหัสยืนยันที่ Mendeley ส่งกลับมา - แม้ว่าฉันจะผิดเกี่ยวกับเรื่องนี้ก็ตาม :)

ฉันมีรหัสและความลับของผู้บริโภคจาก Mendeley และด้วย DotNetOpenAuth ฉันจัดการเพื่อให้เบราว์เซอร์เปิดตัวพร้อมกับหน้า Mendeley ที่ให้รหัสยืนยันสำหรับผู้ใช้เพื่อเข้าสู่แอปพลิเคชัน อย่างไรก็ตามในตอนนี้ฉันหลงทางและไม่สามารถหาวิธีการส่งกลับไปยังแอปพลิเคชันได้อย่างสมเหตุสมผล

ฉันยินดีเป็นอย่างยิ่งที่จะยอมรับว่าฉันไม่รู้ว่าจะเริ่มจากจุดไหนดี (แม้ว่าจะดูเหมือนว่ามีช่วงการเรียนรู้ที่ค่อนข้างสูง) - ถ้าใครสามารถชี้ฉันไปในทิศทางที่ถูกต้องฉันก็จะขอบคุณ!

คำตอบ:


182

ฉันเห็นด้วยกับคุณ. คลาสการสนับสนุน OAuth แบบโอเพนซอร์สที่มีให้สำหรับแอป. NET นั้นยากที่จะเข้าใจซับซ้อนเกินไป (DotNetOpenAuth มีวิธีการเปิดใช้งานกี่วิธี) ออกแบบมาไม่ดี (ดูวิธีการที่มีพารามิเตอร์สตริง 10 ตัวในโมดูล OAuthBase.cs จาก Google นั้น ลิงค์ที่คุณให้มา - ไม่มีการจัดการของรัฐเลย) หรือไม่น่าพอใจ

ไม่จำเป็นต้องซับซ้อนขนาดนี้

ฉันไม่ใช่ผู้เชี่ยวชาญเรื่อง OAuth แต่ฉันได้สร้างคลาสผู้จัดการฝั่งไคลเอ็นต์ OAuth ซึ่งฉันใช้กับ Twitter และ TwitPic ได้สำเร็จ ใช้งานง่าย เป็นโอเพนซอร์สและมีให้ที่นี่: Oauth.cs

สำหรับการตรวจสอบใน OAuth 1.0a ... ตลกดีนะมีชื่อพิเศษและดูเหมือน "มาตรฐาน" แต่เท่าที่ฉันรู้บริการเดียวที่ใช้ "OAuth 1.0a" คือ Twitter ฉันเดาว่าเป็นมาตรฐานเพียงพอ โอเคอย่างไรก็ตามใน OAuth 1.0a วิธีการทำงานสำหรับแอปเดสก์ท็อปคือ:

  1. คุณซึ่งเป็นผู้พัฒนาแอปลงทะเบียนแอปและรับ "รหัสผู้บริโภค" และ "ความลับของผู้บริโภค" ใน Arstechnica มีการวิเคราะห์ที่เขียนได้ดีว่าทำไมรุ่นนี้จะไม่ดีที่สุดแต่เป็นพวกเขากล่าวว่ามันเป็นสิ่งที่มันเป็น

  2. แอปของคุณทำงาน ในครั้งแรกที่ทำงานผู้ใช้ต้องให้การอนุมัติอย่างชัดเจนเพื่อให้แอปส่งคำขอ REST ที่ตรวจสอบสิทธิ์โดยอัตโนมัติไปยัง Twitter และบริการในเครือเดียวกัน (เช่น TwitPic) ในการดำเนินการนี้คุณต้องผ่านขั้นตอนการอนุมัติซึ่งเกี่ยวข้องกับการอนุมัติอย่างชัดเจนโดยผู้ใช้ สิ่งนี้จะเกิดขึ้นในครั้งแรกที่แอปทำงานเท่านั้น แบบนี้:

    • ขอ "โทเค็นคำขอ" Aka โทเค็นชั่วคราว
    • เปิดหน้าเว็บโดยส่งโทเค็นคำขอนั้นเป็นพารามิเตอร์แบบสอบถาม หน้าเว็บนี้แสดง UI แก่ผู้ใช้โดยถามว่า "คุณต้องการให้สิทธิ์การเข้าถึงแอปนี้หรือไม่"
    • ผู้ใช้ล็อกอินเข้าสู่หน้าเว็บ twitter และอนุญาตหรือปฏิเสธการเข้าถึง
    • หน้า html ตอบกลับจะปรากฏขึ้น หากผู้ใช้ให้สิทธิ์การเข้าถึงจะมี PIN แสดงเป็นแบบอักษร 48 pt
    • ตอนนี้ผู้ใช้จำเป็นต้องตัด / วางหมุดนั้นลงในกล่องฟอร์มของ windows แล้วคลิก "ถัดไป" หรือสิ่งที่คล้ายกัน
    • จากนั้นแอปเดสก์ท็อปจะทำการร้องขอ "โทเค็นการเข้าถึง" ที่ผ่านการตรวจสอบสิทธิ์ คำขอ REST อื่น
    • แอปเดสก์ท็อปจะได้รับ "โทเค็นการเข้าถึง" และ "ความลับการเข้าถึง"

หลังจากได้รับการอนุมัติแล้วแอปเดสก์ท็อปสามารถใช้ "โทเค็นการเข้าถึง" และ "ความลับการเข้าถึง" เฉพาะผู้ใช้ (พร้อมกับ "รหัสผู้ใช้" และ "ความลับของผู้บริโภค" เฉพาะแอป) เพื่อดำเนินการตามคำขอที่ได้รับการรับรองความถูกต้องในนามของผู้ใช้ ไปที่ Twitter สิ่งเหล่านี้จะไม่มีวันหมดอายุแม้ว่าผู้ใช้จะยกเลิกการอนุญาตแอปหรือหาก Twitter ยกเลิกการอนุญาตแอปของคุณด้วยเหตุผลบางประการหรือหากคุณสูญเสียโทเค็นการเข้าถึงและ / หรือความลับคุณจะต้องดำเนินการอนุมัติอีกครั้ง .


หากคุณไม่ฉลาดโฟลว์ UI สามารถจัดเรียงมิเรอร์โฟลว์ข้อความ OAuth แบบหลายขั้นตอนได้ มีวิธีที่ดีกว่า

ใช้การควบคุมเว็บเบราว์เซอร์และเปิดหน้าเว็บอนุญาตภายในแอปเดสก์ท็อป เมื่อผู้ใช้คลิก "อนุญาต" ให้ดึงข้อความตอบกลับจากการควบคุมเว็บเบราว์เซอร์นั้นแยก PIN โดยอัตโนมัติจากนั้นรับโทเค็นการเข้าถึง คุณส่งคำขอ HTTP 5 หรือ 6 รายการ แต่ผู้ใช้ต้องการเห็นไดอะล็อก Allow / Deny เพียงช่องเดียว เรียบง่าย

แบบนี้:
ข้อความแสดงแทน


หากคุณได้รับการจัดเรียง UI ความท้าทายเดียวที่ยังคงอยู่คือการสร้างคำขอที่ลงนามโดยอัตโนมัติ การเดินทางนี้มีผู้คนจำนวนมากเนื่องจากข้อกำหนดในการลงนาม oauth เป็นประเภทเฉพาะ นั่นคือสิ่งที่คลาส OAuth Manager แบบง่ายทำ

ตัวอย่างรหัสเพื่อขอโทเค็น:

var oauth = new OAuth.Manager();
// the URL to obtain a temporary "request token"
var rtUrl = "https://api.twitter.com/oauth/request_token";
oauth["consumer_key"] = MY_APP_SPECIFIC_KEY;
oauth["consumer_secret"] = MY_APP_SPECIFIC_SECRET;    
oauth.AcquireRequestToken(rtUrl, "POST");

นั่นแหล่ะ เรียบง่าย ดังที่คุณเห็นจากโค้ดวิธีการเข้าถึงพารามิเตอร์ oauth คือผ่านตัวทำดัชนีแบบสตริงเช่นพจนานุกรม เมธอด AcquireRequestToken จะส่งคำขอที่ลงนามโดย oauth ไปยัง URL ของบริการที่ให้โทเค็นคำขอหรือที่เรียกว่าโทเค็นชั่วคราว สำหรับ Twitter URL นี้คือ " https://api.twitter.com/oauth/request_token " ข้อกำหนด oauth ระบุว่าคุณต้องแพ็คชุดพารามิเตอร์ oauth (โทเค็น, token_secret, nonce, การประทับเวลา, รหัสผู้บริโภค, เวอร์ชันและการโทรกลับ) ในลักษณะใดวิธีหนึ่ง (เข้ารหัส URL และเข้าร่วมด้วยเครื่องหมายแอมเปอร์แซนด์) และในเชิงศัพท์ - เรียงลำดับสร้างลายเซ็นบนผลลัพธ์นั้นจากนั้นรวมพารามิเตอร์เดียวกันเหล่านั้นพร้อมกับลายเซ็นที่จัดเก็บไว้ในพารามิเตอร์ oauth_signature ใหม่ด้วยวิธีอื่น (รวมด้วยลูกน้ำ) คลาสตัวจัดการ OAuth จะดำเนินการให้คุณโดยอัตโนมัติ มันสร้าง nonces และการประทับเวลาและเวอร์ชันและลายเซ็นโดยอัตโนมัติ - แอปของคุณไม่จำเป็นต้องดูแลหรือตระหนักถึงสิ่งนั้น เพียงตั้งค่าพารามิเตอร์ oauth และทำการเรียกใช้เมธอดง่ายๆ ระดับผู้จัดการจะส่งคำขอและแยกวิเคราะห์การตอบกลับให้คุณ

ตกลงแล้วไง เมื่อคุณได้รับโทเค็นคำขอแล้วคุณจะเปิด UI ของเว็บเบราว์เซอร์ซึ่งผู้ใช้จะให้การอนุมัติอย่างชัดเจน ถ้าคุณทำถูกต้องคุณจะปรากฏในเบราว์เซอร์ในตัว สำหรับ Twitter URL สำหรับสิ่งนี้คือ " https://api.twitter.com/oauth/authorize?oauth_token= " พร้อมกับ oauth_token ต่อท้าย ทำในโค้ดดังนี้:

var url = SERVICE_SPECIFIC_AUTHORIZE_URL_STUB + oauth["token"];
webBrowser1.Url = new Uri(url);

(หากคุณทำสิ่งนี้ในเบราว์เซอร์ภายนอกที่คุณใช้System.Diagnostics.Process.Start(url))

การตั้งค่าคุณสมบัติ Url ทำให้ตัวควบคุมเว็บเบราว์เซอร์นำทางไปยังเพจนั้นโดยอัตโนมัติ

เมื่อผู้ใช้คลิกปุ่ม "อนุญาต" หน้าใหม่จะถูกโหลด เป็นรูปแบบ HTML และใช้งานได้เหมือนกับเบราว์เซอร์เต็มรูปแบบ ในรหัสของคุณลงทะเบียนตัวจัดการสำหรับเหตุการณ์ DocumentedCompleted ของตัวควบคุมเว็บเบราว์เซอร์และในตัวจัดการนั้นให้จับพิน:

var divMarker = "<div id=\"oauth_pin\">"; // the div for twitter's oauth pin
var index = webBrowser1.DocumentText.LastIndexOf(divMarker) + divMarker.Length;
var snip = web1.DocumentText.Substring(index);
var pin = RE.Regex.Replace(snip,"(?s)[^0-9]*([0-9]+).*", "$1").Trim();

นั่นคือการขูดหน้าจอ HTML เล็กน้อย

หลังจากจับพินแล้วคุณไม่จำเป็นต้องใช้เว็บเบราว์เซอร์อีกต่อไปดังนั้น:

webBrowser1.Visible = false; // all done with the web UI

... และคุณอาจต้องการเรียก Dispose () ด้วยเช่นกัน

ขั้นตอนต่อไปคือการรับโทเค็นการเข้าถึงโดยการส่งข้อความ HTTP อื่นพร้อมกับพินนั้น นี่คือการเรียก oauth ที่ลงนามอีกครั้งซึ่งสร้างขึ้นด้วยการสั่งซื้อและการจัดรูปแบบที่ฉันอธิบายไว้ข้างต้น แต่อีกครั้งมันง่ายมากกับคลาส OAuth.Manager:

oauth.AcquireAccessToken(URL_ACCESS_TOKEN,
                         "POST",
                         pin);

สำหรับ Twitter URL นั้นคือ " https://api.twitter.com/oauth/access_token "

ตอนนี้คุณมีโทเค็นการเข้าถึงแล้วและคุณสามารถใช้โทเค็นเหล่านี้ในคำขอ HTTP ที่ลงนามได้ แบบนี้:

var authzHeader = oauth.GenerateAuthzHeader(url, "POST");

... urlจุดสิ้นสุดของทรัพยากรอยู่ที่ไหน หากต้องการอัปเดตสถานะของผู้ใช้จะเป็น " http://api.twitter.com/1/statuses/update.xml?status=Hello "

จากนั้นตั้งค่าสตริงที่ลงในส่วนหัว HTTP ชื่อการอนุมัติ

ในการโต้ตอบกับบริการของบุคคลที่สามเช่น TwitPic คุณต้องสร้างส่วนหัว OAuth ที่แตกต่างกันเล็กน้อยดังนี้:

var authzHeader = oauth.GenerateCredsHeader(URL_VERIFY_CREDS,
                                            "GET",
                                            AUTHENTICATION_REALM);

สำหรับ Twitter ค่าของ URL และขอบเขตการยืนยันเครดิตคือ " https://api.twitter.com/1/account/verify_credentials.json " และ " http://api.twitter.com/ " ตามลำดับ

... และใส่สิ่งนั้นสตริงอนุมัติในส่วนหัว HTTP ที่เรียกว่าX-ตรวจสอบข้อมูลประจำตัว--Authorization จากนั้นส่งไปที่บริการของคุณเช่น TwitPic พร้อมกับคำขอใด ๆ ที่คุณส่ง

แค่นั้นแหละ.

โดยรวมแล้วรหัสสำหรับอัปเดตสถานะ Twitter อาจเป็นดังนี้:

// the URL to obtain a temporary "request token"
var rtUrl = "https://api.twitter.com/oauth/request_token";
var oauth = new OAuth.Manager();
// The consumer_{key,secret} are obtained via registration
oauth["consumer_key"] = "~~~CONSUMER_KEY~~~~";
oauth["consumer_secret"] = "~~~CONSUMER_SECRET~~~";
oauth.AcquireRequestToken(rtUrl, "POST");
var authzUrl = "https://api.twitter.com/oauth/authorize?oauth_token=" + oauth["token"];
// here, should use a WebBrowser control. 
System.Diagnostics.Process.Start(authzUrl);  // example only!
// instruct the user to type in the PIN from that browser window
var pin = "...";
var atUrl = "https://api.twitter.com/oauth/access_token";
oauth.AcquireAccessToken(atUrl, "POST", pin);

// now, update twitter status using that access token
var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
var request = (HttpWebRequest)WebRequest.Create(appUrl);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);

using (var response = (HttpWebResponse)request.GetResponse())
{
    if (response.StatusCode != HttpStatusCode.OK)
        MessageBox.Show("There's been a problem trying to tweet:" +
                        Environment.NewLine +
                        response.StatusDescription);
}

OAuth 1.0a มีความซับซ้อนภายใต้ฝาครอบ แต่ไม่จำเป็นต้องใช้ OAuth.Manager จะจัดการการสร้างคำขอ oauth ที่ส่งออกและการรับและการประมวลผลเนื้อหา oauth ในการตอบกลับ เมื่อคำขอ Request_token ให้ oauth_token แก่คุณแอปของคุณไม่จำเป็นต้องจัดเก็บ Oauth.Manager ฉลาดพอที่จะทำสิ่งนั้นโดยอัตโนมัติ ในทำนองเดียวกันเมื่อคำขอ access_token ได้รับโทเค็นการเข้าถึงและความลับกลับมาคุณไม่จำเป็นต้องจัดเก็บข้อมูลเหล่านั้นอย่างชัดเจน OAuth.Manager จะจัดการสถานะนั้นให้คุณ

ในการรันครั้งต่อ ๆ ไปเมื่อคุณมีโทเค็นการเข้าถึงและข้อมูลลับแล้วคุณสามารถสร้างอินสแตนซ์ OAuth.Manager ได้ดังนี้:

var oauth = new OAuth.Manager();
oauth["consumer_key"] = CONSUMER_KEY;
oauth["consumer_secret"] = CONSUMER_SECRET;
oauth["token"] = your_stored_access_token;
oauth["token_secret"] = your_stored_access_secret;

... แล้วสร้างส่วนหัวการอนุญาตตามด้านบน

// now, update twitter status using that access token
var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
var request = (HttpWebRequest)WebRequest.Create(appUrl);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);

using (var response = (HttpWebResponse)request.GetResponse())
{
    if (response.StatusCode != HttpStatusCode.OK)
        MessageBox.Show("There's been a problem trying to tweet:" +
                        Environment.NewLine +
                        response.StatusDescription);
}

คุณสามารถดาวน์โหลดDLL ที่มีระดับ OAuth.Manager ที่นี่ นอกจากนี้ยังมี helpfile ในการดาวน์โหลดนั้น หรือคุณสามารถดู helpfile ออนไลน์

ดูตัวอย่างของแบบฟอร์ม Windows ที่ใช้ผู้จัดการนี้ที่นี่


ตัวอย่างการทำงาน

ดาวน์โหลดตัวอย่างการทำงานของเครื่องมือบรรทัดคำสั่งที่ใช้คลาสและเทคนิคที่อธิบายไว้ที่นี่:


สวัสดีขอบคุณมากสำหรับคำตอบของคุณ! ฉันได้ย้ายจาก OAuth ไปแล้ว (ฉันยอมแพ้ Mendeley และเลือกทางเลือกอื่นแล้ว) - แต่ฉันอ่านคำตอบของคุณแล้วมันก็สมเหตุสมผลและครอบคลุมมาก ฉันยังบุ๊กมาร์กชั้นเรียนที่คุณเขียนไว้สำหรับอนาคตที่ฉันอาจต้องการ! ขอบคุณมากอีกครั้ง
ยอห์น

2
สวัสดี Cheeso ขอขอบคุณสำหรับการแบ่งปันรหัสและคำอธิบายโดยละเอียดของคุณ คุณให้วิธีแก้ปัญหาที่ยอดเยี่ยม แต่เรียบง่าย อย่างไรก็ตามคุณจะต้องทำการเปลี่ยนแปลงเล็กน้อยในเมธอด GetSignatureBase ของคุณเพื่อรองรับโซลูชันที่ไม่ใช่ "oob" สำหรับการไม่ "oob" คุณต้องเข้ารหัส URL ของการเรียกกลับดังนั้นคุณจะต้องเพิ่มสิ่งนี้เมื่อคุณทำซ้ำผ่าน this._params: if (p1.Key == "callback") {p.Add ( "oauth_" + p1.Key, UrlEncode (p1.Value)); ต่อ;}
Johnny Oshika

1
สิ่งนี้ใช้ไม่ได้กับ OAuth 2.0 คลาสนี้มีไว้สำหรับ OAuth 1.0a OAuth2.0 นั้นใช้งานง่ายกว่าอย่างมากเนื่องจากไม่มีการลงนามและการเรียงลำดับคำศัพท์ของพารามิเตอร์ต่างๆ ดังนั้นคุณอาจไม่จำเป็นต้องมีคลาสภายนอกเพื่อทำ OAuth 2.0 หรือ ... ถ้าคุณต้องการคลาสภายนอกมันจะง่ายกว่าคลาสนี้มาก
Cheeso

1
ไม่พบ helpfile ออนไลน์ : cheeso.members.winisp.net/OAuthManager1.1
Kiquenet

3
ดูเหมือนว่าลิงก์ทั้งหมดจะเสีย ฉันพบสำเนาที่นี่: gist.github.com/DeskSupport/2951522#file-oauth-cs
John
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.