我有一个ASP.Net核心1.1网站,我想将电源BI报告嵌入网站。
Azure托管数据文档:https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-embed-sample-app-owns-data/
使用在https://github.com/Microsoft/PowerBI-Developer-Samples找到的 App拥有数据示例我使用该示例创建了一个有效的嵌入式报表解决方案。但是,示例项目正在.Net Framework 4.5.2上运行,当尝试将解决方案迁移到我的.Net核心应用程序时,我已迁移了代码但是.Net Core中的ActiveDirectorylibrary(Microsoft.IdentityModel.Clients.ActiveDirectorylibrary)没有包含UserPasswordCredential的方法
var credential = new UserPasswordCredential(Username, Password);
我发现在线推荐用于ASP.Net核心的解决方案是使用标签帮助程序但是现在Power BI嵌入式和Power BI服务已经与Power BI Premium的新功能融合了我不认为这个解决方案是可行的由于App Hosted数据的令牌认证依赖性。
完整报告控制器方法:
public class ReportController : Controller
{
private static readonly string Username = ConfigurationManager.AppSettings["pbiUsername"];
private static readonly string Password = ConfigurationManager.AppSettings["pbiPassword"];
private static readonly string AuthorityUrl = ConfigurationManager.AppSettings["authorityUrl"];
private static readonly string ResourceUrl = ConfigurationManager.AppSettings["resourceUrl"];
private static readonly string ClientId = ConfigurationManager.AppSettings["clientId"];
private static readonly string ApiUrl = ConfigurationManager.AppSettings["apiUrl"];
private static readonly string GroupId = ConfigurationManager.AppSettings["groupId"];
public async Task<ActionResult> EmbedReport()
{
// Create a user password cradentials.
var credential = new UserPasswordCredential(Username, Password);
// Authenticate using created credentials
var authenticationContext = new AuthenticationContext(AuthorityUrl);
var authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceUrl, ClientId, credential);
if (authenticationResult == null)
{
return View(new EmbedConfig()
{
ErrorMessage = "Authentication Failed."
});
}
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
// Create a Power BI Client object. It will be used to call Power BI APIs.
using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
{
// Get a list of reports.
var reports = await client.Reports.GetReportsInGroupAsync(GroupId);
// Get the first report in the group.
var report = reports.Value.FirstOrDefault();
if (report == null)
{
return View(new EmbedConfig()
{
ErrorMessage = "Group has no reports."
});
}
// Generate Embed Token.
var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(GroupId, report.Id, generateTokenRequestParameters);
if (tokenResponse == null)
{
return View(new EmbedConfig()
{
ErrorMessage = "Failed to generate embed token."
});
}
// Generate Embed Configuration.
var embedConfig = new EmbedConfig()
{
EmbedToken = tokenResponse,
EmbedUrl = report.EmbedUrl,
Id = report.Id
};
return View(embedConfig);
}
}
}
我尝试过:
在.Net Core项目中添加对.Net Framework 4.6.1的引用以公开.Net Framework并允许使用.Net等效的IdentityModel.Clients.ActiveDirectory,但似乎没有帮助。
如何修复库问题并嵌入.Net核心?
根据@Fei Xue提供的答案,我编写了一个HTTP助手类,它向API执行了一个帖子。基于返回的JSON,我创建了一个对象并使用了现在可用的身份验证令牌
助手班级:
#region Settings
public static string BaseUrl
{
get
{
return "https://login.microsoftonline.com/common/oauth2/token";
}
}
#endregion
public static async Task<HttpResponseMessage> MakeAsyncRequest(string url, Dictionary<string, string> content)
{
var httpClient = new HttpClient
{
Timeout = new TimeSpan(0, 5, 0),
BaseAddress = new Uri(url)
};
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type: application/x-www-form-urlencoded", "application/json");
if (content == null)
{
content = new Dictionary<string, string>();
}
var encodedContent = new FormUrlEncodedContent(content);
var result = await httpClient.PostAsync(httpClient.BaseAddress, encodedContent);
return result;
}
对象:
public class AAD
{
public string token_type { get; set; }
public string scope { get; set; }
public string expires_in { get; set; }
public string ext_expires_in { get; set; }
public string expires_on { get; set; }
public string not_before { get; set; }
public string resource { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
}
来自控制器的呼叫:
var url = APIHelper.BaseUrl;
var content = new Dictionary<string, string>();
content["grant_type"] = "password";
content["resource"] = "https://analysis.windows.net/powerbi/api";
content["username"] = "<username>";
content["password"] = "<password>";
content["client_id"] = "<clientid>";
var response = await APIHelper.MakeAsyncRequest(url, content);
var result = response.Content.ReadAsStringAsync().Result;
var AAD = JsonConvert.DeserializeObject<AAD>(result);
答案 0 :(得分:10)
Active Directory身份验证库的.Net核心不支持资源所有者密码凭据流。
作为一种变通方法,您可以直接撰写HTTP请求。以下是供您参考的示例:
POST https://login.microsoftonline.com/{tenant}/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=password
&resource={resource}
&username={username}
&password={password}
&client_id={clientid}
&client_secret={client_secret}//if it is confidential app
答案 1 :(得分:3)
不确定在调整PowerBI嵌入式App-Owns-Data示例代码到.Net核心方面会有多少人遇到这个问题,但我能够通过融合Chad&#成功地完成并运行39;带有this post from the powerBI community.
代码的示例类密钥(至少对我来说)是按照PowerBI帖子中的建议在原始样本中交换此代码:
var credential = new UserPasswordCredential(Username, Password);
// Authenticate using created credentials
var authenticationContext = new AuthenticationContext(AuthorityUrl);
var authenticationResult = await
authenticationContext.AcquireTokenAsync(ResourceUrl, ClientId, credential);
这个位(AAD是来自Chad&#39的样本的返回对象):
var authenticationResult = AAD;
还提供了一个实际的嵌入式令牌,而无需调用UserPasswordCredential。
下面的完整控制器代码,希望它可以节省其他人几个小时的挖掘时间。谢谢Chad和Fei Xue的样品!欢呼声。
public class DashboardController : Controller
{
private static readonly string Username = "yourUN";
private static readonly string Password = "yourPW";
private static readonly string AuthorityUrl = "https://login.windows.net/common/oauth2/authorize/";
private static readonly string ResourceUrl = "https://analysis.windows.net/powerbi/api";
private static readonly string ClientId = "yourClientId";
private static readonly string ApiUrl = "https://api.powerbi.com";
private static readonly string GroupId = "yourGroupId";
private static readonly string ReportId = "yourReportId";
public class APIHelper
{
#region Settings
public static string BaseUrl
{
get
{
return "https://login.microsoftonline.com/common/oauth2/token";
}
}
#endregion
public static async Task<HttpResponseMessage> MakeAsyncRequest(string url, Dictionary<string, string> content)
{
var httpClient = new HttpClient
{
Timeout = new TimeSpan(0, 5, 0),
BaseAddress = new Uri(url)
};
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type: application/x-www-form-urlencoded", "application/json");
if (content == null)
{
content = new Dictionary<string, string>();
}
var encodedContent = new FormUrlEncodedContent(content);
var result = await httpClient.PostAsync(httpClient.BaseAddress, encodedContent);
return result;
}
}
public class AAD
{
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("experies_in")]
public int ExpiresIn { get; set; }
[JsonProperty("ext_experies_in")]
public int ExtExpiresIn { get; set; }
[JsonProperty("experies_on")]
public int ExpiresOn { get; set; }
[JsonProperty("not_before")]
public int NotBefore { get; set; }
[JsonProperty("resource")]
public Uri Resource { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
public async Task<ActionResult> Index(string username, string roles)
{
var url = APIHelper.BaseUrl;
var content = new Dictionary<string, string>();
content["grant_type"] = "password";
content["resource"] = "https://analysis.windows.net/powerbi/api";
content["username"] = Username;
content["password"] = Password;
content["client_id"] = ClientId;
var response = await APIHelper.MakeAsyncRequest(url, content);
var tokenresult = response.Content.ReadAsStringAsync().Result;
var AAD = JsonConvert.DeserializeObject<AAD>(tokenresult);
var result = new EmbedConfig();
try
{
result = new EmbedConfig { Username = username, Roles = roles };
var error = GetWebConfigErrors();
if (error != null)
{
result.ErrorMessage = error;
return View(result);
}
// Create a user password cradentials.
var authenticationResult = AAD;
if (authenticationResult == null)
{
result.ErrorMessage = "Authentication Failed.";
return View(result);
}
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
// Create a Power BI Client object. It will be used to call Power BI APIs.
using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
{
// Get a list of reports.
var reports = await client.Reports.GetReportsInGroupAsync(GroupId);
Report report;
if (string.IsNullOrEmpty(ReportId))
{
// Get the first report in the group.
report = reports.Value.FirstOrDefault();
}
else
{
report = reports.Value.FirstOrDefault(r => r.Id == "3a447f03-ad31-4e78-a3f1-2a6c008fcd8e");
}
if (report == null)
{
result.ErrorMessage = "Group has no reports.";
return View(result);
}
var datasets = await client.Datasets.GetDatasetByIdInGroupAsync(GroupId, report.DatasetId);
result.IsEffectiveIdentityRequired = datasets.IsEffectiveIdentityRequired;
result.IsEffectiveIdentityRolesRequired = datasets.IsEffectiveIdentityRolesRequired;
GenerateTokenRequest generateTokenRequestParameters;
// This is how you create embed token with effective identities
generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(GroupId, report.Id, generateTokenRequestParameters);
if (tokenResponse == null)
{
result.ErrorMessage = "Failed to generate embed token.";
return View(result);
}
// Generate Embed Configuration.
result.EmbedToken = tokenResponse;
result.EmbedUrl = report.EmbedUrl;
result.Id = report.Id;
return View(result);
}
}
catch (HttpOperationException exc)
{
result.ErrorMessage = string.Format("Status: {0} for ID: ({1})\r\nResponse: {2}\r\nRequestId: {3}", exc.Response.StatusCode, (int)exc.Response.StatusCode, exc.Response.Content, exc.Response.Headers["RequestId"].FirstOrDefault());
}
catch (Exception exc)
{
result.ErrorMessage = exc.ToString();
}
return View(result);
}
}
答案 2 :(得分:2)
我尝试过这里的解决方案,而其他各种在线获取嵌入式令牌的解决方案是aspnetcore。我一直在获取无效的用户名/密码,但是我发现,如果使用Microsoft的.NET Framework示例,一切都将正常工作-
所以,我知道我的凭据是正确的。最后,我在本地运行.NET Framework解决方案,并观看了Fiddler的传出流量。我能够看到有额外的交流发生,因为我是在联合广告租户(ADFS)上。我从那里向后工作,以提出以下(有效)代码。希望它可以帮助某人-
public class PowerBIAuthenticator
{
private static OAuthResult _cachedResult = null;
private PowerBIOptions _powerBIOptions;
private ILogger<PowerBIAuthenticator> _logger;
public PowerBIAuthenticator()
{
_powerBIOptions = new PowerBIOptions();
_powerBIOptions.ResourceUrl = "https://analysis.windows.net/powerbi/api";
_powerBIOptions.AuthorityUrl = "https://login.windows.net/common/oauth2/token";
_powerBIOptions.ApiUrl = "https://api.powerbi.com/";
_powerBIOptions.ClientId = "azure-ad-client-id-here";
_powerBIOptions.Username = "master-account-username-here";
_powerBIOptions.Password = "master-account-password-here";
}
public async Task<OAuthResult> AuthenticateAsync(PowerBISecureOptions secureOptions)
{
if (_cachedResult != null)
{
var expireDateTime = DateTimeOffset.FromUnixTimeSeconds(_cachedResult.ExpiresOn);
var currentDateTime = DateTimeOffset.Now.UtcDateTime;
if (currentDateTime < expireDateTime)
{
return _cachedResult;
}
}
OAuthResult authToken = await this.GetAuthToken();
_cachedResult = authToken;
return authToken;
}
private async Task<OAuthResult> GetAuthToken()
{
string commonRequestGuid = Guid.NewGuid().ToString();
OAuthResult oauthResult = null;
UserRealm userRealm = await this.GetUserRealm(commonRequestGuid);
if (userRealm.account_type.Equals("Federated"))
{
XmlDocument metadata
= await this.GetFederationMetadata(commonRequestGuid, userRealm.federation_metadata_url);
string trustBinding = GetFederatedUserTrustBinding(metadata);
XmlDocument trustDocument
= await this.GetFederatedUserTrust(commonRequestGuid, trustBinding);
var userAssertionNodes = trustDocument.GetElementsByTagName("saml:Assertion");
var userAssertionNode = userAssertionNodes[0].OuterXml;
using (var client = new HttpClient())
{
string tokenUri = "https://login.windows.net/common/oauth2/token";
var ua = new UserAssertion(
userAssertionNode,
"urn:ietf:params:oauth:grant-type:saml1_1-bearer",
Uri.EscapeDataString(_powerBIOptions.Username));
UTF8Encoding encoding = new UTF8Encoding();
Byte[] byteSource = encoding.GetBytes(ua.Assertion);
string base64ua = Convert.ToBase64String(byteSource);
var tokenForm = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("resource", _powerBIOptions.ResourceUrl),
new KeyValuePair<string, string>("client_id", _powerBIOptions.ClientId),
new KeyValuePair<string, string>("grant_type", "urn:ietf:params:oauth:grant-type:saml1_1-bearer"),
new KeyValuePair<string, string>("assertion", base64ua),
new KeyValuePair<string, string>("scope", "openid"),
});
var tokenResult = await client.PostAsync(tokenUri, tokenForm);
var tokenContent = await tokenResult.Content.ReadAsStringAsync();
oauthResult = JsonConvert.DeserializeObject<OAuthResult>(tokenContent);
}
}
else
{
using (var client = new HttpClient())
{
var result = await client.PostAsync(_powerBIOptions.AuthorityUrl, new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("resource", _powerBIOptions.ResourceUrl),
new KeyValuePair<string, string>("client_id", _powerBIOptions.ClientId),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", _powerBIOptions.Username),
new KeyValuePair<string, string>("password", _powerBIOptions.Password),
new KeyValuePair<string, string>("scope", "openid"),
}));
var tokenContent = await result.Content.ReadAsStringAsync();
oauthResult = JsonConvert.DeserializeObject<OAuthResult>(tokenContent);
}
}
return oauthResult;
}
private async Task<UserRealm> GetUserRealm(string commonRequestGuid)
{
UserRealm userRealm = new UserRealm();
using (var client = new HttpClient())
{
string userRealmUri = $"https://login.windows.net/common/UserRealm/{_powerBIOptions.Username}?api-version=1.0";
HttpRequestMessage realmRequest = new HttpRequestMessage(HttpMethod.Get, userRealmUri);
realmRequest.Headers.Add("Accept", "application/json");
realmRequest.Headers.Add("return-client-request-id", "true");
realmRequest.Headers.Add("client-request-id", commonRequestGuid);
HttpResponseMessage realmResponse = client.SendAsync(realmRequest).Result;
string realmString = await realmResponse.Content.ReadAsStringAsync();
userRealm = JsonConvert.DeserializeObject<UserRealm>(realmString);
}
return userRealm;
}
private async Task<XmlDocument> GetFederationMetadata(string commonRequestGuid, string adfsMetadataUri)
{
string metadataString = string.Empty;
using (var client = new HttpClient())
{
HttpRequestMessage metadataRequest = new HttpRequestMessage(HttpMethod.Get, adfsMetadataUri);
metadataRequest.Headers.Add("Accept", "application/json");
metadataRequest.Headers.Add("return-client-request-id", "true");
metadataRequest.Headers.Add("client-request-id", commonRequestGuid);
HttpResponseMessage metadataResponse = client.SendAsync(metadataRequest).Result;
metadataString = await metadataResponse.Content.ReadAsStringAsync();
}
XmlDocument metadataDoc = new XmlDocument();
metadataDoc.LoadXml(metadataString);
return metadataDoc;
}
private async Task<XmlDocument> GetFederatedUserTrust(string commonRequestGuid, string trustBindingUri)
{
string trustString = null;
using (var client = new HttpClient())
{
HttpRequestMessage trustRequest = new HttpRequestMessage(HttpMethod.Post, trustBindingUri);
trustRequest.Headers.Add("Accept", "application/json");
trustRequest.Headers.Add("return-client-request-id", "true");
trustRequest.Headers.Add("client-request-id", commonRequestGuid);
trustRequest.Headers.Add("SOAPAction", "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue");
DateTime now = DateTime.UtcNow;
DateTime then = now.AddMinutes(10);
string trustBody = __federatedUserTrustBody.
Replace("{messageGuid}", Guid.NewGuid().ToString()).
Replace("{adfsUserEndpoint}", trustBindingUri).
Replace("{SecurityCreated}", now.ToString("o")).
Replace("{SecurityExpires}", then.ToString("o")).
Replace("{tokenGuid}", Guid.NewGuid().ToString()).
Replace("{username}", _powerBIOptions.Username).
Replace("{password}", _powerBIOptions.Password);
trustRequest.Content = new StringContent(trustBody, Encoding.UTF8, "application/soap+xml");
HttpResponseMessage userTrustResponse = client.SendAsync(trustRequest).Result;
trustString = await userTrustResponse.Content.ReadAsStringAsync();
}
XmlDocument trustDocument = new XmlDocument();
trustDocument.LoadXml(trustString);
return trustDocument;
}
private string GetFederatedUserTrustBinding(XmlDocument metadata)
{
XmlNodeList services = metadata.GetElementsByTagName("wsdl:service");
List<XmlNode> ports = new List<XmlNode>();
foreach (XmlNode node in services[0])
{
if (node.Name.Equals("wsdl:port"))
ports.Add(node);
}
XmlNode trustPort = ports.FirstOrDefault(p => p.Attributes["name"] != null
&& p.Attributes["name"].Value.Equals("UserNameWSTrustBinding_IWSTrust13Async"));
XmlNode trustAddress = null;
foreach (XmlNode node in trustPort.ChildNodes)
{
if (node.Name.Equals("soap12:address"))
{
trustAddress = node;
break;
}
}
return trustAddress.Attributes["location"].Value;
}
public class UserRealm
{
public string ver { get; set; }
public string account_type { get; set; }
public string domain_name { get; set; }
public string federation_protocol { get; set; }
public string federation_metadata_url { get; set; }
public string federation_active_auth_url { get; set; }
public string cloud_instance_name { get; set; }
public string cloud_audience_urn { get; set; }
}
public class PowerBIOptions
{
[JsonProperty("resourceUrl")]
public string ResourceUrl { get; set; }
[JsonProperty("authorityUrl")]
public string AuthorityUrl { get; set; }
[JsonProperty("apiUrl")]
public string ApiUrl { get; set; }
[JsonProperty("clientId")]
public string ClientId { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("password")]
public string Password { get; set; }
}
public class OAuthResult
{
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("ext_expires_in")]
public int ExtExpiresIn { get; set; }
[JsonProperty("expires_on")]
public int ExpiresOn { get; set; }
[JsonProperty("not_before")]
public int NotBefore { get; set; }
[JsonProperty("resource")]
public Uri Resource { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
private string __federatedUserTrustBody =
@"<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
<s:Header>
<a:Action s:mustUnderstand='1'>http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:messageID>urn:uuid:{messageGuid}</a:messageID>
<a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>
<a:To s:mustUnderstand='1'>{adfsUserEndpoint}</a:To>
<o:Security s:mustUnderstand='1' xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'><u:Timestamp u:Id='_0'><u:Created>{SecurityCreated}</u:Created><u:Expires>{SecurityExpires}</u:Expires></u:Timestamp><o:UsernameToken u:Id='uuid-{tokenGuid}'><o:Username>{username}</o:Username><o:Password>{password}</o:Password></o:UsernameToken></o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust='http://docs.oasis-open.org/ws-sx/ws-trust/200512'>
<wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
<a:EndpointReference>
<a:Address>urn:federation:MicrosoftOnline</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>";
}
答案 3 :(得分:0)
我已经通过以下代码片段解决了这个问题。您只需要替换您的PowerBI帐户的用户名和密码,client_id,client_secret,authEndPoint并创建AzureAdTokenResponseDto类。
public static async Task<string> GetToken()
{
HttpClient client = new HttpClient();
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", "<user name>"),
new KeyValuePair<string, string>("password", "<password>"),
new KeyValuePair<string, string>("client_id", "<client_id>"),
new KeyValuePair<string, string>("scope", "openid"),
new KeyValuePair<string, string>("client_secret", "<client_secret>"),
new KeyValuePair<string, string>("resource", "https://analysis.windows.net/powerbi/api")
});
HttpResponseMessage res = client.PostAsync("authEndPoint", content).Result;
string json = await res.Content.ReadAsStringAsync();
AzureAdTokenResponseDto tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponseDto>(json);
return tokenRes.AccessToken;
}
现在,您需要按如下所示创建AzureAdTokenResponseDto类。
public class AzureAdTokenResponseDto
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
}
请确保安装Newtonsoft.Json Nuget软件包。它对我来说与ASP.NET Core完美结合。