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": "这里填写密码"
}
]
}
]
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"
}
}
{"issued_token":"
\r\n
<saml:Issuer>stsidsaml:Issuer>
\r\n
<ds:SignedInfo>\r\n
\r\n
\r\n
\r\n
<ds:Transforms>\r\n
\r\n
\r\n
ds:Transforms>\r\n
\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\nds: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\nds:X509Certificate>\r\n
ds:X509Data>\r\n
ds:KeyInfo>\r\n
ds:Signature>
<saml:Subject>\r\n
amadminsaml:NameID>
\r\n
saml:SubjectConfirmation>\r\n
saml:Subject>
\r\n
<saml:AudienceRestriction>\r\n
<saml:Audience>stsidsaml:Audience>\r\n
saml:AudienceRestriction>\r\n
saml:Conditions>\r\n
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSessionsaml: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")