什么是SAML协议

SAML(Security Assertion Markup Language)是一种依据XML的规范,用于在不同的安全域之间传递身份验证和授权数据。SAML2.0是SAML协议的最新版别,它供给了一种规范的办法来完成单点登录(SSO)和跨域身份验证(Cross-Domain Authentication)。

【分布式技术专题】「单点登录技术架构」一文带领你好好对接对应的Okta单点登录实现接口服务的实现落地

SAML2.0协议定义了三种角色:身份供给者(Identity Provider,IdP)服务供给者(Service Provider,SP)用户(User) 。其间,身份供给者 是负责认证用户身份的体系,服务供给者是供给服务的体系,用户是需求拜访服务的个别。

SAML 2.0协议的流程

【分布式技术专题】「单点登录技术架构」一文带领你好好对接对应的Okta单点登录实现接口服务的实现落地

  1. 用户向服务供给者建议恳求,服务供给者查看用户是否现已登录。

  2. 假如用户没有登录,则服务供给者将用户重定向到身份供给者。

  3. 身份供给者验证用户身份,并生成一个SAML呼应,其间包括用户的身份信息和授权信息。

  4. 身份供给者将SAML呼应发送给服务供给者。

  5. 服务供给者验证SAML呼应的签名,并提取用户的身份信息和授权信息。

  6. 服务供给者运用用户的身份信息和授权信息来授权用户拜访服务。

SAML2.0协议的长处

SAML 2.0协议的长处在于它供给了一种规范的办法来完成跨域身份验证和单点登录,从而简化了用户的登录流程,提高了用户体会。此外,SAML 2.0协议还供给了强大的安全性和灵活性,能够满足各种不同的安全需求。

SAML2.0协议的特点

  • 可用性:从门户或Intranet的一键式拜访,深层链接,消除暗码和自动续订会话,运用户的日子更轻松。
  • 安全性:SAML依据强大的数字签名进行身份验证和完整性,是世界上最大,最具安全意识的企业所依赖的安全单点登录协议。(网页仿冒防备-假如您没有应用程序暗码,就不会被诈骗在虚假的登录页面上输入暗码
  • 速度:SAML速度很快。

总归,SAML 2.0协议是一种重要的安全规范,它能够协助企业完成跨域身份验证和单点登录,提高用户体会和安全性。

SAML开发实战攻略

建立SAML协议的SP服务供给者-服务

首要,咱们需求先建立出来一个具有支撑SAML协议的认证的SP(服务供给者)。运用Java工具包使咱们能够将Java应用程序转换为能够连接到IdP(身份供给者)的SP(服务供给者)

SP需求支撑功用和特性:

  • SSO(单点登录)和SLO(单点登出)
    • SSO:由SP建议
    • SLO:由IdP建议
  • 解析和分析断语Assert和nameId加密
  • 断语签名:signature
  • 音讯签名:AuthNRequest, LogoutRequest, LogoutResponses.
  • 装备断语Assert消费者服务(ACS) 端点.
  • 装备单点登出服务(SLS) 端点.
  • 发布SP metadata(能够签名)

建立SAML协议的IDP服务

咱们运用oneLogin供给的IDP服务来进行开发测验SAML协议,该服务需求注册开发者账户后获取。下面将介绍怎么搭建。

developers.onelogin.com/

【分布式技术专题】「单点登录技术架构」一文带领你好好对接对应的Okta单点登录实现接口服务的实现落地

当然你也能够经过其他第三方的IDP进行完成也可,例如Okta或者Azure服务都能够,此处暂时不做过多的赘余豁免。


开始对接完成SP服务供给者

Maven引进办法

<dependency>
    <groupId>com.onelogin</groupId>
    <artifactId>java-saml</artifactId>
    <version>2.5.0</version>
</dependency>

采用OneLogin完成Saml协议认证开发

装备对应的IDP身份的相关参数

默许需求在OneLogin的装备文件onelogin.saml.properties中装备(IDP)身份供给者参数:

SSO的符号特点 Settings的装备特点
Issuer URL onelogin.saml2.idp.entityid
SAML 2.0 Endpoint (HTTP) onelogin.saml2.idp.single_sign_on_service.url
SLO Endpoint (HTTP) onelogin.saml2.idp.single_logout_service.url
X.509 Certificate > View Details onelogin.saml2.idp.x509cert

主要面向的就是装备onelogin.saml.properties的“ idp”(身份供给者,以onelogin.saml2.idp开头的参数)部分。

装备对应的SP身份的相关参数

在OneLogin中定义(SP)服务供给商的参数

SSO的符号特点 Settings的装备特点
Audience onelogin.saml2.sp.entityid
Single Logout URL onelogin.saml2.sp.single_logout_service.url
Recipient onelogin.saml2.sp.assertion_consumer_service.url
ACS (Consumer) URL onelogin.saml2.sp.assertion_consumer_service.url
RelayState 参数能够不必填写 在建议sso代码处能够指定

IDP端的装备能够实时修正保存(在 正式项目环境中咱们一般 用metadata文件来进行装备交互)

布置运行SP项目

对于onelogin.saml2.sp.nameid格局,将unspecified更改为emailAddress.在正式项目中依据实际情况来进行装备,这里是现在是OneLogin运用的值。

onelogin.saml2.sp.entityid = http://localhost:8080/metadata.jsp
onelogin.saml2.sp.assertion_consumer_service.url = http://localhost:8080/acs.jsp
onelogin.saml2.sp.single_logout_service.url = http://localhost:8000/sls.jsp

保存装备后进入onelogin连接器的装备选项卡,然后将值从onelogin.saml.properties复制到“装备’’选项卡字段中,如下所示。

onelogin.saml.properties装备文件解读

SP端装备
# 协议装备 基本上默许 没有特别修正
# SP entityId
#  Identifier of the SP entity  (must be a URI)
onelogin.saml2.sp.entityid = http://localhost:8080/metadata.jsp
# SP 断语解析服务地址
# Specifies info about where and how the <AuthnResponse> message MUST be
#  returned to the requester, in this case our SP.
# URL Location where the <Response> from the IdP will be returned
onelogin.saml2.sp.assertion_consumer_service.url = http://localhost:8080/acs.jsp
# SAML protocol binding to be used when returning the <Response>
# 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
#单点登出 服务地址 主要是供给给 IDP端用于接纳登出呼应的
# Specifies info about where and how the <Logout Response> message MUST be
# returned to the requester, in this case our SP.
onelogin.saml2.sp.single_logout_service.url = http://localhost:8080/sls.jsp
# SAML protocol binding to be used when returning the <LogoutResponse> or sending the <LogoutRequest>
# 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
# nameID 格局 一般运用 unspecified 默许参数 
# 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:emailAddress
# sp端用于报文加密签名运用到的证书
# 这里咱们能够自签的x509格局证书 也能够运用pem格局经过 以下办法转换(我运用的是https证书 other下载而来的)
# 公钥转换 openssl x509 -in x509cert.pem -text -out Cert.pem
# 私钥转换 openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
# 证书张贴留意不要前后注释 并保持在一行上 证书解析对空格换行符灵敏
# 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
onelogin.saml2.sp.x509cert =
# Requires Format PKCS#8   BEGIN PRIVATE KEY	     
# If you have     PKCS#1    convert it by   openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
onelogin.saml2.sp.privatekey =

IDP端装备


# IDP  entityId
# Identifier of the IdP entity  (must be a URI)
onelogin.saml2.idp.entityid = https://app.onelogin.com/saml/metadata/2edb5038-be70-40f5-ad3b-2de9d00ab1a3
# SSO endpoint info of the IdP. (Authentication Request protocol)
# URL Target of the IdP where the SP will send the Authentication Request Message
onelogin.saml2.idp.single_sign_on_service.url = https://westinfosoft-dev.onelogin.com/trust/saml2/http-post/sso/2edb5038-be70-40f5-ad3b-2de9d00ab1a3
# SAML protocol binding to be used when returning the <Response>
# 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
onelogin.saml2.idp.single_logout_service.url =https://westinfosoft-dev.onelogin.com/trust/saml2/http-redirect/slo/1095020
# 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
onelogin.saml2.idp.single_logout_service.response.url =
# SAML protocol binding to be used when returning the <Response>
# 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
# 正式项目中 sp端都是从SP_metadata 中获取 的
# Public x509 certificate of the IdP
onelogin.saml2.idp.x509cert =
# 以下是 指纹模式的装备 不过官方不建议运用 应为hash碰撞的问题 
# 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 = 3E:3B:0D:FA:F2:80:B2:0E:95:46:36:07:9A:78:BD:04:CC:76:CE:A8
# onelogin.saml2.idp.certfingerprint_algorithm = sha1
# Security settings
#安全装备 在演示项目中运用不多 不过在正式环境中 需求留意开启对应的加密项
# Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
# will be encrypted.
onelogin.saml2.security.nameid_encrypted = false
#认证恳求的加密
# Indicates whether the <samlp:AuthnRequest> 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 <samlp:logoutRequest> messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutrequest_signed = false
#登出呼应的加密
# Indicates whether the <samlp:logoutResponse> messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutresponse_signed = false
# Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
# <samlp:LogoutResponse> elements received by this SP to be signed.
onelogin.saml2.security.want_messages_signed = false
# Indicates a requirement for the <saml:Assertion> 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_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

SP端代码

keyStores

Auth结构函数支撑从KeyStore读取SP公共证书/私钥的功用。必须为KeyStoreSettings对象供给KeyStore,别名和KeyEntry暗码。

String keyStoreFile = "oneloginTestKeystore.jks";
String alias = "keywithpassword";
String storePass = "changeit";
String keyPassword = "keypassword";
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyStoreFile), storePass.toCharArray());
KeyStoreSettings keyStoreSettings =  new keyStoreSettings(ks, alias, keyPassword);
Auth auth = new Auth(KeyStoreSettings keyStoreSetting);
动态装备

您能够从其他来源(例如文件,数据库或生成的值)加载值。SettingsBuilder类公开了fromValues(Map <String,Object> samlData)办法,该办法可让您动态构建设置,密钥字符串与特点文件中的相同。

Map<String, Object> samlData = new HashMap<>();
samlData.put("onelogin.saml2.sp.entityid", "http://localhost:8080/java-saml-tookit-jspsample/metadata.jsp");
samlData.put("onelogin.saml2.sp.assertion_consumer_service.url", new URL("http://localhost:8080/java-saml-tookit-jspsample/acs.jsp"));
samlData.put("onelogin.saml2.security.want_xml_validation",true);
samlData.put("onelogin.saml2.sp.x509cert", myX509CertInstance);
SettingsBuilder builder = new SettingsBuilder();
Saml2Settings settings = builder.fromValues(samlData).build();
//实例化您编写的Auth类
Auth auth = new Auth(settings, request, response);

建议单点登录

用于向IDP发送AuthNRequest

Auth auth = new Auth(request, response);
auth.login();

AuthNRequest将依据安全设置“ onelogin.saml2.security.authnrequest_signed”以签名或未签名的方式发送。然后,IdP将把SAML呼应返回给用户的客户端。然后,运用此信息将客户端转发到SP的特点消费者服务。咱们能够为登录函数设置一个“ returnTo” URL参数,并将其转换为“ RelayState”参数:

//指定登录成功后跳转的地址
String targetUrl = "https://github.com/onelogin/java-saml";
auth.login(targetUrl);

SP 端点

三个重要的端点,sp元数据(SP_metadata),ACS(断语解析服务),SLS(单点登出呼应解析服务)

SP Metadata

这段代码将依据设置文件中供给的信息供给SP的XML元数据文件。

Auth auth = new Auth();
Saml2Settings settings = auth.getSettings();
String metadata = settings.getSPMetadata();
List<String> errors = Saml2Settings.validateMetadata(metadata);
if (errors.isEmpty()) {
   out.println(metadata);
} else {
   response.setContentType("text/html; charset=UTF-8");
   for (String error : errors) {
       out.println("<p>"+error+"</p>");
   }
}

Attribute Consumer Service(ACS)

此代码处理IdP经过用户客户端转发到SP的SAML呼应。

Auth auth = new Auth(request, response);
//具体的 呼应断语解析
auth.processResponse();
if (!auth.isAuthenticated()) {
   out.println("Not authenticated");
}
List<String> 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<String, List<String>> 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<String> keys = attributes.keySet();
            for(String name :keys){
                out.println(name);
                List<String> values = attributes.get(name);
                for(String value :values) {
                    out.println(" - " + value);
                }
            }
        }
    }
}

在尝试获取特点之前,请查看用户是否已经过身份验证。假如用户未经过身份验证,则将返回一个空的Map。例如,假如咱们在auth.processResponse之前调用getAttributes,则getAttributes()将返回一个空Map。

Single Logout Service (SLS)

以下代码处理刊出恳求和刊出呼应。

Auth auth = new Auth(request, response);
auth.processSLO();
List<String> errors = auth.getErrors();
if (errors.isEmpty()) {
   out.println("Sucessfully logged out");
} else {
   for(String error : errors) {
      out.println(error);
   }
}

假如SLS端点收到刊出呼应,则该呼应将得到验证,并且HttpRequest的会话可能会关闭。假如SLS端点接纳到刊出恳求,则该恳求将得到验证,会话将关闭,并且刊出呼应将发送到IdP的SLS端点。假如咱们不期望该processSLO损坏会话,则将keepLo​​calSession参数作为true传递给processSLO办法。

2.建议单点登出

用于发送Logout Request到IdP

留意:此办法 是经过SP端建议的单点登出

Auth auth = new Auth(request, response);
String nameId = null;
if (session.getAttribute("nameId") != null) {
    nameId = session.getAttribute("nameId").toString();
}
String nameIdFormat = null;
if (session.getAttribute("nameIdFormat") != null) {
    nameIdFormat = session.getAttribute("nameIdFormat").toString();
}
String nameidNameQualifier = null;
if (session.getAttribute("nameidNameQualifier") != null) {
    nameIdFormat = session.getAttribute("nameidNameQualifier").toString();
}
String nameidSPNameQualifier = null;
if (session.getAttribute("nameidSPNameQualifier") != null) {
    nameidSPNameQualifier = session.getAttribute("nameidSPNameQualifier").toString();
}
String sessionIndex = null;
if (session.getAttribute("sessionIndex") != null) {
    sessionIndex = session.getAttribute("sessionIndex").toString();
}
auth.logout(null, nameId, sessionIndex, nameIdFormat);

将依据安全设置“ onelogin.saml2.security.logoutrequest_signed”以签名或未签名的方式发送刊出恳求。IdP将经过用户客户端将刊出呼应返回到SP的单一刊出服务。