Win10/UWP开发—凭据保险箱PasswordVault

Cle****-he UID.1073626
2015-12-15 发表

本帖最后由 Clever-he 于 2015-12-15 10:06 编辑

PasswordVault用户凭据保险箱其实并不算是Win10的新功能,早在Windows 8.0时代就已经存在了,本文仅仅是介绍在UWP应用中如何使用凭据保险箱进行安全存储和检索用户凭据。

那么什么是凭据保险箱呢?简单的说就是开发者可以在用户输入完凭证(一般是用户名和密码),凭证有效的情况下将该凭证存储在叫做"凭据保险箱"里,该凭据保险箱里的用户凭据将会自动漫游到用户设备的Windows账户中并随时能够再次被App获取。

例如:有一个UWP的App运行在PC上,某用户在使用该App时登录了自己的账户并允许将账户凭据保存在"凭据保险箱"中,那么当该用户再使用平板电脑、手机或者其他同一个Windows账户的Windows设备时第一次打开该App,我们就可以从Windows账户中将该用户的数据漫游到新设备进行用户登录的操作。

说的简单点就是用户的账户和密码会被漫游到Windows账户中,日后及时是跨设备使用该App,只要设备Windows账户没变,就可以实现在新设备上快捷登陆的功能。

要想使用"凭据保险箱",我们需要了解以下几个类:

[mw_shl_code=csharp,true]– Windows.Security.Credentials.PasswordVault 表示凭据的"凭据保险箱"

保险箱的内容特定于应用程序或服务。应用程序和服务无法访问与其他应用程序或服务关联的凭据。

– PasswordVault.Add(PasswordCredential credential); 添加一个凭证保险箱

– PasswordVault.Remove(PasswordCredential credential); 移除一个凭证保险箱

– PasswordVault.Retrieve(System.String resource, System.String userName); 读取一个凭证保险箱

– PasswordVault.FindAllByResource(System.String resource); 搜索与指定资源相匹配的凭据保险箱

– Windows.Security.Credentials.PasswordCredential 表示凭据对象

– PasswordCredential.PasswordCredential(System.String resource, System.String userName, System.String password); 创建一个凭据对象

– PasswordCredential.UserName 凭据对象存储的UserName

– PasswordCredential.Resource 凭据对象所属的资源名

– PasswordCredential.Password 凭据对象存储的密码
[/mw_shl_code]

我们通过例子来看一下"凭据保险箱"是如何工作的(注:本文不考虑实现 记住密码 自动登陆 功能)

首先创建一个UWP通用项目,

然后新建一个LoginPage页面,并将起始页改为LoginPage页面

在LoginPage上布局出一个登陆框如下:

***附件停止解析***

xaml代码:
[mw_shl_code=csharp,true]<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="0,0,0,128" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBlock Text="账 户:"/>
<TextBox Text="{x:Bind UserName,Mode=TwoWay}" PlaceholderText="请输入您的账户" Width="240"/>
</StackPanel>
<StackPanel Margin="0,4" Orientation="Horizontal">
<TextBlock Text="密 码:"/>
<PasswordBox PlaceholderText="请输入您的密码" Password="{x:Bind Psw,Mode=TwoWay}" Width="240"/>
</StackPanel>

<CheckBox IsChecked="{x:Bind RoamCredential,Mode=TwoWay}" Content="漫游我的登录凭证" HorizontalAlignment="Right" ToolTipService.ToolTip="勾选漫游凭证后,您的登录信息将被漫游到本机WindowsLive账户" />

<Grid Margin="0,12,0,0" >
<Button Click="{x:Bind QuickLogin}" Content="WindowsLive快捷登陆"/>
<Button Click="{x:Bind Login}" Content="登 录" HorizontalAlignment="Right"/>
</Grid>
</StackPanel>
</Grid>[/mw_shl_code]

后台代码中,我们先实现"登录按钮"的Click方法:

[mw_shl_code=csharp,true]private async void Login(object sender, RoutedEventArgs args)
{
// 假设做用户账户和密码的验证工作(这里不考虑登录是否成功 一律登录成功)
await ShowLoding();
if (RoamCredential != true) return;
//添加用户凭证到 PasswordVault
var vault = new PasswordVault();
vault.Add(new PasswordCredential(
_resourceName, UserName, Psw));
}

/// <summary>
/// 显示登陆中Tip 并跳转到MainPage
/// </summary>
/// <returns>Null</returns>
private async Task ShowLoding()
{
var dialog = new ContentDialog
{
Title = "提示",
Content = "正在登录",
IsPrimaryButtonEnabled = true
};
#pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
dialog.ShowAsync();
#pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法

await Task.Delay(2000);
dialog.Content = "登录成功";
await Task.Delay(1000);
dialog.Hide();
Frame.Navigate(typeof(MainPage), _userName);
}
[/mw_shl_code]

假设用户的用户名和密码验证通过情况下,如果用户允许保存凭据到 "凭据保险箱",我们就可以使用PasswordVault对象的Add方法添加到"凭据保险箱"里一个PasswordCredential 凭据对象,然后跳转到MainPage,并传递用户名。

当用户点击使用"Windows账户快捷登陆"按钮时,我们首先去检测在该App的ResourceName下,一共有几个用户凭据,如果有多个则需要让用户选择一个账户进行登陆,代码如下:


[mw_shl_code=csharp,true]private async void QuickLogin(object sender, RoutedEventArgs args)
{
var credential = await GetCredentialFromLocker();
if (credential == null) return;
UserName = credential.UserName;
Psw = credential.Password;
await ShowLoding();
}
/// <summary>
/// 获取密码凭证
/// </summary>
/// <returns>PasswordCredential</returns>
private async Task<PasswordCredential> GetCredentialFromLocker()
{
PasswordCredential credential = null;

var vault = new PasswordVault();
var credentialList = vault.FindAllByResource(_resourceName);
if (credentialList.Count > 0)
{
if (credentialList.Count == 1)
{
credential = credentialList[0];
}
else
{
_defaultUserName = await GetDefaultUserNameUI(credentialList.Select(s => s.UserName));
if (!string.IsNullOrEmpty(_defaultUserName))
{
//读取凭证
credential = vault.Retrieve(_resourceName, _defaultUserName);
}
}
}

return credential;
}

/// <summary>
/// 获取一个用户名,如果存在多个用户凭证则选择一个
/// </summary>
/// <param name="userList">用户名集合</param>
/// <returns>UserName</returns>
private async Task<string> GetDefaultUserNameUI(IEnumerable<string> userList)
{
var userName = string.Empty;
var dialog = new ContentDialog
{
Title = "账户选择",
IsPrimaryButtonEnabled = true,
IsSecondaryButtonEnabled = true,
BorderThickness = new Thickness(0, 0, 1, 1),
PrimaryButtonText = "确定",
SecondaryButtonText = "取消"
};
var sp = new StackPanel();
var tb = new TextBlock { Text = "请选择您要登录哪个账户:" };
var cm = new ComboBox();
foreach (var user in userList)
{
// ReSharper disable once PossibleNullReferenceException
cm.Items.Add(new TextBlock
{
Text = user
});
}
cm.SelectedIndex = 0;
sp.Children.Add(tb);
sp.Children.Add(cm);
dialog.Content = sp;
dialog.PrimaryButtonClick += (s, a) =>
{
// ReSharper disable once PossibleNullReferenceException
userName = (cm.SelectedItem as TextBlock).Text;
};
dialog.SecondaryButtonClick += (s, a) => { dialog.Hide(); };
await dialog.ShowAsync();
return userName;
}[/mw_shl_code]

至此我们的App就支持了使用“凭据保险箱”功能,只要Windows账户一致,不论在什么windows设备上我们都可以做快捷登陆,下面是效果(本机demo的Win账户是公司的账户,和自己的手机上账户不一致,没法演示跨设备,不过我们可以使用删除App重新安装的方式来查看凭据是否被漫游了,答案是肯定的)

***图片停止解析***

后台完整的代码还是贴一下吧:

[mw_shl_code=csharp,true] /*
- Windows.Security.Credentials.PasswordVault 表示凭据的"凭据保险箱"
保险箱的内容特定于应用程序或服务。应用程序和服务无法访问与其他应用程序或服务关联的凭据。
- PasswordVault.Add(PasswordCredential credential); 添加一个凭证保险箱
- PasswordVault.Remove(PasswordCredential credential); 移除一个凭证保险箱
- PasswordVault.Retrieve(System.String resource, System.String userName); 读取一个凭证保险箱
- PasswordVault.FindAllByResource(System.String resource); 搜索与指定资源相匹配的凭据保险箱

- Windows.Security.Credentials.PasswordCredential 表示凭据保险箱对象
- PasswordCredential.PasswordCredential(System.String resource, System.String userName, System.String password);
创建一个凭据保险箱对象
- PasswordCredential.UserName 凭据保险箱对象存储的UserName
- PasswordCredential.Resource 凭据保险箱对象所属的资源名
- PasswordCredential.Password 凭据保险箱对象存储的密码
*/

public sealed partial class LoginPage : Page, INotifyPropertyChanged
{
public LoginPage()
{
InitializeComponent();
}

// ReSharper disable once UnusedParameter.Local
private async void QuickLogin(object sender, RoutedEventArgs args)
{
var credential = await GetCredentialFromLocker();
if (credential == null) return;
UserName = credential.UserName;
Psw = credential.Password;
await ShowLoding();
}

// ReSharper disable once UnusedParameter.Local
private async void Login(object sender, RoutedEventArgs args)
{
// 假设做用户账户和密码的验证工作(这里不考虑登录是否成功 一律登录成功)
await ShowLoding();
if (RoamCredential != true) return;
//添加用户凭证到 PasswordVault
var vault = new PasswordVault();
vault.Add(new PasswordCredential(
_resourceName, UserName, Psw));
}

/// <summary>
/// 显示登陆中Tip 并跳转到MainPage
/// </summary>
/// <returns>Null</returns>
private async Task ShowLoding()
{
var dialog = new ContentDialog
{
Title = "提示",
Content = "正在登录",
IsPrimaryButtonEnabled = true
};
#pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
dialog.ShowAsync();
#pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法

await Task.Delay(2000);
dialog.Content = "登录成功";
await Task.Delay(1000);
dialog.Hide();
Frame.Navigate(typeof(MainPage), _userName);
}

/// <summary>
/// 获取密码凭证
/// </summary>
/// <returns>PasswordCredential</returns>
private async Task<PasswordCredential> GetCredentialFromLocker()
{
PasswordCredential credential = null;

var vault = new PasswordVault();
var credentialList = vault.FindAllByResource(_resourceName);
if (credentialList.Count > 0)
{
if (credentialList.Count == 1)
{
credential = credentialList[0];
}
else
{
_defaultUserName = await GetDefaultUserNameUI(credentialList.Select(s => s.UserName));
if (!string.IsNullOrEmpty(_defaultUserName))
{
//读取凭证
credential = vault.Retrieve(_resourceName, _defaultUserName);
}
}
}

return credential;
}

/// <summary>
/// 获取一个用户名,如果存在多个用户凭证则选择一个
/// </summary>
/// <param name="userList">用户名集合</param>
/// <returns>UserName</returns>
private async Task<string> GetDefaultUserNameUI(IEnumerable<string> userList)
{
var userName = string.Empty;
var dialog = new ContentDialog
{
Title = "账户选择",
IsPrimaryButtonEnabled = true,
IsSecondaryButtonEnabled = true,
BorderThickness = new Thickness(0, 0, 1, 1),
PrimaryButtonText = "确定",
SecondaryButtonText = "取消"
};
var sp = new StackPanel();
var tb = new TextBlock { Text = "请选择您要登录哪个账户:" };
var cm = new ComboBox();
foreach (var user in userList)
{
// ReSharper disable once PossibleNullReferenceException
cm.Items.Add(new TextBlock
{
Text = user
});
}
cm.SelectedIndex = 0;
sp.Children.Add(tb);
sp.Children.Add(cm);
dialog.Content = sp;
dialog.PrimaryButtonClick += (s, a) =>
{
// ReSharper disable once PossibleNullReferenceException
userName = (cm.SelectedItem as TextBlock).Text;
};
dialog.SecondaryButtonClick += (s, a) => { dialog.Hide(); };
await dialog.ShowAsync();
return userName;
}

#region [Property]

private readonly string _resourceName = "Aran.CredentialLockerSample";

private string _defaultUserName;

private string _userName, _psw;
private bool? _roamCredential = false;

public string UserName
{
get { return _userName; }
set
{
_userName = value;
SendPropertyChanged();
}
}

public string Psw
{
get { return _psw; }
set
{
_psw = value;
SendPropertyChanged();
}
}

public bool? RoamCredential
{
get { return _roamCredential; }
set
{
_roamCredential = value;
SendPropertyChanged();
}
}

#endregion

#region [Property Notify]

public event PropertyChangedEventHandler PropertyChanged;

public void SendPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

#endregion
}[/mw_shl_code]


或许有人会说,如果在移动设备中手机丢失了,那么会不会被被人使用Winows快捷登陆登录我的账户?其实这个问题不用担心,因为手机丢失后别人不知道Pin码的情况下是无法使用你的手机的,更别提能打开App。

或许你又说我没有设置Pin怎么办?我想说的是,你连Pin都没设置,别说App保不住,你的短信、电话等其他App都有可能被别人登录,这种懒人,神都没法救你。

不过面对上面那种奇葩用户,我们还是有办法解决的,知道支付宝吗?尤其是iPhone版本的支付宝,在开启指纹解锁后,及时在机主自己使用支付宝的时候都需要验证下指纹来确保设备丢失也不会影响到App的非法使用。好消息是我们Windows通过也可以做的这么高大上,我们可以通过UWP提供的Windows.Security.Credentials.UI.UserConsentVerifier类来实现各种各样的用户身份验证,比如设备支持指纹验证、虹膜验证、面部验证的话,我们就可以使用这些验证方式。传统的设备即使没有配备高大上的验证设备,也可以通过该类使用传统的Windows账户密码验证、Pin码验证。

本文出自:53078485群大咖Aran,期待您的加入

标签: 保险箱 开发

敬告:
为防止不可控的内容风险,本站已关闭新用户注册,新贴的发表及评论;
你现在看到的内容只是互联网用户曾经发表的言论快照,仅用于老用户留存纪念,且仅与科技行业相关,全部内容不代表本站观点及立场;
本站重新开放前已针对包括用户隐私、版权保护、信息安全、国家政策在内的各种互联网法律法规要求,执行了隐患内容的自查、屏蔽和删除;
本站目前所属个人主体,未有任何盈利安排与计划,且与原WFUN.COM所属公司不存在任何关联关系;
如果本帖内容或者相关资源侵犯到您的合法权益,或者您认为存在问题,那么请您务必点此举报或投诉!
全部回复:
wind UID.62
2015-12-15 回复

这个有用,开发者可以好好学习下。

赧****月 UID.1258880
2015-12-15 回复

好好学习下

本站使用Golang构建,点击此处申请开源鄂ICP备18029942号-4联系站长投诉/举报