The Windows Azure Toolkits – Integrated ACS with iOS、Windows Phone、Android and Windows 8

隨著Windows Azure Platform的發展,其支援的平台及語言也越來越多,行動裝置自然也是重點之一,從去年開始,Windows Azure Toolkit for Windows Phone率先推出,
緊接著for iOS與Android版本也釋出了。10個月後的今天,這些Windows Azure Toolkits隨著Windows Azure Platform的發展逐步演進

The Windows Azure Toolkits – Integrated ACS with iOSWindows PhoneAndroid and Windows 8

 

/黃忠成

 

   隨著Windows Azure Platform的發展,其支援的平台及語言也越來越多,行動裝置自然也是重點之一,從去年開始,Windows Azure Toolkit for Windows Phone率先推出,

緊接著for iOS與Android版本也釋出了。10個月後的今天,這些Windows Azure Toolkits隨著Windows Azure Platform的發展逐步演進,除了修正了許多Bugs之外,

還加入了新的生力軍:Windows Azure Toolkits for Windows 8。

圖1

這些Toolkits的功能其實都差不多,主要是提供Storage  Services(Table、Queue、Blob)存取能力,還有與ACS(Access Control Service)整合的能力,

在新加入的Windows Azure Toolkit for Windows 8中還支援了Service Bus。

本文先將重點擺在ACS部分,其餘的Storage Services部分可請讀者先下載去年Tech Days的投影片及範例,未來我會抽空補充這一部分。

http://www.code6421.com/TechDays2011/Publish.zip

 

關於ACS

 

ACS是Windows Azure Platform所提供的服務之一,主要是負責使用者身分驗證的動作,ACS本身是一個中介者,協助應用程式使用不同的Identity Providers(Facebook、

Google、Windows Live、ADFS、Open ID)來進行使用者驗證的動作。

應用程式使用ACS來進行驗證最大的好處在於可允許使用者透過主流的Identity Providers來登入你提供的服務,在現今這個社群導向的年代,使用行動裝置的使用者應該

都至少擁有Facebook、Windows Live、Google、Yahoo中的一個帳號,而ACS支援這些主流社群網站,也就是說你的服務可以不必要求使用者註冊,也不必儲存使用者的帳號密碼,

透過ACS,應用程式只需要取得需要的資料即可(like Email),也因為不須註冊就可以使用服務,使用者可以跳過繁瑣的註冊動作直接使用服務,這將提升使用者試用你的服務的意願。

另外,ACS也可作為一種Single Sign-On(單一簽入)的解決方案,不過這不在本文的討論範圍。

 

設定ACS

 

首先,你得先有Windows Azure帳號才能使用ACS,需要的請至以下網址申請測試帳號(免費的哦)

http://www.windowsazure.com/zh-tw/

  有了帳號後,就可到Windows Azure Portal中啟用ACS。

圖2

點選上方的新增來添加一個服務命名空間。

圖3

圖4

於此填入命名空間的名稱及選擇國家/地區,完成後等待此服務空間啟用。

圖5

本文的範例是使用Facebook作為驗證的Identity Provider,所以需要至http://developers.facebook.com/來申請建立一個應用程式(注意,

你必須擁有Facebook帳號及通過手機簡訊驗證才能建立Facebook應用程式)。

圖6

注意App Domains及Website with Facebook Login欄位,此處需填入你的ACS 網站網址,通常是<服務命名空間>.accesscontrol.windows.net。

接著使用瀏覽器開啟https://<服務命名空間>.accesscontrol.windows.net,便可看到類似下圖的畫面。

圖7

點選識別提供者,接著再點選新增。

圖8

此處選擇Facebook應用程式。

圖9

這裡需輸入一個顯示名稱,此處使用Facebook,接著要填入之前建立Facebook的應用程式識別碼及密碼(應用程式密鑰)。

接著點選左方的信賴憑證者應用程式來新增一個應用程式。

圖7

圖10

此處需輸入顯示名稱及領域,領域必須是一個URL,可使用localhost(這只是一個識別名稱),接著選擇權杖格式為SWT,下方會出現要求產生憑證的部分。

圖11

此處只須點選產生後按下儲存即可,這樣就算完成ACS整合Facebook Identity Provider的部分了,接下來就是開始透過應用程式連接ACS來進行與Facebook的整合驗證。

 

 

Windows Phone 7

 

  透過Windows Azure Toolkit for Windows Phone,應用程式可直接使用其提供的Library&Controls來存取Storage Services及ACS,讀者可以由以下網址下載。

http://watwp.codeplex.com/

要連接ACS,只需建立一個新的Windows Phone Application專案,並將WAToolkitForWP7\Samples\WP7.1\Libraries\SL.Phone.Federation目錄下的.csproj加到方案中,

再由Windows Phone Application專案加入對SL.Phone.Federation的參考即可。

圖12

接著修改App.xaml,加入以下內容。

圖13

建立一個新的頁面(SignInControl),並加入Azure Tookit所提供的Login控制項。

圖14

Realm需對應信賴者憑證應用程式中的領域設定。

圖10

ServiceNamespace則對應ACS命名空間(我們的ACS網址是demoacs3.accesscontrol.windows.net,所以此處為demoacs3)。

修改主頁面(MainPage.xaml)。

……………………
using SL.Phone.Federation.Utilities;

namespace DemoACS
{
    public partial class MainPage : PhoneApplicationPage
    {
        RequestSecurityTokenResponseStore _rstrStore = null;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
            _rstrStore = (RequestSecurityTokenResponseStore)App.Current.Resources["rstrStore"];
        }

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (!_rstrStore.ContainsValidRequestSecurityTokenResponse())
            {
                NavigationService.Navigate(new Uri("/SignInControl.xaml", UriKind.Relative));
            }
            else
            {
                textBlock1.Text = "thanks for your login";
            }

        }
    }
}

 

完成後測試,將可看到以下的畫面。

圖15

圖16

輸入帳密後,正確的話就可看到圖17。

圖17

 

Android

 

   Windows Azure Toolkit for Android可由以下網址下載。

https://github.com/WindowsAzure-Toolkits/wa-toolkit-android

下載後解壓,透過Eclipse將WAToolkitForAndroid\library\accesscontrol Import到現在使用的Workspace,接著建立一個Android Application Project,Build SDK請選擇Android 2.2,

修改此Project的Properties中Android頁面設定,將accesscontrol這個Project新增為Library。

圖18

修改Project中的AndroidManifest.xml如下。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.demoacsb"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SuccessLoginActivity"
            android:label="@string/title_activity_success_login" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".UnsuccessLogin"
            android:label="@string/title_activity_unsuccess_login" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity android:name="com.microsoft.samples.windowsazure.android.accesscontrol.login.AccessControlLoginActivity" android:label="@string/app_name" />        
		<activity android:name="com.microsoft.samples.windowsazure.android.accesscontrol.login.AccessControlWebAuthActivity" android:label="@string/app_name" />
    </application>

</manifest>

加入Resource Strings。

/res/values/strings.xml

<resources>

    <string name="app_name">DemoACSB</string>
    <string name="hello_world">Hello world!</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">MainActivity</string>
    <string name="cloud_ready_acs_namespace">demoacs3</string>
    <string name="cloud_ready_acs_realm">http://www.code6421.com</string>
    <string name="cloud_ready_acs_symmetric_key">9FhZFekCsz1YsWpjdiQxJAbh2AhW3WS/aQ----blx3g=</string>
    <string name="title_activity_success_login">SuccessLoginActivity</string>
    <string name="title_activity_unsuccess_login">UnsuccessLogin</string>

</resources>

注意cloud_ready_acs_symmetric_key欄位,這裡要放置ACS中的憑證金鑰,另外cloud_ready_acs_realm則是對應信賴者憑證應用程式中的領域設定。

加入兩個Activity。

 

SuccessLoginActivity.java

 

package com.example.demoacsb;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class SuccessLoginActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_success_login);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_success_login, menu);
        return true;
    }    
}

/res/layout/activity_success_login.xml

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:padding="@dimen/padding_medium"
        android:text="Login OK"
        tools:context=".SuccessLoginActivity" />
</RelativeLayout>

 

UnsuccessLogin.java

 

package com.example.demoacsb;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class UnsuccessLogin extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_unsuccess_login);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_unsuccess_login, menu);
        return true;
    }    
}

/res/layout/activity_unsuccess_login.xml

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:padding="@dimen/padding_medium"
        android:text="Login Fail"
        tools:context=".UnsuccessLogin" />
</RelativeLayout>

在主要的Activity對應的Layout加入一個Button。

res/layout/activity_main.xml

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="162dp"
        android:text="Login" />
</RelativeLayout>

鍵入以下的程式碼。

MainActivity.java

package com.example.demoacsb;

import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.support.v4.app.NavUtils;
import com.microsoft.samples.windowsazure.android.accesscontrol.core.IdentityProvidersRepository;
import com.microsoft.samples.windowsazure.android.accesscontrol.login.AccessControlLoginActivity;
import com.microsoft.samples.windowsazure.android.accesscontrol.login.AccessControlLoginContext;
import com.microsoft.samples.windowsazure.android.accesscontrol.swt.SimpleWebTokenHandler; 

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    	Button manualLoginButton = (Button) findViewById(R.id.button1);    	
		manualLoginButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {	
				doManualLogin();
			}
		}); 
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        
        return true;
    }
    
    private void doManualLogin() {
    	if (!validateACSConfiguration()) return;
    	
    	String acsNamespace = getString(R.string.cloud_ready_acs_namespace);
    	String acsRealm = getString(R.string.cloud_ready_acs_realm);
    	String acsSymmKey = getString(R.string.cloud_ready_acs_symmetric_key);
    	
		Intent intent = new Intent(this, AccessControlLoginActivity.class);

		AccessControlLoginContext loginContext = new AccessControlLoginContext();
    	loginContext.IdentityProviderRepository = new IdentityProvidersRepository(
    			String.format(
    					"https://%s.accesscontrol.windows.net/v2/metadata/IdentityProviders.js?protocol=javascriptnotify&realm=%s&version=1.0",
    					acsNamespace, acsRealm));    	
    	loginContext.AccessTokenHandler = new SimpleWebTokenHandler(acsRealm, acsSymmKey);
    	loginContext.SuccessLoginActivity = SuccessLoginActivity.class;
    	loginContext.ErrorLoginActivity = com.example.demoacsb.UnsuccessLogin.class;
    	intent.putExtra(AccessControlLoginActivity.AccessControlLoginContextKey, loginContext);	
	
    	startActivity(intent);    
    } 
    
    private boolean validateACSConfiguration() {
    	String acsNamespace = getString(R.string.cloud_ready_acs_namespace);
    	String acsRealm = getString(R.string.cloud_ready_acs_realm);
    	String acsSymmKey = getString(R.string.cloud_ready_acs_symmetric_key);

    	if (isValidConfigurationValue(acsNamespace) && isValidConfigurationValue(acsRealm) && isValidConfigurationValue(acsSymmKey))
    		return true;
    	
		AlertDialog dialog = new AlertDialog.Builder(this).create();
		dialog.setTitle("Configuration Error");
		dialog.setCanceledOnTouchOutside(true);
		dialog.setCancelable(true);
		dialog.setMessage("Please review your ACS configuration and try again");
		dialog.show();

		return false;
    } 
    
    private boolean isValidConfigurationValue(String value) {
		return !(value.startsWith("{") || value.trim() == "");
	}     
}

完成後執行前,請特別注意要使用Android 2.2的模擬器,因為2.2以上的模擬器在WebView物件處理上有Bugs,無法處理Scripting Injection部分。

圖19

圖20

圖21

圖22

 

iOS

 

Windows Azure Toolkit for iOS 可由以下網址下載。

https://github.com/WindowsAzure-Toolkits/wa-toolkit-ios/

下載後須先透過XCode 開啟WAToolkitForIOS\library\watoolkitios-lib.xcodeproj這個Project,然後進行編譯後取得.a及對應的.h檔案。

接著透過XCode建立一個Single View的Project,先取名為DemoACS,將先前取得的.h檔案複製到此Project目錄下的watoolkitios-lib目錄中,並將.a檔案新增進此專案中,

接著修改DemoACS-Prefix.pch加入import。

DeniACS-Prefix.pch

//
// Prefix header for all source files of the 'DemoACS' target in the 'DemoACS' project
//

#import 

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import 
    #import 
    #import "watoolkitios-lib/WAToolkit.h"
#endif

然後修改MainStoryboard_iPhone.storyboard加入一個Button,並修改DemoACSViewController.h加入一個method。

DemoACSViewController.h

//
//  DemoACSViewController.h
//  DemoACS
//
//  Created by  on 12/7/6.
//  Copyright 2012年 __MyCompanyName__. All rights reserved.
//

#import 

@interface DemoACSViewController : UIViewController {}
- (IBAction)loginAction:(id)sender;
@end

緊接著修改DemoACSViewController.m。

//
//  DemoACSViewController.m
//  DemoACS
//
//  Created by  on 12/7/6.
//  Copyright 2012年 __MyCompanyName__. All rights reserved.
//

#import "DemoACSViewController.h"

NSString * const ACSNamespace = @"demoacs3";
NSString * const ACSRealm = @"http://www.code6421.com";
NSString * const NameIdentifierClaim = @"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
NSString * const AccessTokenClaim = @"http://www.facebook.com/claims/AccessToken";


@implementation DemoACSViewController- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidUnload
{    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
	[super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
	[super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
}
- (IBAction)loginAction:(id)sender {
    WACloudAccessControlClient *acsClient = [WACloudAccessControlClient accessControlClientForNamespace:ACSNamespace realm:ACSRealm];
    [acsClient showInViewController:self allowsClose:NO withCompletionHandler:^(BOOL authenticated) { 
        if (!authenticated) { 
            UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Info" message:@"Login Fail" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [dialog show];
        } else {
            UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Info" message:@"Login Successed" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [dialog show];         
        }
    }];
    
}
    
@end

最後透過Interface Builder把loginAction與畫面上的Button建立繫結,然後修改此Project的Build Settings中的Other Link Flags為  -ObjC -all_load 即可。

圖23

圖24

圖25

圖26

 

 

Windows 8

 

  Windows Azure Toolkit for Windows 8可由下列網址下載。

http://watwindows8.codeplex.com/

不過此版本目前僅支援Consumer Preview及Visual Studio 2012 Beta,要動點手腳才能正常使用,另外,其ACS部分僅提供使用protocol為WS-Federation模式的驗證,跟我們前面使用

javascriptnotify為protocol的方式不同,前者目前需要一個收取claims的網站,因此我修改了Toolkit中的ACS部分,使其支援javascriptnotify驗證模式(載點在文末)。

 請先下載我所提供的ACS Library,然後建立一個Windows Metro style App 專案,加入兩個類別。

LoginEventArgs.cs

 

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Windows8.CSharp.Identity.AccessControl
{
    using System;
    using Windows8.Identity.AccessControl;

    public class LoginEventArgs : EventArgs
    {
        public LoginEventArgs(IAuthenticationResult result)
        {
            this.Result = result;
        }

        public IAuthenticationResult Result { get; private set; }
    }
}

 

ProvidersInfoEventArgs.cs

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Windows8.CSharp.Identity.AccessControl
{
    using System;
    using Windows8.Identity.AccessControl;

    public class ProvidersInfoEventArgs : EventArgs
    {
        public ProvidersInfoEventArgs(IIdentityProvidersInfo info)
        {
            this.ProvidersInfo = info;
        }

        public IIdentityProvidersInfo ProvidersInfo { get; private set; }
    }
}

接著加入一個UserControl,取名為ACSLogin。

ACSLogin.xaml

 

<UserControl x:Class="Windows8.CSharp.Identity.AccessControl.ACSLogin"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.Resources>
        <Style x:Key="TitleStyle" TargetType="TextBlock">
            <Setter Property="Foreground" Value="#707070" />
            <Setter Property="FontFamily" Value="Segoe UI Light" />
            <Setter Property="FontSize" Value="16" />
        </Style>
        <Style x:Key="ListBoxTextStyle" TargetType="TextBlock">
            <Setter Property="FontFamily" Value="Segoe UI Light" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="Padding" Value="14" />
        </Style>
    </UserControl.Resources>

    <StackPanel Name="root">
        <TextBlock Text="Select one of the following identity providers to authenticate using ACS:" Style="{StaticResource TitleStyle}" TextWrapping="Wrap" />
        <ProgressBar x:Name="ProgressBar" Margin="0 50 0 0" IsIndeterminate="True" Width="500" HorizontalAlignment="Left" Visibility="Collapsed" />
        <ListBox x:Name="IdentityProviderList" Margin="0 25 0 0" HorizontalAlignment="Left" BorderThickness="0" Width="500" Visibility="Collapsed">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" Style="{StaticResource ListBoxTextStyle}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</UserControl>

 

ACSLogin.xaml.cs

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Windows8.CSharp.Identity.AccessControl
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Windows.Foundation;
    using Windows.UI.Core;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Data;
    using Windows8.Identity.AccessControl;

    public sealed partial class ACSLogin
    {
        public static readonly DependencyProperty ACSNamespaceProperty = DependencyProperty.Register("AccessControlNamespace", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));
        public static readonly DependencyProperty RealmProperty = DependencyProperty.Register("Realm", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));
        public static readonly DependencyProperty BouncerReplyUrlProperty = DependencyProperty.Register("BouncerReplyUrl", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));
        public static readonly DependencyProperty BouncerEndUrlProperty = DependencyProperty.Register("BouncerEndUrl", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));

        private AccessControlManager accessControlManager;

        public ACSLogin()
        {
            InitializeComponent();

            accessControlManager = new AccessControlManager();
        }

        public string AccessControlNamespace
        {
            get
            {
                return (string)GetValue(ACSNamespaceProperty);
            }
            set
            {
                SetValue(ACSNamespaceProperty, value);
            }
        }

        public string Realm
        {
            get
            {
                return (string)GetValue(RealmProperty);
            }
            set
            {
                SetValue(RealmProperty, value);
            }
        }

        public string BouncerReplyUrl
        {
            get
            {
                return (string)GetValue(BouncerReplyUrlProperty);
            }
            set
            {
                SetValue(BouncerReplyUrlProperty, value);
            }
        }

        public string BouncerEndUrl
        {
            get
            {
                return (string)GetValue(BouncerEndUrlProperty);
            }
            set
            {
                SetValue(BouncerEndUrlProperty, value);
            }
        }

        public void Login()
        {
            this.ConfigureAccessControlManager();

            this.ProgressBar.Visibility = Windows.UI.Xaml.Visibility.Visible;
            var getOperation = accessControlManager.GetIdentityProviders(true);

            getOperation.Completed = ((info, status) =>
            {
                var ipInfo = info.GetResults();
                this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    () =>
                    {
                        if (OnProviderListRetrieved != null) OnProviderListRetrieved(this, new ProvidersInfoEventArgs(ipInfo));

                        foreach (var ip in ipInfo.IdentityProviderList) 
                            IdentityProviderList.Items.Add(ip);

                        this.IdentityProviderList.SelectionChanged += new SelectionChangedEventHandler(IdentityProviderListItemSelected);
                        this.ProgressBar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                        this.IdentityProviderList.Visibility = Windows.UI.Xaml.Visibility.Visible;
                    });
            });
        }

        public event EventHandler OnLogin;

        public event EventHandler OnProviderListRetrieved;

        private void IdentityProviderListItemSelected(object sender, SelectionChangedEventArgs e)
        {
            var item = this.IdentityProviderList.SelectedItem as IIdentityProvider;
            IdentityProviderList.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            var loginOperation = item.Login(root, true);

            loginOperation.Completed = ((info, status) =>
            {
                if (OnLogin != null) OnLogin(this, new LoginEventArgs(info.GetResults()));
            });
        }

        private void ConfigureAccessControlManager()
        {
            this.accessControlManager.AccessControlNamespace = this.AccessControlNamespace;
            this.accessControlManager.Realm = this.Realm;
            this.accessControlManager.BouncerReplyUrl = this.BouncerReplyUrl;
            this.accessControlManager.BouncerEndUrl = this.BouncerEndUrl;
        }
    }
}

最後在MainPage中添加ACSLogin控制項即可。

 

<Page
    x:Class="App13.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App13"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:loginControl="using:Windows8.CSharp.Identity.AccessControl"
    mc:Ignorable="d" Loaded="MainPage_Loaded_1">

    <Grid x:Name="root" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <loginControl:ACSLogin x:Name="login" AccessControlNamespace="demoacs3" Realm="http://www.code6421.com" BouncerEndUrl="http://localhost" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Page>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows8.CSharp.Identity.AccessControl;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace App13
{
    /// 
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// 
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            login.OnLogin += login_OnLogin;
        }

        void login_OnLogin(object sender, LoginEventArgs e)
        {
            new MessageDialog(e.Result.Token).ShowAsync();
        }

        /// 
        /// Invoked when this page is about to be displayed in a Frame.
        /// 
        /// 
Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private void MainPage_Loaded_1(object sender, RoutedEventArgs e)
        {
            login.Login();
        }
    }
}

圖27

圖28

圖29

就這樣,打完收工,下次有機會再來聊聊Storage Access部分。

 

ACS Library for Windows 8

http://www.code6421.com/BlogPics/W8ACS.zip