SAML Java SSO 单点登录

简介

在企业应用中,用户需要进行多次登录操作才能访问不同的系统,这给用户带来了不便。而单点登录(Single Sign-On,简称SSO)技术的出现解决了这个问题,它可以让用户一次登录后,就可访问多个系统。SAML(Security Assertion Markup Language)是一种用于实现SSO的开放标准,它基于XML并使用安全令牌进行身份验证和授权。

本文将介绍如何使用Java实现SAML SSO单点登录,并提供相应的代码示例。

SAML 单点登录流程

SAML单点登录的流程如下:

  1. 用户访问应用系统A,并尚未登录。
  2. 应用系统A将用户重定向到身份提供者(Identity Provider,简称IdP)的登录页面。
  3. 用户在IdP的登录页面中输入用户名和密码进行身份验证。
  4. IdP验证用户身份后,生成SAML断言(Assertion),并将其加密后发送给应用系统A。
  5. 应用系统A通过解密和验证SAML断言,确认用户身份。
  6. 应用系统A可以根据用户的权限等信息,决定是否授权用户访问系统。
  7. 用户在应用系统A中进行操作,当访问其他应用系统时,无需重新登录。

SAML SSO Java 实现

下面通过一个简单的示例来演示如何使用Java实现SAML SSO单点登录。

首先,我们需要添加SAML依赖,可以使用Maven来管理依赖。在pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml</artifactId>
    <version>3.4.3</version>
</dependency>

<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-impl</artifactId>
    <version>3.4.3</version>
</dependency>

<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-profile-api-impl</artifactId>
    <version>3.4.3</version>
</dependency>

接下来,我们创建一个SAMLUtil工具类,用于生成SAML请求和解析SAML断言:

import org.opensaml.saml2.core.*;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.provider.FileBackedHTTPMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.security.x509.X509Credential;
import org.opensaml.xml.signature.*;
import org.opensaml.xml.signature.impl.SignatureBuilder;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Element;

import javax.xml.namespace.QName;
import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;

public class SAMLUtil {

    private static XMLObjectBuilderFactory builderFactory;

    static {
        try {
            builderFactory = Configuration.getBuilderFactory();
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
    }

    public static AuthnRequest createAuthnRequest(String issuer, String destination) {
        AuthnRequest authnRequest = (AuthnRequest) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME).buildObject(AuthnRequest.DEFAULT_ELEMENT_NAME);
        authnRequest.setID("_" + SAMLUtil.generateID());
        authnRequest.setDestination(destination);
        authnRequest.setIssuer(createIssuer(issuer));
        authnRequest.setIssueInstant(SAMLUtil.getCurrentTime());
        authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
        authnRequest.setAssertionConsumerServiceURL(destination);
        authnRequest.setVersion(SAMLVersion.VERSION_20);
        authnRequest.setForceAuthn(false);
        authnRequest.setIsPassive(false);
        return authnRequest;
    }

    private static Issuer createIssuer(String issuer) {
        Issuer issuerElement = (Issuer) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME).buildObject(Issuer.DEFAULT_ELEMENT_NAME);
        issuerElement.setValue(issuer);
        return issuerElement;
    }

    public static String generateID() {
        return "_" + Long.toHexString