我有一些电视剧正在对ServiceStack REST服务进行调用,我使用开箱即用的IdentityServer STS保护了该服务。
我正在针对REST端点进行AJAX调用,而且我不确定如何设置登录过程以获取安全令牌。 REST端点与进行呼叫的网站位于不同的域中。到目前为止,我发现的信息似乎都围绕着客户端调用安全资源的过程获得302重定向到身份服务器登录页面,然后成功验证后获得302重定向到域或者回复取决于配置。我已正确地将所有这些联系起来,如果我只是浏览REST服务,它就会很好用。但是对于我的网络应用程序,AJAX和302s并不是最好的朋友,所以理想情况下我认为我想拥有的是来自同一ServiceStack网站的REST端点,它接受用户名和密码并返回一个安全令牌,没有任何重定向的复杂性(我将处理我的Web应用程序本身中的401重定向,当我关闭web.config中的passiveRedirectEnabled时,我将获得该重定向)。有关如何使用IdentityServer实现此目的的任何想法?
干杯, 克林特。
答案 0 :(得分:5)
使用完整的REST端点完成答案:
在ServiceStack网络应用中:
使用以下内容路由到AppHost.cs中的登录端点:
public override void Configure(Container container)
{
Routes.Add<Logon>("/logon", "POST");
}
然后有一个简单的用户名/密码Request DTO
public class Logon
{
public string UserName { get; set; }
public string Password { get; set; }
}
响应DTO
响应DTO只需要处理POST - 是的,您可以添加URL /密码 作为GET请求的URL中的参数,但这听起来不像是推荐的。 实际上,您通常可能会将此信息放在HTTP请求的Authorization标头中 但这会使你在ServiceStack中的工作变得更难。
public class LogonService : Service
{
public object Post(Logon request)
{
var securityToken = GetSaml2SecurityToken(request.UserName, request.Password, "https://myserver/identityserverwebapp/issue/wstrust/mixed/username", "http://myserver/servicestackwebapp/");
return SerializeRequestSecurityTokenResponse(securityToken);
}
private RequestSecurityTokenResponse GetSaml2SecurityToken(string username, string password, string endpointAddress, string realm)
{
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(endpointAddress))
{
TrustVersion = TrustVersion.WSTrust13
};
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
var channel = (WSTrustChannel)factory.CreateChannel();
RequestSecurityTokenResponse requestSecurityTokenResponse;
channel.Issue(new RequestSecurityToken
{
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
AppliesTo = new EndpointReference(realm),
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
}, out requestSecurityTokenResponse);
return requestSecurityTokenResponse;
}
private string SerializeRequestSecurityTokenResponse(RequestSecurityTokenResponse requestSecurityTokenResponse)
{
var serializer = new WSTrust13ResponseSerializer();
var context = new WSTrustSerializationContext(FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlerCollectionManager);
var stringBuilder = new StringBuilder(128);
using (var writer = XmlWriter.Create(new StringWriter(stringBuilder), new XmlWriterSettings { OmitXmlDeclaration = true}))
{
serializer.WriteXml(requestSecurityTokenResponse, writer, context);
writer.Flush();
return stringBuilder.ToString();
}
}
}
ServiceStack Web应用程序Web.config看起来应该与此非常相似:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
<location path="FederationMetadata">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<!-- to allow the logon route without requiring authentication first. -->
<location path="logon">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<system.web>
<httpHandlers>
<add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" />
</httpHandlers>
<compilation debug="true" />
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
<httpRuntime targetFramework="4.5" requestValidationMode="4.5" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add path="*" name="ServiceStack.Factory" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
</handlers>
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="http://myserver/servicestackwebapp/" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add thumbprint="B6E05E14243FB7D76D5B660532520FB94679AA01" name="http://mycertificatefriendlyname" />
</trustedIssuers>
</issuerNameRegistry>
<certificateValidation certificateValidationMode="None" />
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapContext="true" />
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
<wsFederation passiveRedirectEnabled="false" issuer="https://myserver/identityserverwebapp/issue/wsfed" realm="http://myserver/servicestackwebapp/" requireHttps="false" />
</federationConfiguration>
</system.identityModel.services>
</configuration>
最后,要使用REST端点验证一个简单的Javascript客户端应用程序,将用户名和密码POST到servicestackwebapp的登录端点,然后当您收到响应时,将其发回到领域 - 这样设置您当前会话的FedAuth cookie,因此您不必再考虑令牌管理客户端了。
$.ajax({
type: "POST",
url: "/servicestackwebapp/logon",
dataType: "text",
data: { UserName: "myuser", Password: "mypassword" },
success: function (data) {
$.ajax({
type: "POST",
url: "/servicestackwebapp/",
data: "wa=wsignin1.0&wresult=" + encodeURIComponent(data)
});
}
});
此外,我应该注意,上面的所有HTTP端点都应该通过HTTPS - 不要像我在我的示例中所做的那样愚蠢,并通过HTTP发送明文声明。
在我实施解决方案后,我发现了这一点:http://msdn.microsoft.com/en-us/library/hh446531.aspx ...我希望我之前找到它,但是知道我已经实现了类似于Microsoft示例的东西 - 我们在将它们转换为简单Web令牌时分歧 - 我将其保留为SAML令牌,并且令人放心。将(序列化的)传递给客户端。
答案 1 :(得分:2)
到目前为止我的解决方案:
我在REST服务上公开了一个端点,该端点默认对IdentityServer提供的WS-Trust端点进行调用。在.NET 4.5中,您需要引用Thinktecture.IdentityModel,因为System.IdentityModel中没有UserNameWSTrustBinding,请参阅:What's the .NET 4.5 equivalent to UserNameWSTrustBinding?
从端点获取SAML2安全令牌的代码如下所示:
private SecurityToken GetSamlSecurityToken(string username, string password, string endpointAddress, string realm)
{
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(endpointAddress))
{
TrustVersion = TrustVersion.WSTrust13
};
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
var channel = factory.CreateChannel();
var securityToken = channel.Issue(new RequestSecurityToken
{
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
AppliesTo = new EndpointReference(realm),
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
});
return securityToken;
}
这将根据用户名和密码进行身份验证,而endpointAddress参数将类似于:
https://myserver/identityserverapp/issue/wstrust/mixed/username
然后我按如下方式序列化安全令牌:
private string SerializeSecurityToken(SecurityToken securityToken)
{
var serializer = new WSSecurityTokenSerializer();
var stringBuilder = new StringBuilder();
using (var writer = XmlWriter.Create(new StringWriter(stringBuilder)))
{
serializer.WriteToken(writer, securityToken);
return stringBuilder.ToString();
}
}
我认为唯一剩下的就是建立FedAuth cookie,我认为这是在安全令牌的第一篇文章中设置到安全的网络应用程序。
请权衡任何改进或建议。谢谢!