What’s SAML2
SAML是安全斷言標記語言(英語:Security Assertion Markup Language,簡稱SAML,發音sam-el),一種xml格式的語言。 有兩個點: 第一是安全(Security)
, 第二是斷言(assertion)
。
先看它的核心概念斷言(assertion)
。 斷言是什么? 就是做出判斷的語言。比如一個例子:小紅沒有權限讀取根目錄。這就是一個斷言。 這種“做出判斷的語句
”我們在很多場合都需要用到。 比如你在網上嘗試登陸一個服務的時候, 這個服務需要知道你是不是合法的用戶。 這個時候如果你能提供一個“安全,可靠,可信任”的斷言:“***有權登陸XX服務”, 那么這個服務就知道你合法了, 於是就能為你提供服務了。 這個例子比較抽象,但基本上能表達斷言在實際用例中的作用了。 實際上SAML2的大部分內容就在於證明你是誰,你擁有什么權限等等了。
接下來第二個概念就是安全(Security)
了。 你能提供一個斷言, 別人能不能假冒你提供一個斷言從而騙取服務端的信任呢? 另外服務端為什么會信任你給的斷言呢? 這就涉及到安全的問題了。為了防止斷言被假冒,篡改。SAML中加入了安全措施。 當然現今能抵御假冒,篡改,重放攻擊的利器就是公鑰-私鑰
/keystore
系統了。 通過給斷言加上簽名和加密,再結合數字證書系統就確保了SAML不受攻擊。
Config With OpenAM SAML2
OpenAM配置SAML2分2種方式
:
- 默認方式(本文采用默認方式,Sign Assertion)
- 開發方式(自定義AttributeMapper,TokenProvider)
默認方式和開發方式都是分3步走
,只是配置有所不同而已:
- 配置SAML2 STS Instance
- 配置SAML2 Service Provider(Configure OAuth2 authorization server)
- 驗證SAML2
配置SAML2 STS Instance
1.進入Top Level Realm
,找到STS
,這里的STS
是Security Token Service
的意思。
2.new一個STS
,Supported Token Transforms
選擇OPENAM->SAML2,don't invalidate interim OpenAM session
3.Deployment Url Element
就是這個STS的名稱,這里要記住,后面會用到,拼接REST請求的時候,URL為rest-sts/stsName
,那么我們就是rest-sts/mysts
4.這里定義一下issuer Id
和Entity Id
5.詳細設置SAML2 Sign相關
Sign Assertion
,打勾√,啟用斷言簽證,不簽的話,報文很少KeyStorePath
位於C:\Users\你的用戶\openampro\openampro\keystore.jks
(如果你的項目叫openam則為C:\Users\你的用戶\openam\openam\keystore.jks)Keystore Password
,記事本打開.storepass
文件,明文copy即可Signature Key Password
,記事本打開.keypass
文件,明文copy即可
6.保存
配置SAML2 Service Provider
1.返回Dashboard
,扎到Common Tasks
下面的Configure SAMLv2 Provider

2.由於是本地Localhost的Provider,所以是選擇Create Hosted Service Provider

3.默認配置即可,metadata
會自動填寫,如果沒有就寫上項目路徑,Circle Of Trust
隨便寫個cot或者mycot即可。

4.保存並返回查看

驗證SAML2
1.POST請求authenticate接口獲得以下authenticate報文,無需任何參數
http://localhost:8099/openampro/json/authenticate
{
"authId": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvdGsiOiJraWM1cmM3aGVjN2dsbWIzYWZzczE4MHQ0cCIsInJlYWxtIjoiZGM9b3BlbmFtLGRjPWZvcmdlcm9jayxkYz1vcmciLCJzZXNzaW9uSWQiOiJBUUlDNXdNMkxZNFNmY3c0OFhTUDNlR2NIZVB1VUN3bW9IdGRyVWpsdHZsX1BIby4qQUFKVFNRQUNNREVBQWxOTEFCTTBOVFEzTURVMU1URTBOVGMwTXpFMU1EZzVBQUpUTVFBQSoifQ.Xq4okO5FjamU62bu0xeZbUSE15pCaG_fKw9XseMJi64",
"template": "",
"stage": "DataStore1",
"header": "Sign in",
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": "User Name:"
}
],
"input": [
{
"name": "IDToken1",
"value": ""
}
]
},
{
"type": "PasswordCallback",
"output": [
{
"name": "prompt",
"value": "Password:"
}
],
"input": [
{
"name": "IDToken2",
"value": ""
}
]
}
]
}
2.Copy上一部獲取到的報文在body/raw里面,然后補全authenticate報文的賬號(IDToken1-value)和密碼(IDToken2-value),繼續請求該接口
http://localhost:8099/openampro/json/authenticate
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": "User Name:"
}
],
"input": [
{
"name": "IDToken1",
"value": "這里填寫賬號"
}
]
},
{
"type": "PasswordCallback",
"output": [
{
"name": "prompt",
"value": "Password:"
}
],
"input": [
{
"name": "IDToken2",
"value": "這里填寫密碼"
}
]
}
]
3.得到tokenId
AQIC5wM2LY4SfczBElQ-gyrwsTOLXPZscWBxk776W1IYfS4.AAJTSQACMDEAAlNLABM0ODEyODAzNTQxNzU4MDE3MDA3AAJTMQAA
4.拼接報文,把tokenId
填入session_id
后面的值,然后POST請求translate接口,進行OPENAM->SAML2報文轉換
http://localhost:8099/openampro/rest-sts/mysts?_action=translate
{
"input_token_state":
{
"token_type": "OPENAM",
"session_id": "AQIC5wM2LY4SfczBElQ-gyrwsTOLXPZscWBxk776W1IYfS4.*AAJTSQACMDEAAlNLABM0ODEyODAzNTQxNzU4MDE3MDA3AAJTMQAA*"
},
"output_token_state":
{
"token_type": "SAML2",
"subject_confirmation": "BEARER"
}
}
5.得到Signed后的報文,這就是SAML2報文啦
{"issued_token":"
<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"s2a8a19d7e7801a6243ad1b34d5066852eca7da372\" IssueInstant=\"2019-05-31T06:25:50Z\" Version=\"2.0\">\r\n
<saml:Issuer>stsid</saml:Issuer>
<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\r\n
<ds:SignedInfo>\r\n
<ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\r\n
<ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\r\n
<ds:Reference URI=\"#s2a8a19d7e7801a6243ad1b34d5066852eca7da372\">\r\n
<ds:Transforms>\r\n
<ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\r\n
<ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\r\n
</ds:Transforms>\r\n
<ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>\r\n
<ds:DigestValue>AZShA8hm3+BuU8SkTQtbvkjpl9o=</ds:DigestValue>\r\n
</ds:Reference>\r\n
</ds:SignedInfo>\r\n
<ds:SignatureValue>\r\nRkuIgun5A6sInaD3HWZ7CbQXkiWDxTR2zJ6o/h4IOf7jutSl6lCLEHUs1qSjyILO1xeOMS3VsDpn\r\nplpYfZF3tHornzdDm++9x538qDnxlzIHVN3WQKu9yLoqrkw0arU1I+KZb8dKnZHIPf9RnK96RLuz\r\nO4yEsjQrPpB3hRBF0oI=\r\n</ds:SignatureValue>\r\n
<ds:KeyInfo>\r\n
<ds:X509Data>\r\n
<ds:X509Certificate>\r\nMIICQDCCAakCBEeNB0swDQYJKoZIhvcNAQEEBQAwZzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh\r\nbGlmb3JuaWExFDASBgNVBAcTC1NhbnRhIENsYXJhMQwwCgYDVQQKEwNTdW4xEDAOBgNVBAsTB09w\r\nZW5TU08xDTALBgNVBAMTBHRlc3QwHhcNMDgwMTE1MTkxOTM5WhcNMTgwMTEyMTkxOTM5WjBnMQsw\r\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExDDAK\r\nBgNVBAoTA1N1bjEQMA4GA1UECxMHT3BlblNTTzENMAsGA1UEAxMEdGVzdDCBnzANBgkqhkiG9w0B\r\nAQEFAAOBjQAwgYkCgYEArSQc/U75GB2AtKhbGS5piiLkmJzqEsp64rDxbMJ+xDrye0EN/q1U5Of+\r\nRkDsaN/igkAvV1cuXEgTL6RlafFPcUX7QxDhZBhsYF9pbwtMzi4A4su9hnxIhURebGEmxKW9qJNY\r\nJs0Vo5+IgjxuEWnjnnVgHTs1+mq5QYTA7E6ZyL8CAwEAATANBgkqhkiG9w0BAQQFAAOBgQB3Pw/U\r\nQzPKTPTYi9upbFXlrAKMwtFf2OW4yvGWWvlcwcNSZJmTJ8ARvVYOMEVNbsT4OFcfu2/PeYoAdiDA\r\ncGy/F2Zuj8XJJpuQRSE6PtQqBuDEHjjmOQJ0rV/r8mO1ZCtHRhpZ5zYRjhRC9eCbjx9VrFax0JDC\r\n/FfwWigmrW0Y0Q==\r\n</ds:X509Certificate>\r\n
</ds:X509Data>\r\n
</ds:KeyInfo>\r\n
</ds:Signature>
<saml:Subject>\r\n
<saml:NameID Format=\"urn:oasis:names:tc:SAML:1.0:nameid-format:unspecified\">amadmin</saml:NameID>
<saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\r\n
<saml:SubjectConfirmationData NotOnOrAfter=\"2019-05-31T06:35:50Z\"/>
</saml:SubjectConfirmation>\r\n
</saml:Subject>
<saml:Conditions NotBefore=\"2019-05-31T06:25:50Z\" NotOnOrAfter=\"2019-05-31T06:35:50Z\">\r\n
<saml:AudienceRestriction>\r\n
<saml:Audience>stsid</saml:Audience>\r\n
</saml:AudienceRestriction>\r\n
</saml:Conditions>\r\n
<saml:AuthnStatement AuthnInstant=\"2019-05-31T06:25:50Z\">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>"}
以下部分為shell+json自動化配置,部分核心配置
使用JSON配置
{
"invocation_context": "invocation_context_client_sdk",
"instance_state": {
"persist-issued-tokens-in-cts": "false",
"supported-token-transforms": [{
"inputTokenType": "OPENAM",
"outputTokenType": "SAML2",
"invalidateInterimOpenAMSession": false
}],
"custom-token-validators": [],
"custom-token-providers": [{
"customTokenName":"SAML2_XXX",
"customOperationClassName":"com.xxxcompany.sts.tokengeneration.saml2.SamlTokenProvider"
}],
"custom-token-transforms": [{
"inputTokenType": "OPENAM",
"outputTokenType": "SAML2_HSM",
"invalidateInterimOpenAMSession": false
}],
"deployment-config": {
"deployment-realm": "/sdex_global",
"deployment-url-element": "ssotoken_transformer_xxxapp",
"deployment-auth-target-mappings": {},
"deployment-offloaded-two-way-tls-header-key": null,
"deployment-tls-offload-engine-hosts": {}
},
"saml2-config": {
"issuer-name": "xxx",
"saml2-sp-entity-id": "xxxapp_url_tbc",
"saml2-sp-acs-url": "xxxapp_url_tbc",
"saml2-name-id-format": "urn:oasis:names:tc:SAML:1.0:nameid-format:unspecified",
"saml2-token-lifetime-seconds": "30",
"saml2-custom-conditions-provider-class-name": null,
"saml2-custom-subject-provider-class-name": null,
"saml2-custom-authentication-statements-provider-class-name": null,
"saml2-custom-attribute-statements-provider-class-name": null,
"saml2-custom-authz-decision-statements-provider-class-name": null,
"saml2-custom-attribute-mapper-class-name": "com.xxxcompany.sts.tokengeneration.saml2.statements.CustomAttributeMapper",
"saml2-custom-authn-context-mapper-class-name": null,
"saml2-attribute-map": {
"customerId": "session|XXXCom-Customer-Id",
"guid":"session|guid"
},
"saml2-sign-assertion": "true",
"saml2-encrypt-assertion": "false",
"saml2-encrypt-attributes": "false",
"saml2-encrypt-nameid": "false",
"saml2-encryption-algorithm": "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
"saml2-encryption-algorithm-strength": "128",
"saml2-keystore-filename": "/opt/xxxcompany/xxxapp/xxxapp_SAML_xxx_DEV.keystore",
"saml2-keystore-password": "changeit",
"saml2-encryption-key-alias": "",
"saml2-signature-key-alias": "xxxapp_SAML_CMB_DEV",
"saml2-signature-key-password": "changeit"
}
}
}
sts-setup.sh
#!/bin/bash
# Constants for text color
color_off='\033[0m'
color_red='\033[0;31m'
color_green='\033[0;32m'
color_yellow='\033[0;33m'
script_path="$( cd "$(dirname "$0")" ; pwd -P )" # Load bootstrap file bootstrap_file=${script_path}/bootstrap.properties [ ! -f $bootstrap_file ] && { echo "Error: Missing $bootstrap_file"; exit 1; } source $bootstrap_file [[ -z "$am_instance_name" ]] && { echo "Error: 'am_instance_name' undefined in $bootstrap_file"; exit 1; } [[ -z "$am_server_url" ]] && { echo "Error: 'am_server_url' undefined in $bootstrap_file"; exit 1; } [[ -z "$admin_user" ]] && { echo "Error: 'admin_user' undefined in $bootstrap_file"; exit 1; } [[ -z "$admin_password_file" ]] && { echo "Error: 'admin_password_file' undefined in $bootstrap_file"; exit 1; } [ ! -f $admin_password_file ] && { echo "Error: Missing $admin_password_file"; exit 1; } [[ -z "$com_iplanet_am_cookie_name" ]] && { echo "Error: 'com_iplanet_am_cookie_name' undefined in $bootstrap_file"; exit 1; } [[ -z "$openam_tools_dir" ]] && { echo "Error: 'openam_tools_dir' undefined in $bootstrap_file"; exit 1; } [[ -z "$ssoadm" ]] && { echo "Error: 'ssoadm' undefined in $bootstrap_file"; exit 1; } [[ -z "$realms" ]] && { echo "Error: 'realms' undefined in $bootstrap_file"; exit 1; } create_sts_instance() { realm=$1 # Validate data file data_file=${script_path}/${realm#/}/rest-sts-instance-config.json [ ! -f $data_file ] && { echo -e "${color_red}Error: Missing data file ${data_file}${color_off}"; return; } deployment_realm=$(cat $data_file 2>/dev/null | python -c 'import sys, json; print json.load(sys.stdin)["instance_state"]["deployment-config"]["deployment-realm"]' 2>/dev/null) [[ -z "$deployment_realm" ]] && { echo -e "${color_red}Error: 'deployment-realm' undefined in ${data_file}${color_off}"; return; } [ "$deployment_realm" != "$realm" ] && { echo -e "${color_red}Error: Deployment realm '${deployment_realm}' is different than the configuration realm '${realm}'${color_off}"; return; } deployment_url_element=$(cat $data_file 2>/dev/null | python -c 'import sys, json; print json.load(sys.stdin)["instance_state"]["deployment-config"]["deployment-url-element"]' 2>/dev/null) [[ -z "$deployment_url_element" ]] && { echo -e "${color_red}Error: 'deployment-url-element' undefined in ${data_file}${color_off}"; return; } echo -n "Create STS instance ${deployment_realm}/${deployment_url_element} in realm ${realm}... " admin_password=$(cat $admin_password_file) auth_response=$(curl -k -s -X POST \ -H "Content-type: application/json" \ -H "X-OpenAM-Username:${admin_user}" \ -H "X-OpenAM-Password:${admin_password}" \ ${am_server_url}/json/authenticate) am_token=$(echo $auth_response | python -c 'import sys, json; print json.load(sys.stdin)["tokenId"]' 2>/dev/null) [[ -z "$am_token" ]] && { echo -e "${color_red}FAILED!\nAuthentication failed.\nAuthentication response: ${auth_response}${color_off}"; return; } sts_response=$(curl -k -s -X POST \ -H "Content-Type: application/json" \ -H "${com_iplanet_am_cookie_name}: ${am_token}" \ -d @$data_file \ ${am_server_url}/sts-publish/rest?_action=create) sts_response_result=$(echo $sts_response | python -c 'import sys, json; print json.load(sys.stdin)["result"]' 2>/dev/null) [ "$sts_response_result" = "success" ] && echo -e "${color_green}SUCCESS!" || echo -e "${color_red}FAILED!" echo -e "${sts_response}${color_off}" } for realm in "${realms[@]}" do echo "=================================================" echo "Configuring realm ${realm}:" create_sts_instance $realm echo "================================================="
done
bootstrap.properties
# AM settings
am_instance_name=xxx-dsp-openam
am_server_url=https://yourserver/${am_instance_name}
admin_user=amadmin
admin_password_file=/opt/openam/openam-tools/admin/.pwd
com_iplanet_am_cookie_name=AMToken
# SSO Admin Tools settings
openam_tools_dir=/opt/openam/openam-tools/admin
ssoadm=${openam_tools_dir}/${am_instance_name}/bin/ssoadm
# Realms to configure
realms=("/sdex_global")