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.

DragonBoard 410c w/ UEFI

DragonBoard 410c runs Windows RT 8.1

I purchased a Qualcomm DragonBoard 410c for some specific purpose (generally, for fun). All DragonBoard 410c ship with Android, but I want Windows IoT and UEFI so I flashed it.

Dragonboard 410c runs Windows RT 8.1
Dragonboard 410c runs Windows RT 8.1

Well, you can boot Windows RT 8.1 on it, as long as you got critical HAL extensions. But the most important one, USB controller, is not available on DragonBoard 410c. It utilizes USB Role Switch, which is officially supported in Windows 10, not in Windows 8.1. I posted how to boot Windows RT 8.1 installation disk on XDA Developers forum, if you are interested in that, please search it.

Note: USB will not be a problem for DragonBoard 800 because Snapdragon 800 has more than one USB channel, while Snapdragon 410 has only one USB channel.

Windows failed to load with Bugcheck code 0x5c
Windows failed to load with Bugcheck code 0x5c

I think there’s something weird with the internal EFI shell. It seems that all GOP operations will let the firmware hang, however, you can run EFI applications with GOP in the internal EBL(yet another lightweight EFI shell). What’s more, if you boot any Windows OS releases from EFI shell, it will crash during HAL initialization. Such situation doesn’t exist on any Qualcomm-based Windows Phones (MSM8960), so I believe it is a bug specific to 410c’s firmware.

Huaji PC Boot Logo
Huaji PC Boot Logo

All ACPI-related files, including boot logo, is stored in a small hidden FAT16 partition called PLAT. You can replace files – but I haven’t tested customized ACPI DSDT table. Maybe we can let USB controller work by removing device URS0 and expose device USB0 to root.

GRUB-EFI (ARMHF) works on Qemu emulator, but it will hang on 410c’s firmware. I haven’t got a USB UART cable yet, so I didn’t know what happened.

Rule-based Routing & Traffic Forwarding with IPsec Site to Site VPN and Linux

This article is adapted from https://www.v2ex.com/t/180070 and http://hjc.im/shi-yong-strongswanda-jian-ipsecikev2-vpn/. The major difference is ShadowVPN is replaced by StrongSwan IPsec VPN in this article.

Readiness Check

  • Upgrade your staging/production environment to the latest version. Make sure all security patches are installed.
  • Make sure you have packages libpam0g-dev libssl-dev make gcc installed.

Building StrongSwan

Download the latest version here: http://download.strongswan.org/strongswan.tar.gz

Unarchive it, and configure using the following params:

./configure  --enable-eap-identity --enable-eap-md5 
--enable-eap-mschapv2 --enable-eap-tls --enable-eap-ttls --enable-eap-peap  
--enable-eap-tnc --enable-eap-dynamic --enable-eap-radius --enable-xauth-eap  
--enable-xauth-pam  --enable-dhcp  --enable-openssl  --enable-addrblock --enable-unity  
--enable-certexpire --enable-radattr --enable-tools --enable-openssl --disable-gmp --enable-kernel-libipsec

We have to specify routing table’s priority on client server(not IPsec Access Server) since we wants to specify routing table manually: –with-routing-table-prio=32800.  Also, TAP/TUN device is enabled instead of StrongSwan’s own kernel module. It will simplify the configuration later.

Then make and install it.

Configure IPsec Access Server

Just a reminder, if you have any issues about IP range, please refer to the demo topology graph. The image is unavailable right now for some reason. I’ll fix it ASAP.

Go to /usr/local/etc. Edit ipsec.secrets:

: PSK "<IPsec PRE SHARED KEY, PLEASE REMEMBER TO REPLACE IT WITH YOU OWN KEY>"
s2s : XAUTH "<ANY PASSWORD YOU WANT, PLEASE REMEMBER TO REPLACE IT WITH YOU OWN PASSWORD>"

Go to /usr/local/etc. Edit ipsec.conf:

config setup
    uniqueids=never 

conn s2sbj1_xauth_psk
    keyexchange=ikev1
    left=%defaultroute
    leftauth=psk
    leftsubnet=0.0.0.0/0
    right=%any
    rightauth=psk
    rightauth2=xauth
    rightsourceip=100.11.2.0/24
    auto=add

Go to /usr/local/etc. Edit strongswan.conf:

 charon {
         load_modular = yes
         duplicheck.enable = no
         compress = yes
         plugins {
                 include strongswan.d/charon/*.conf
         }
         # In China, please consider about replacing 8.8.8.8/8.8.4.4 to 114.114.114.114. They do offer correct DNS query results outside mainland China, as long as you have configured the Chinese routing exception for it(route to non-mainland China outbound server)
         dns1 = 8.8.8.8
         dns2 = 8.8.4.4
         nbns1 = 8.8.8.8
         nbns2 = 8.8.4.4
 }
 include strongswan.d/*.conf

Turn on IPv4 forwarding in sysctl.conf.

Edit iptables. The following configuration is for Ubuntu 14.04 LTS. For other Linux distro, check out its documentation.

iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 100.11.0.0/24  -j ACCEPT
iptables -A FORWARD -s 100.11.1.0/24  -j ACCEPT
iptables -A FORWARD -s 100.11.2.0/24  -j ACCEPT
iptables -A INPUT -i eth0 -p esp -j ACCEPT
iptables -A INPUT -i eth0 -p udp --dport 500 -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --dport 500 -j ACCEPT
iptables -A INPUT -i eth0 -p udp --dport 4500 -j ACCEPT
iptables -A INPUT -i eth0 -p udp --dport 1701 -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --dport 1723 -j ACCEPT
iptables -A FORWARD -j REJECT
iptables -t nat -A POSTROUTING -s 100.11.0.0/24 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 100.11.1.0/24 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 100.11.2.0/24 -o eth0 -j MASQUERADE

Save it

iptables-save > /etc/iptables.rules
cat > /etc/network/if-up.d/iptables<<EOF
#!/bin/sh
iptables-restore < /etc/iptables.rules
EOF
chmod +x /etc/network/if-up.d/iptables

Configure IPsec client

Suppose you have the correct StrongSwan with TAP/TUN and routing table priority installed.

Go to /usr/local/etc, edit ipsec.secrets, just put what you have in the previous step.

Go to /usr/local/etc, edit ipsec.conf:

config setup
    uniqueids=never 
    strictcrlpolicy=no
    

conn s2sbj1
    keyexchange=ikev1
    ikelifetime=1440m
    keylife=120m
    # Enable this will cause authentication failure
    aggressive=no
    ike=aes-sha2-modp2048
    esp=ase-sha2
    xauth=client
    left=<eth0's IP address>
    # Ask your server
    leftsourceip=%config
    leftauth=psk
    leftauth2=xauth
    rightauth=psk
    rightauth2=xauth
    right=<Your IPsec VPN Server's public IP address>
    # For Microsoft Azure and other service providers who use SNAT, specify that to prevent IKE_SA failure
    rightid=%any
    xauth_identity=s2s
    auto=add
    rightsubnet=0.0.0.0/0

Establish Connection

On your IPsec VPN server, type sudo ipsec start .

On your client server, type:

sudo ipsec start
sudo ipsec up s2sbj1

It should get connected shortly. Go to ifconfig and you should find a new network adapter called ipsec0.

Configure rule-based routing

Create a new routing table.

user@ibntwkstgepbj1:~$  sudo vim /etc/iproute2/rt_tables
200 bj1s2s
:q
user@ibntwkstgepbj1:~$

Get the routing configuration in table 220 (IPsec table).

user@ibntwkstgepdm1:~$  sudo ip route list table 220
default dev ipsec0  proto static  src 100.11.2.1
42.159.66.233 via 10.0.0.1 dev eth0  proto static  src 10.0.0.4

Specify the default route for this table (copy it from 220):

user@ibntwkstgepdm1:~$  sudo ip route add default dev ipsec0  proto static  src 100.11.2.1 table bj1s2s

(You don’t have to copy the second line I guess, but I added that)

Add IP rules.

user@ibntwkstgepdm1:~$  ip rule add from <IP Range> table bj1s2s

Refresh routing table.

user@ibntwkstgepdm1~$  ip route flush cache

Compose a shell script if you want to compelte that automatically for every reconnection.

Conclusion

We implemented a simple line optimization using rule-based routing in this example. For application-based service, you are all set and ready to go. For VPN access services, configuration for iptables is needed in order to tag data packets and route them correctly. Check out this article for more details.

I didn’t offer a auto routing script in this example. I strongly recommend you to write it since it saves your time by configuring routing table automatically.

For multiple IPsec connections and routings, just specify the source IP, which is 100.11.2.1 and 100.21.2.1 in routing tables. They use the same adapter.

Using WASAPI Exclusive mode in Universal Windows Apps (Desktop)

Windows Audio Session API (WASAPI) was first introduced in Windows Vista. It offers advanced audio control and playback features for Windows Apps. Since it mitigates SRC issue in some aspects, WASAPI Exclusive Mode gains its popularity among music lovers. Windows Runtime supports a small subset of WASAPI APIs, including WASAPI Exclusive mode (in Windows 10).

However, here is one thing you should know: Windows 10 Mobile doesn’t support WASAPI Exclusive mode (by design).  As far as I know, only desktop platform is supported yet. Luckily, modern Windows Phone devices can choose the best format for shared mode using input wave’s format, so you don’t have to worry that (at least on Lumia 950 and Lumia 950 XL).

Audio Format

There’s no IMMDevice available in Windows Runtime. Try to create it via CLSID & IID will throw HRESULT Class Not Registered. It will cause some trouble getting all natively supported formats for Exclusive mode. A possible solution is using Properties property in DeviceInformation class in Windows Runtime instead of IMMDevice class & OpenPropertyStore method. Then, query the format using IsFormatSupported in IAudioClient2. Remember that in Exclusive Mode, system won’t return the best-fit wave format in this method, so you have to try all formats and select the best wave format by yourself. In my sample, I specified 44.1kHz / 16Bit / WaveFormat = 0x1, which is supported by my Surface Pro’s audio subsystem.

Initialize Audio Client

Then you can initialize IAudioClient2 in exclusive mode. Simply pass AUDCLNT_SHAREMODE_EXCLUSIVE in, specify Buffer Length and Wave Format. You may get some HRESULTs like AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED or AUDCLNT_E_BUFFER_SIZE_ERROR. Just check out this page and find solutions.

Perform Playback

Like HW-Offload mode, you don’t have to calculate available frames by yourself In event-based playback mode. The value for padding frames is exactly the same as available frames. Then return audio samples as what you do in shared mode.

Notes

It should supports Windows 8.1 desktop, but I haven’t test it on Windows 8.1 since I don’t have a Windows 8.1 desktop device.

I often noticed weird noise during playback when the system average load is high. I believe the root cause is process priority.

Windows 10 removed Background Audio category in WASAPI headers. In order to implement background playback, a customized out-of-process COM server or Media Foundation extension is required.

I don’t offer a demo program here, because it’s pretty easy to adapt the official sample to WASAPI Exclusive mode. 🙂

玩物丧志 · Yubikey 4 (智能卡登录)

Yubikey 是很早就有的东西了,然而受限于经济能力还有海关,直到最近有人提出来要团购我才买了个一个玩玩。我主要拿来做智能卡登录。

为什么呢?

虽然我用的电脑都装备有了 TPM ,但是这样还是有一些不方便的:

  • TPM 的数据基本上只留在这台电脑了(这也是优点)
  • 虽然我的电脑很轻,但是我不一定天天带出去,万一有事情需要连接到服务器上还是比较麻烦
  • 虚拟智能卡基本上一直在域账号里使用比较有效,然而我平时还是以登录个人账号为主(虽然电脑已经加入域)
 Yubikey 可以解决移动性的问题,而且出门不需要输入一个几十位复杂脑残的密码。
也许我哪天要玩玩 U2F 呢?这大概是以后的文章了。

准备智能卡证书

假定电脑已经加入域,域环境中已经部署了 Active Directory Certificate Services,并且 CS 服务器已经发布到 AD 中。

登录域账号,打开 Certificate Management,并 Request New Certificate。
一路往下,选择 Smart Card User / Smart Card  Logon 模板(我选的是前者)。
在 Private Key Configuration 下把私钥长度设置为 2048/4096,然后 CSP 不用动。
注:如果你选择了 Smart Card Crypto Service Provider,那么默认情况下在有 TPM 而且虚拟智能卡开启的情况下,会选择的是虚拟智能卡,在 Windows 证书选择里表示为“安全设备凭据”而不是“智能卡凭据”,但是功能类似。然后勾选允许导出私钥。
申请完证书,导出证书。
打开 Yubikey PIV Manager,并导入证书。
Screenshot of Yubikey PIV Manager
Screenshot of Yubikey PIV Manager

基本工作到此结束。下面来试试智能卡登录。

如果是要给 Active Directory Federation Services 打开智能卡登录支持呢?

很简单。进入 AD CS 管理界面,找到认证选项,在对应的 Intranet / External 认证模式里勾选证书登录,AD FS Web Proxy 启用49443端口即可。

Smartcard and ADFS
Smartcard and ADFS

效果大概如图所示。

未完待续

自制 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

未完待续