- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我发现了许多不同的 OAuth 解决方案,或者是一些库,或者是纯请求(https://github.com/googlesamples/oauth-apps-for-windows)。
但是,没有一个解决方案看起来像我真正需要的解决方案。目前,我的应用程序使用自己的数据库供用户使用 WCF 服务请求(使用用户名和密码)登录。但是,所有用户都使用 Google 帐户创建了他们的域电子邮件,因此我想添加另一个按钮“使用 Google 登录”,这将确保用户也可以使用他的 Google 电子邮件-密码对登录。我不需要返回的 token 以供进一步使用等。
在 WPF/C# 桌面应用程序中实现此功能的最简单方法是什么?
最佳答案
这是一个独立的、无第 3 方的 WPF 示例,它执行 Google 身份验证(它也可以很容易地转换为 winforms)。
如果您运行它,您将不会被记录,并且该应用程序会显示一个按钮。如果您单击该按钮,嵌入式网络浏览器控件将通过 Google 身份验证运行。一旦您通过身份验证,该应用程序将只显示 Google 返回的您的姓名。
请注意,它基于此处的官方 Google 样本:https://github.com/googlesamples/oauth-apps-for-windows但它使用嵌入式浏览器而不是生成外部浏览器(以及其他一些差异)。
XAML 代码:
<Window x:Class="GoogleAuth.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="750" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="btv" />
</Window.Resources>
<Grid>
<DockPanel Visibility="{Binding State.IsSigned, Converter={StaticResource btv}}">
<Label>You are signed as:</Label>
<Label Content="{Binding State.Token.Name}" />
</DockPanel>
<Grid Visibility="{Binding State.IsNotSigned, Converter={StaticResource btv}}">
<Grid.RowDefinitions>
<RowDefinition Height="23" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Click="Button_Click">Click to Sign In</Button>
<WebBrowser Grid.Row="1" x:Name="Wb" Height="Auto" />
</Grid>
</Grid>
</Window>
C#代码:
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace GoogleAuth
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
State = new OAuthState();
DataContext = this;
}
public OAuthState State { get; }
private void Button_Click(object sender, RoutedEventArgs e)
{
var thread = new Thread(HandleRedirect);
thread.Start();
}
private async void HandleRedirect()
{
State.Token = null;
// for example, let's pretend I want also want to have access to WebAlbums
var scopes = new string[] { "https://picasaweb.google.com/data/" };
var request = OAuthRequest.BuildLoopbackRequest(scopes);
var listener = new HttpListener();
listener.Prefixes.Add(request.RedirectUri);
listener.Start();
// note: add a reference to System.Windows.Presentation and a 'using System.Windows.Threading' for this to compile
await Dispatcher.BeginInvoke(() =>
{
Wb.Navigate(request.AuthorizationRequestUri);
});
// here, we'll wait for redirection from our hosted webbrowser
var context = await listener.GetContextAsync();
// browser has navigated to our small http servern answer anything here
string html = string.Format("<html><body></body></html>");
var buffer = Encoding.UTF8.GetBytes(html);
context.Response.ContentLength64 = buffer.Length;
var stream = context.Response.OutputStream;
var responseTask = stream.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
stream.Close();
listener.Stop();
});
string error = context.Request.QueryString["error"];
if (error != null)
return;
string state = context.Request.QueryString["state"];
if (state != request.State)
return;
string code = context.Request.QueryString["code"];
State.Token = request.ExchangeCodeForAccessToken(code);
}
}
// state model
public class OAuthState : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private OAuthToken _token;
public OAuthToken Token
{
get => _token;
set
{
if (_token == value)
return;
_token = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Token)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSigned)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsNotSigned)));
}
}
public bool IsSigned => Token != null && Token.ExpirationDate > DateTime.Now;
public bool IsNotSigned => !IsSigned;
}
// This is a sample. Fille information (email, etc.) can depend on scopes
[DataContract]
public class OAuthToken
{
[DataMember(Name = "access_token")]
public string AccessToken { get; set; }
[DataMember(Name = "token_type")]
public string TokenType { get; set; }
[DataMember(Name = "expires_in")]
public int ExpiresIn { get; set; }
[DataMember(Name = "refresh_token")]
public string RefreshToken { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string Picture { get; set; }
[DataMember]
public string Locale { get; set; }
[DataMember]
public string FamilyName { get; set; }
[DataMember]
public string GivenName { get; set; }
[DataMember]
public string Id { get; set; }
[DataMember]
public string Profile { get; set; }
[DataMember]
public string[] Scopes { get; set; }
// not from google's response, but we store this
public DateTime ExpirationDate { get; set; }
}
// largely inspired from
// https://github.com/googlesamples/oauth-apps-for-windows
public sealed class OAuthRequest
{
// TODO: this is a sample, please change these two, use your own!
private const string ClientId = "581786658708-elflankerquo1a6vsckabbhn25hclla0.apps.googleusercontent.com";
private const string ClientSecret = "3f6NggMbPtrmIBpgx-MK2xXK";
private const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
private const string TokenEndpoint = "https://www.googleapis.com/oauth2/v4/token";
private const string UserInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo";
private OAuthRequest()
{
}
public string AuthorizationRequestUri { get; private set; }
public string State { get; private set; }
public string RedirectUri { get; private set; }
public string CodeVerifier { get; private set; }
public string[] Scopes { get; private set; }
// https://developers.google.com/identity/protocols/OAuth2InstalledApp
public static OAuthRequest BuildLoopbackRequest(params string[] scopes)
{
var request = new OAuthRequest
{
CodeVerifier = RandomDataBase64Url(32),
Scopes = scopes
};
string codeChallenge = Base64UrlEncodeNoPadding(Sha256(request.CodeVerifier));
const string codeChallengeMethod = "S256";
string scope = BuildScopes(scopes);
request.RedirectUri = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
request.State = RandomDataBase64Url(32);
request.AuthorizationRequestUri = string.Format("{0}?response_type=code&scope=openid%20profile{6}&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
AuthorizationEndpoint,
Uri.EscapeDataString(request.RedirectUri),
ClientId,
request.State,
codeChallenge,
codeChallengeMethod,
scope);
return request;
}
// https://developers.google.com/identity/protocols/OAuth2InstalledApp Step 5: Exchange authorization code for refresh and access tokens
public OAuthToken ExchangeCodeForAccessToken(string code)
{
if (code == null)
throw new ArgumentNullException(nameof(code));
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code",
code,
Uri.EscapeDataString(RedirectUri),
ClientId,
CodeVerifier,
ClientSecret
);
return TokenRequest(tokenRequestBody, Scopes);
}
// this is not used in this sample, but can be used to refresh a token from an old one
// https://developers.google.com/identity/protocols/OAuth2InstalledApp Refreshing an access token
public OAuthToken Refresh(OAuthToken oldToken)
{
if (oldToken == null)
throw new ArgumentNullException(nameof(oldToken));
string tokenRequestBody = string.Format("refresh_token={0}&client_id={1}&client_secret={2}&grant_type=refresh_token",
oldToken.RefreshToken,
ClientId,
ClientSecret
);
return TokenRequest(tokenRequestBody, oldToken.Scopes);
}
private static T Deserialize<T>(string json)
{
if (string.IsNullOrWhiteSpace(json))
return default(T);
return Deserialize<T>(Encoding.UTF8.GetBytes(json));
}
private static T Deserialize<T>(byte[] json)
{
if (json == null || json.Length == 0)
return default(T);
using (var ms = new MemoryStream(json))
{
return Deserialize<T>(ms);
}
}
private static T Deserialize<T>(Stream json)
{
if (json == null)
return default(T);
var ser = CreateSerializer(typeof(T));
return (T)ser.ReadObject(json);
}
private static DataContractJsonSerializer CreateSerializer(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
var settings = new DataContractJsonSerializerSettings
{
DateTimeFormat = new DateTimeFormat("yyyy-MM-dd'T'HH:mm:ss.fffK")
};
return new DataContractJsonSerializer(type, settings);
}
// https://stackoverflow.com/questions/223063/how-can-i-create-an-httplistener-class-on-a-random-port-in-c/
private static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
private static string RandomDataBase64Url(int length)
{
using (var rng = new RNGCryptoServiceProvider())
{
var bytes = new byte[length];
rng.GetBytes(bytes);
return Base64UrlEncodeNoPadding(bytes);
}
}
private static byte[] Sha256(string text)
{
using (var sha256 = new SHA256Managed())
{
return sha256.ComputeHash(Encoding.ASCII.GetBytes(text));
}
}
private static string Base64UrlEncodeNoPadding(byte[] buffer)
{
string b64 = Convert.ToBase64String(buffer);
// converts base64 to base64url.
b64 = b64.Replace('+', '-');
b64 = b64.Replace('/', '_');
// strips padding.
b64 = b64.Replace("=", "");
return b64;
}
private static OAuthToken TokenRequest(string tokenRequestBody, string[] scopes)
{
var request = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
byte[] bytes = Encoding.ASCII.GetBytes(tokenRequestBody);
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
var response = request.GetResponse();
using (var responseStream = response.GetResponseStream())
{
var token = Deserialize<OAuthToken>(responseStream);
token.ExpirationDate = DateTime.Now + new TimeSpan(0, 0, token.ExpiresIn);
var user = GetUserInfo(token.AccessToken);
token.Name = user.Name;
token.Picture = user.Picture;
token.Email = user.Email;
token.Locale = user.Locale;
token.FamilyName = user.FamilyName;
token.GivenName = user.GivenName;
token.Id = user.Id;
token.Profile = user.Profile;
token.Scopes = scopes;
return token;
}
}
private static UserInfo GetUserInfo(string accessToken)
{
var request = (HttpWebRequest)WebRequest.Create(UserInfoEndpoint);
request.Method = "GET";
request.Headers.Add(string.Format("Authorization: Bearer {0}", accessToken));
var response = request.GetResponse();
using (var stream = response.GetResponseStream())
{
return Deserialize<UserInfo>(stream);
}
}
private static string BuildScopes(string[] scopes)
{
string scope = null;
if (scopes != null)
{
foreach (var sc in scopes)
{
scope += "%20" + Uri.EscapeDataString(sc);
}
}
return scope;
}
// https://developers.google.com/+/web/api/rest/openidconnect/getOpenIdConnect
[DataContract]
private class UserInfo
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "kind")]
public string Kind { get; set; }
[DataMember(Name = "email")]
public string Email { get; set; }
[DataMember(Name = "picture")]
public string Picture { get; set; }
[DataMember(Name = "locale")]
public string Locale { get; set; }
[DataMember(Name = "family_name")]
public string FamilyName { get; set; }
[DataMember(Name = "given_name")]
public string GivenName { get; set; }
[DataMember(Name = "sub")]
public string Id { get; set; }
[DataMember(Name = "profile")]
public string Profile { get; set; }
[DataMember(Name = "gender")]
public string Gender { get; set; }
}
}
}
此代码使用嵌入式 Internet Explorer 控件,但由于 Google 不支持旧的 Internet Explorer 版本,您可能还需要添加一些代码来使用 IE 的兼容性功能,例如此处所述:https://stackoverflow.com/a/28626667/403671
您可以将该代码放在 App.xaml.cs 中,如下所示:
public partial class App : Application
{
public App()
{
// use code from here: https://stackoverflow.com/a/28626667/403671
SetWebBrowserFeatures();
}
}
请注意,网络浏览器将显示的内容完全取决于 Google,并且可能会因 cookie、语言等环境的不同而有很大差异。
关于c# - 使用 Google 的 WPF 应用程序身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48321034/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!