之所以和ADFS 'sayhello'是公司要求,实现内网项目在外网下的SSO登录访问
当第一次看到ADFS时,第一想到是公司内部哪个工程师搞得一个架构,取英文缩写ADFS,大概用于身份认证,提到到认证方式,想到目前市面主流的oauth2,Jwt,OpenID等,基于SAML2.0的ADFS服务器集成方案是啥,如果是内部框架也没有太详细的部署方案啊,去搜索saml概念,发现是网上有介绍,看一些saml介绍发现ADFS也是有介绍的.那么正式进入学习状态:
搞懂需求上写的啥至少前提要搞懂三种概念,
1.LDAP协议与AD域概念
2.SAML概念
3.ADFS概念
如果用了多年windows系统对windows域与工作组概念还模糊的查看windows域与工作组概念
学习新东西啃专业名词理解概念及原理是不可缺少的一步,以上概念如果你读了一遍还是蒙圈状态,那就多读几遍,书读百遍,其义自见.要不然不知道怎么动手,下面就大概记录下web端大概的集成思路
系统使用ADFS主要的目的:目前系统使用LDAP协议的AD域模式登录,是在内网下运行,如果实现系统的外网访问,保证用户身份验证的安全性,选择采用基于SAML的ADFS联合身份验证方案,具体的集成方案说明如下:
这张图取自: https://www.oasis-open.org/committees/download.php/11511/sstc-saml-tech-overview-2.0-draft-03.pdf
以上是用户认证的简单流程图,图中共有三个角色:
此图片说明了以下步骤:
web端启用基于SAML的SSO,使用OneLogin的开源SAML Java工具包,提供SAML身份验证,应用程序启用单点登录(SSO)。
核心包(com.onelogin:JavaSAML-core)
用于处理AuthNRequest、SAMLResponse、LogoutRequest、LogoutResponse和元数据的类和方法。此外,它还包含类来加载工具箱和HttpRequest类的设置,HttpRequest类是一个框架无关的HTTP请求表示。
工具包(com.onelogin:JavaSAML)
包含一个带有Auth类的,用于处理javax.servlet.http对象,javax.servlet.http对象。
github地址:https://github.com/onelogin/java-saml
Java-SAML具有以下依赖关系:
org.apache.santuario:xmlsec
joda-time:joda-time
org.apache.commons:commons-lang3
commons-codec:commons-codec
testing:
org.hamcrest:hamcrest-core and org.hamcrest:hamcrest-library
junit:junit
org.mockito:mockito-core
logging:
org.slf4j:slf4j-api
ch.qos.logback:logback-classic
For CI:
org.jacoco:jacoco-maven-plugin
具体请参看官网说明,官网地址:
https://developers.onelogin.com/saml/java
所有设置都在此文件中定义。
首先,我们需要配置SP的信息,IDP的信息,在某些情况下,还需要高级安全问题的配置,如签名和加密。
SAML JAVA Toolkit已经封装的相对比较完善,所以关键点在于我们对自己的Java项目进行正确的配置就可以接入
实际项目配置的onelogin.saml.properties如下:注解项除外保持默认值即可
# If 'strict' is True, then the Java Toolkit will reject unsigned
# or unencrypted messages if it expects them signed or encrypted
# Also will reject the messages if not strictly follow the SAML
onelogin.saml2.strict = false
# Enable debug mode (to print errors)
onelogin.saml2.debug = false
# Service Provider Data that we are deploying
#
# Identifier of the SP entity (must be a URI)
onelogin.saml2.sp.entityid = https://cat-dev.###.com/cat/jsp/sso/metadata.jsp
# Specifies info about where and how the message MUST be
# returned to the requester, in this case our SP.
# URL Location where the from the IDP will be returned
onelogin.saml2.sp.assertion_consumer_service.url = https://cat-dev.###.com/cat/r
# SAML protocol binding to be used when returning the
# message. Onelogin Toolkit supports for this endpoint the
# HTTP-POST binding only
onelogin.saml2.sp.assertion_consumer_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
# Specifies info about where and how the message MUST be
# returned to the requester, in this case our SP.
onelogin.saml2.sp.single_logout_service.url =
# SAML protocol binding to be used when returning the or sending the
# message. Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.sp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
# Specifies constraints on the name identifier to be used to
# represent the requested subject.
# Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
onelogin.saml2.sp.nameidformat = urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
# Usually x509cert and privateKey of the SP are provided by files placed at
# the certs folder. But we can also provide them with the following parameters
# test-----------------------------------------------------------
onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----\nMIICpDCCAYwCCQCaHqptRwiRkDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwNjEzMDYwNjE4WhcNMjgwNjEwMDYwNjE4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChL1RZp0wr7OgaAUk/n4woZgIv0i3ub7nLwgulK6TFYUUo66gKy/Pvpafwn4UklW0xv0Yfr2inyMjbTvmVAtUKWloPrEwvCw1N4w1Fo9KiPUkUt9mrAkiBgp7tbXE34wDT2qJZpx+3ne70nfIJcY3GUYx/FksKvo6s9yJ+GW4nZkMZEkAHD3AZwIF0OZwRicezbSsPOukE6Poc9q7bwoAjDrW8Ab3ll7U4F14ErKU/eesZ5lmTOKaEwZRkuq/1XwVQdFHySdUQuxdDsSNKbVOVBGL6r6ZAcyxX8KBunU6A3E55UWA2I4mg+rFRAEuXm6BAUOdkkS8yPyMkf+IeoIA7AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFR7Zmoug2EBjRtgF8GjpNAg94rZEBB6vWkNJPsFE6Bc9v7jOULNUuJUF+jvVntSrvs024ONHbAqD/2j/uKpO6Np7LUDEy/lY6t3IpZAmymrWjoVnm1/VZYm2VRZmOSlrkIuZLwu3LWwd39zxL4mDLbZTTQHz3SdiZP6MGIUgZy5NERCmJnLwPrrgHu0JJqZtXiwqAQUlyT1cZXwiOulwQd/3TDwxPPY/zTbclZw3u9WskLgjO0M0mKlg20rZxLpiTSUBDHMGsETKW5k6Q2fwwRreKROUG0zL62Jlj4DIIvW6vDNL8NQSnhohLOfXQMaSZeiH1XUuxC1ZR4fq+yCl7o=\n-----END CERTIFICATE-----
# Requires Format PKCS#8 BEGIN PRIVATE KEY
# If you have PKCS#1 BEGIN RSA PRIVATE KEY convert it by openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
# test-----------------------------------------------------------
onelogin.saml2.sp.privatekey = -----BEGIN PRIVATE KEY-----\nMIIEowIBAAKCAQEAoS9UWadMK+zoGgFJP5+MKGYCL9It7m+5y8ILpSukxWFFKOuoCsvz76Wn8J+FJJVtMb9GH69op8jI2075lQLVClpaD6xMLwsNTeMNRaPSoj1JFLfZqwJIgYKe7W1xN+MA09qiWacft53u9J3yCXGNxlGMfxZLCr6OrPcifhluJ2ZDGRJABw9wGcCBdDmcEYnHs20rDzrpBOj6HPau28KAIw61vAG95Ze1OBdeBKylP3nrGeZZkzimhMGUZLqv9V8FUHRR8knVELsXQ7EjSm1TlQRi+q+mQHMsV/Cgbp1OgNxOeVFgNiOJoPqxUQBLl5ugQFDnZJEvMj8jJH/iHqCAOwIDAQABAoIBADXOK9UlsJq1IaGUrlPruYi+zJoUCjse1qG669I+KGmvF7waNmUsQgjMfqwnQ/W7X9EMbackEcZ4kvwKd+wTHvSuxoOW23OUt+M5GPQXRLfdx2iAGswoHfYFmXHeZ73lLCCMSketLzxHHz5O/z3BxzbdgA3objJu/AenE7+OU6QYykjCn1zMi5yuYLPV8NytZCga4iGw8XS+H5KBfno4OQYPjyow+Q4itSlOJXdc6KFcbqzZXb5tZ+Q82J8qRUjQBgAqyQgOXTfDj/1IdwEn0yHlnjcT9tr+dNPAgeu8cknd9zcxl6xvYE1eYg620X0q5H3q0JJ2k+LRtxI6+Hr3BYkCgYEAzj/N1k53QfcOyCXq8afIGrFw51zeo3rv32AfkKSn6feN2Gy+9I3n9iOK2KTzemAjA3WbEboKK7l1rjUbU+3Svr3b7X1AnonyfBq2yvKWB4TVWkIWlpCy5pLRduY8OiJRtg0SJT8DPJMxESnkyZkqtZJL8LmT8Dw/eYVVY3ZakXcCgYEAyBC8G+8yMb+sgBfl7TAfTcLXk5n8F8nkpWKAcW4VFXtodrAXcLBClrJ+/BUkt/sqZA1IMjbUR094owGEyopJ1cnghRhmZx9dpJfwzgVPb53TOHTLrxm4A18jARaoLekfZ/PBhQOQiTE8akbPHbuwixcQEBS5jwgYJN2YM9A9mF0CgYBciUv1ByeCtTIworKS0dB6CXq6k3RgrNvKwPnoj7e2xZcir0fNuY2FZdT59qg3E8Mh3jZA8dN2YrNmAfXM5jtT0SNHnpbLiuD8xY+V5tlhbju7T0OLMkjSIrVQP2RuQM+geqTViTwOhYvSQ5WezdXXuVfRHbI+awmfoC77fTKNaQKBgQDCb6kyCOUifmMa1p8KRoOV4m/7LmNXh0qlBTdJhjANgbOD7h3J0jPVG8LYIYBfIkYPmOz6iFkEuRLIcThqU73wfdOr5ovXWx96UISi5XxPQPa/3pr6ISe6dyKg8zEd9XwlXjxMlqtI+kX6D7lI71ljxFVDG7E/diFo6sf6Sz8hrQKBgFyrZqsBgz0R6d9V0R0JWNpdt+WShl5Hb0HrG844ZJQZKZbEwJFDxi0I5GomCydfCEnyBS2+iLYdaNkAYPVjDynb5CZVPzy6shCE6T2GLG9Cc/IXhPMvNQz6RHx1HyM7TegIgQY4hh9ZHtDe8ROZrAucpOG0NHaT+gRuXe4bnedd\n-----END PRIVATE KEY-----
# Identity Provider Data that we want connect with our SP
#
# Identifier of the IDP entity (must be a URI)
# test-----------------------------------------------------------
onelogin.saml2.IDP.entityid = https://sso.###.com/FederationMetadata/2007-06/FederationMetadata.xml
# SSO endpoint info of the IDP. (Authentication Request protocol)
# URL Target of the IDP where the SP will send the Authentication Request Message
# test-------------------------------------------------------------
onelogin.saml2.IDP.single_sign_on_service.url = https://sso.###.com/adfs/ls
# SAML protocol binding to be used when returning the
# message. Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.IDP.single_sign_on_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
# SLO endpoint info of the IDP.
# URL Location of the IDP where the SP will send the SLO Request
# test-----------------------------------------------------------
onelogin.saml2.IDP.single_logout_service.url =
# Optional SLO Response endpoint info of the IDP.
# URL Location of the IDP where the SP will send the SLO Response. If left blank, same URL as onelogin.saml2.IDP.single_logout_service.url will be used.
# Some IDPs use a separate URL for sending a logout request and response, use this property to set the separate response url
# test-----------------------------------------------------------
onelogin.saml2.IDP.single_logout_service.response.url =
# SAML protocol binding to be used when returning the
# message. Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.IDP.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
# Public x509 certificate of the IDP
# test-----------------------------------------------------------
onelogin.saml2.IDP.x509cert = MIIC6DCCAdCgAwIBAgIQZiRU+RFa9rRJO57iE1ib9DANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVBREZTIFNpZ25pbmcgLSBkZXZvcHMueGlhb2hvbmdzaHUuY29tMB4XDTE3MDcyNTEzMDIxMFoXDTE4MDcyNTEzMDIxMFowMDEuMCwGA1UEAxMlQURGUyBTaWduaW5nIC0gZGV2b3BzLnhpYW9ob25nc2h1LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALfwBsXi/9Hlor/Q8W9ljtRk/Pqv91OPdS5K9f4SyQKs1idFi1fR94uCnbC3HxZeCo4/3iCYoiwpG7PZnuC/EIDIQ0Pdu2b7jOcgmTww6k2CsyZPMZxBWaFPWofwL7kJ+SEUxz0Vd93TV2J/V09Gy6QR6lTBtz2CJZXpkTSYuLnQMfkDiHJKeFsrBkiH5bqVyEriv7rgiFajb8gZdTiEEbWEqGgps++17oCpmz9dMi0Vjz31ij2YHWPh3Y7EJ/aVGBHcFD1KgIqfWeDxJ0e2EjIqM8bdDAq6MsAWll1++BIkKBMUdoFvu7S8jT5aszUnScQzEPCXJL0Cs6rmBhEDoCkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAhjhQbeaMJEYPC3ZwhfFAZMuuCOcFg3MgSh1fMDQ1iqtJ1uX7EpkX9NG0bJfFmyvU/5L/wjV41byg3N0HdHuO1VsFTV4q2XzFZJRBPb7Ta6OIQYw+qIX4RH18TSObguv3GgT7AbWHS8HDAmivmTWoL2fJL9sgIMnv/iL/EyG8iXf1fyZBP6Kr60d3xmPFyhlJ76/hBzqRGW6WrdAFunD1vIWr0u/zIs2GbY6A9CcLmpCTAgVTtytGmt+2iTm7trck1l8qOCQCkqX6iH6swHULLLZTUo6STQdTdsbtymf9GE396l6Pc3JwNXKG0Eq89/3l8o1vmyv7ikW1FdQUWTOs7w==
# Instead of use the whole x509cert you can use a fingerprint
# (openssl x509 -noout -fingerprint -in "IDP.crt" to generate it,
# or add for example the -sha256 , -sha384 or -sha512 parameter)
#
# If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
# let the toolkit know which Algorithm was used. Possible values: sha1, sha256, sha384 or sha512
# 'sha1' is the default value.
# onelogin.saml2.IDP.certfingerprint =
# onelogin.saml2.IDP.certfingerprint_algorithm = sha1
# Security settings
#
# Indicates that the nameID of the sent by this SP
# will be encrypted.
onelogin.saml2.security.nameid_encrypted = false
# Indicates whether the messages sent by this SP
# will be signed. [The Metadata of the SP will offer this info]
onelogin.saml2.security.authnrequest_signed = false
# Indicates whether the messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutrequest_signed = false
# Indicates whether the messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutresponse_signed = false
# Sign the Metadata
# Empty means no signature, or comma separate the keyFileName and the certFileName
onelogin.saml2.security.want_messages_signed =
# Indicates a requirement for the , and
# elements received by this SP to be signed.
onelogin.saml2.security.want_assertions_signed = false
# Indicates a requirement for the Metadata of this SP to be signed.
# Right now supported null (in order to not sign) or true (sign using SP private key)
onelogin.saml2.security.sign_metadata =
# Indicates a requirement for the Assertions received by this SP to be encrypted
onelogin.saml2.security.want_assertions_encrypted = false
# Indicates a requirement for the NameID received by this SP to be encrypted
onelogin.saml2.security.want_nameid = false
# Indicates a requirement for the NameID received by this SP to be encrypted
onelogin.saml2.security.want_nameid_encrypted = false
# Authentication context.
# Set Empty and no AuthContext will be sent in the AuthNRequest
# You can set multiple values (comma separated them)
onelogin.saml2.security.requested_authncontext = urn:oasis:names:tc:SAML:2.0:ac:classes:Password
# Allows the authn comparison parameter to be set, defaults to 'exact'
onelogin.saml2.security.onelogin.saml2.security.requested_authncontextcomparison = exact
# Indicates if the SP will validate all received xmls.
# (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true).
onelogin.saml2.security.want_xml_validation = true
# Algorithm that the toolkit will use on signing process. Options:
# 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
# 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
# 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
# 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
# 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
onelogin.saml2.security.signature_algorithm = http://www.w3.org/2000/09/xmldsig#rsa-sha1
Auth auth = new Auth(request, response);
auth.login();
根据安全设置‘onelogin.saml2.security.authnrequest_signed’发送已签名或无签名的AuthNRequest。
IDP然后将SAML响应返回给用户的客户端。然后,使用此信息转发到SP。
此代码将根据我们在配置文件中提供的信息提供我们的SP的XML元数据。
Auth auth = new Auth();
Saml2Settings settings = auth.getSettings();
settings.setSPValidationOnly(true);
String metadata = settings.getSPMetadata();
List errors = Saml2Settings.validateMetadata(metadata);
if (errors.isEmpty()) {
out.println(metadata);
} else {
response.setContentType("text/html; charset=UTF-8");
for (String error : errors) {
out.println(""+error+"
");
}
}
getSPMetdata将根据设置的安全参数返回签名或不签名的元数据。onelogin.saml2.security.sign_metadata。
此代码处理IDP通过用户客户端转发给SP的SAML响应。
Auth auth = new Auth(request, response);
auth.processResponse();
if (!auth.isAuthenticated()) {
out.println("Not authenticated");
}
List errors = auth.getErrors();
if (!errors.isEmpty()) {
out.println(StringUtils.join(errors, ", "));
if (auth.isDebugActive()) {
String errorReason = auth.getLastErrorReason();
if (errorReason != null && !errorReason.isEmpty()) {
out.println(auth.getLastErrorReason());
}
}
} else {
Map> attributes = auth.getAttributes();
String nameId = auth.getNameId();
String nameIdFormat = auth.getNameIdFormat();
String sessionIndex = auth.getSessionIndex();
String nameidNameQualifier = auth.getNameIdNameQualifier();
String nameidSPNameQualifier = auth.getNameIdSPNameQualifier();
session.setAttribute("attributes", attributes);
session.setAttribute("nameId", nameId);
session.setAttribute("nameIdFormat", nameIdFormat);
session.setAttribute("sessionIndex", sessionIndex);
session.setAttribute("nameidNameQualifier", nameidNameQualifier);
session.setAttribute("nameidSPNameQualifier", nameidSPNameQualifier);
String relayState = request.getParameter("RelayState");
if (relayState != null && relayState != ServletUtils.getSelfRoutedURLNoQuery(request)) {
response.sendRedirect(request.getParameter("RelayState"));
} else {
if (attributes.isEmpty()) {
out.println("You don't have any attributes");
}
else {
Collection keys = attributes.keySet();
for(String name :keys){
out.println(name);
List values = attributes.get(name);
for(String value :values) {
out.println(" - " + value);
}
}
}
}
}
SAML响应被处理,然后检查以确保没有错误。它还验证用户是否经过身份验证,然后将用户数据存储在会话中。在这一点上,有两种可能的选择:
1.如果没有提供RelayState,我们可以在这个视图中显示用户数据,或者以我们想要的方式显示用户数据。
2.如果提供RelayState,则会发生重定向。注意,在重定向之前将用户数据保存在会话中,以便在RelayState视图中获得可用的用户数据。
为了检索属性,使用:
Map> attributes = auth.getAttributes();
通过这种方法,获得了一个Map,其中包含了IDP在SAML响应断言中提供的所有用户数据。
每个属性名都可以用作获取值的键。在尝试获取属性之前,请检查用户是否经过身份验证。如果未对用户进行身份验证,则将返回一个空Map。例如,如果我们在auth.processResponse之前调用getAttributes,getAttributes()将返回一个空Map。
此代码处理注销请求和注销响应。
Auth auth = new Auth(request, response);
auth.processSLO();
List errors = auth.getErrors();
if (errors.isEmpty()) {
out.println("Sucessfully logged out");
} else {
for(String error : errors) {
out.println(error);
}
}
如果sp接收到注销请求,则验证请求,关闭会话,并将注销响应发送到IDP。
参考:
https://developers.onelogin.com/saml/java
http://www.cnblogs.com/qiuhk/p/9387772.html