A Simple Naïve ™ Guide to ASP.NET Core Linux Deployment

Step 1: Prepare .NET Core environment

This step is absolutely easy on Ubuntu/Debian and other officially supported distributions. If you are using Archlinux, go to AUR for .NET Core runtime.

Step 2: Prepare files.

In local Visual Studio or other development environment, publish project to a folder. (dotnet build -c Release)

Copy files.

Step 3: Setting services

I wrote a simple systemd service for my application:

[Unit]
Description=A certain ASP.NET Core application
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/imbushuo/somepath
ExecStart=/usr/bin/dotnet /opt/imbushuo/somepath/App.dll --server.urls=http://localhost:5050
Restart=on-failure
RestartSec=5
Environment="ASPNETCORE_ENVIRONMENT=Production"

[Install]
WantedBy=multi-user.target

Start it.

Step 4: Setting up reverse proxy

Refer to your frontend server documentation for details.

Some hints:

If you are using per-environment configuration file, make sure the configuration starts with capital letter, like appsettings.Production.json . Otherwise, the Startup class will not load the settings file.

If you want to run multiple applications, make sure add configuration class in Program class and apply it. Then, pass server.urls parameter with address and port, like what I wrote above.

The following Program.cs has been modified to enable parameter support. Feel free to copy it:

using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;

namespace HomePortal
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var configuration = new ConfigurationBuilder()
            .AddCommandLine(args)
            .Build();

            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseConfiguration(configuration)
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

Fixing Single Sign Out for Auth0 WordPress Integration

Since I set up Active Directory & Azure Active Directory for my workgroup and myself, I decided to switch to SSO for my web services. I chose Auth0 as the WordPress Identity Middleware as it is pretty flexible. However, the log out function, doesn’t work properly on federated logons. It will just call auth0 to sign out instead of signing out of all IdPs.

Luckily, fixing that is pretty easy. Located to lib/WP_Auth0_LoginManager.php and find the logout function:

If you don’t see federated after logout, fix it. They have fixed it on GitHub, but I don’t know why they don’t push it to WordPress release.

自制 Federation STS: MediaWiki x ASP.NET OWIN Identity

最近在做一个和 MediaWiki 扩展管理的项目时遇到一个问题:如何安全地把身份凭据传递给ASP.NET MVC的后端,而且共用一套账号系统。

这篇文章将简要讲述完成的过程。

了解 WS-Federation

在 ASP.NET OWIN Identity 里,最方便的实现 Claim Identity 凭据的方法也就是 WS-Federation。WS-Federation 最典型的一个例子就是Active Directory Federation Service,其具体工作流程可以简化为以下的图表:

WS-Fed workflow from docs.oasis-open.org
WS-Fed workflow from docs.oasis-open.org

在本文的场景中,MediaWiki 将充当 IdP 的角色。

获得 Federation Metadata

Federation Metadata是一个XML-Dsig签名后的XML文件,包含了 SP 需要的所有信息,诸如提供的 Claim Identity 类型,公钥,访问端点。一个简化的 Federation Metadata 如下文所示:

<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="_3ef47b02-f5a9-4a32-a48d-3ba56d6b270f" entityID="">
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <!-- 这里是签名,是 enveloped-signature + xml-exc-c14n -->
  <!-- 推荐 sha256RSA -->
  <!-- 记得带上公钥 -->
    </ds:Signature>
    <RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" 
 xsi:type="fed:SecurityTokenServiceType" 
 protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
     <!-- 用于STS签名的公钥, base64 encoded -->
                    <X509Certificate></X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
        <fed:ClaimTypesOffered>
   <!-- 提供的 Claim Identity Types,下面举例四个 -->
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" Optional="true">
                <auth:DisplayName>UPN</auth:DisplayName>
                <auth:Description>User Principal Name</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" Optional="true">
                <auth:DisplayName>User Name</auth:DisplayName>
                <auth:Description>The mutable display name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" Optional="true">
                <auth:DisplayName>Email</auth:DisplayName>
                <auth:Description>Email address of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/groups" Optional="true">
                <auth:DisplayName>Groups</auth:DisplayName>
                <auth:Description>Groups of the user.</auth:Description>
            </auth:ClaimType>
        </fed:ClaimTypesOffered>
  <!-- 服务Endpoint -->
        <fed:PassiveRequestorEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:PassiveRequestorEndpoint>
        <fed:SecurityTokenServiceEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:SecurityTokenServiceEndpoint>
    </RoleDescriptor>
    <RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" xsi:type="fed:SecurityTokenServiceType" protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
     <!-- 用于STS签名的公钥, base64 encoded -->
                    <X509Certificate></X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
  <!-- 服务Scope -->
  <!-- 确保Scope和后续的Audience匹配 -->
        <TargetScopes>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </TargetScopes>
  <!-- 服务Endpoint -->
        <fed:ApplicationServiceEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:ApplicationServiceEndpoint>
        <fed:PassiveRequestorEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:PassiveRequestorEndpoint>
    </RoleDescriptor>
    <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
     <!-- 用于STS签名的公钥, base64 encoded -->
                    <X509Certificate></X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
  <!-- 服务Endpoint -->
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location=""/>
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location=""/>
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location=""/>
    </IDPSSODescriptor>
</EntityDescriptor>

STS 服务

SP在解析 Federation Metadata 后会发起请求。一般在OWIN-based里会带上如下的参数:
– wa: 动作。可以是wsignin1.0 (登录) 和 wsignout1.0 (登出)
– wctx: 上下文。附上即可
– wp: 可能有
– wreply: 如果手动指定,则返回到这个页面;如果不指定,根据应用默认注册情况来
– wtrealm: 应用ID

所有的参数都在 Query String 里。在收到 STS 请求后,IdP首先根据情况判断有无再次输入密码必要。然后判断身份,签发凭据。签发的凭据也是一个XML文档,里面包含有一个SAML文档(XML-Dsig),简化的格式如下:

<?xml version="1.0" encoding="utf-8"?>
<t:RequestSecurityTokenResponse xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<!-- 需要标注有效时间 -->
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-02-12T05:13:30+0000</wsu:Created>
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-02-12T06:13:30+0000</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>https://ligstd.com/STSTest</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- 需要标注有效时间,AssertionID一般就是一个UUID -->
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" MajorVersion="1" MinorVersion="1" AssertionID="_7529070a-0300-4b65-bdee-df0e55c2c775" Issuer="签发者,参考metadata" IssueInstant="2016-02-12T05:13:30+0000">
<saml:Conditions NotBefore="2016-02-12T05:13:30+0000" NotAfter="2016-02-12T06:13:30+0000">
<saml:AudienceRestrictionCondition>
<!- 请注意这个必须和Scope匹配 -->
<saml:Audience>https://ligstd.com/STSTest</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AttributeStatement>
<!-- 各种 Claim Identity结果 -->
<!-- 请注意不能有空的Attribute -->
<saml:Subject>
<saml:NameIdentifier>Imbushuo</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Imbushuo@xxxx</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>i@xxxx</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Imbushuo</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" AuthenticationInstant="2016-02-12T05:13:30+0000">
<saml:Subject>
<saml:NameIdentifier>Imbushuo</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:AuthenticationStatement>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- 签名段,只需要对SAML签名。参考前面的XML签名 -->
</ds:Signature>
</saml:Assertion>
</t:RequestedSecurityToken>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>

然后将其进行 HTML 表单编码,封装到返回里。提供一段PHP代码作参考:

 $output->addHTML("<form method="POST" name="hiddenform" action="{$escapedToken}/">");
        $output->addHTML(' <input type="hidden" name="wa" value="wsignin1.0" />');
        $output->addHTML(" <input type="hidden" name="wresult" value="{$resultXml}" />");
        $output->addHTML(" <input type="hidden" name="wctx" value="{$this->wCtx}" />");
        if(isset($this->wp)){
            $output->addHTML(" <input type="hidden" name="wp" value="{$this->wp}" />");
        }
        $output->addHTML(' <noscript><p>Script is disabled. Click Submit to continue.</p><input type="submit" value="Submit" /></noscript>');
        $output->addHTML(' </form>');
        $output->addHTML(' <script language="javascript"> window.setTimeout('document.forms[0].submit()', 0); </script>');

在 MediaWiki 的工作流程

验证用户登录以及权限。如果不满足需求,返回权限错误。
通过 MediaWiki 的 User.php 里的函数获得必要的信息。
写 SOAP XML 和 SAML XML。用 xmlseclibs 对 SAML XML 签名,封入 SOAP XML,封入表单,返回特殊页面。
ASP.NET OWIN Identity 完成后续验证。

使用

新建 ASP.NET MVC v4.6 项目,认证模式选择 Work and School account,然后选择 On-Premise。输入 Federation Metadata 位置,输入 URI (如果实现了 App 注册,请输入对应的URI)
调试项目,已经可以使用。

备注

MediaWiki 的特殊页面输出非 text/html 有点麻烦,我选择了直接暴露一个在 extensions/文件夹/StsMetadata.php 的文件来暴露。
STS 所需的证书可以用 OpenSSL 生成。
由于 xmlseclib 在 URI 处的一些处理原因,目前似乎无法和 Azure ACS 直接工作,但是可以跟 ASP.NET OWIN Identity 工作。
一般来说,wsignout1.0 的处理就是销毁 Cookie ,注销 ST S这边的登录,ASP.NET 这儿会有 OWIN 自己处理,然后跟 wsignin1.0 的表单类似,但是不需要返回SAML数据。

推荐阅读

Understanding WS-Federation – MSDN
Web Services Federation Language (WS-Federation) Version 1.2

未完待续

面朝HTTPS,春暖花开

从今天起,做一个有证书的人,

加密,解密,保证安全

从今天起,关系PKI体系和TLS连接

我有一个证书,面朝HTTPS,春暖花开

本站现已支持HTTPS连接。稍后在配置后将全站强制HTTPS。