SAML认证demo示例

工具使用示例

java版实现

一、工具使用示例

因为在SAML中, sp 与 idp 之间交互数据都是通过浏览器实现的,借助SAML工具可以较为方便的生成、查看参数。 SAML工具, 包含Base64编码、Deflate+Base64、url编码、签名、加密解密等认证过程中可能会使用到的工具。

1.SAMLRequest编码

首先生成自己的SAMLRequest参数,根据请求方式选择Base64(post)或Deflate+Base64(get)进行编码。

1.png

(1) get请求

Deflate+Base64生成编码后,需要再进行url编码,最后通过拼接url访问。示例:

http://sso.xxx.edu.cn/idp/profile/SAML2/Redirect/SSO?SAMLRequest=fZFfa8IwFMXfBb9DyHvb%2FGklBqt0G2OCg6F1D3uLMZ0Fm3S5qezjL%2BgcwsC3y73nwDm%2FO1t8d0d0Mh5aZ0tMU4KRsdrtW%2FtZ4m39nAi8mI9HM1DdsZfVEA52bb4GAwFFpwV5PpR48FY6BS1IqzoDMmi5qV5XkqVE9t4Fp90R31juOxSA8SFGwqi6jo%2FOwtAZvzH%2B1GqzXa9KPB4dQuhlltEpS%2BlEpEWe5kIKIlgGfaaisVE6YISWTyVWNNcFMVwoUuwbzkjDG0F2VHCV7wpN91xMCzMxHKMlwGCWFoKyocSMMJpQljBR00IyIbn4wOjtt9ZDay%2B47jXaXUQgX%2Br6Lan%2Bgr1f0UcRjqARQmfW8pzAz0MkPctuN%2FEZ2f9vzH8A

(2) post请求

生成base64的编码后,放在请求参数中通过XHTML表单请求。示例:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>POST data</title>
    <script src="/simplesaml/resources/post.js"></script>
    <link type="text/css" rel="stylesheet" href="/simplesaml/resources/post.css"/>
</head>
<body onload="document.forms[0].submit()">

<noscript>
    <p><strong>Note:</strong>
        Since your browser does not support JavaScript,
        you must press the button below once to proceed.</p>
</noscript>

<form method="post"
      action="http://localhost/idp/profile/SAML2/POST/SSO">
    <input type="hidden" name="SAMLRequest"
           value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBBc3NlcnRpb25Db25zdW1lclNlcnZpY2VVUkw9Imh0dHBzOi8vc3B0ZXN0LmlhbXNob3djYXNlLmNvbS9hY3MiIERlc3RpbmF0aW9uPSJodHRwczovL2lkMDgucmdoYWxsLmNvbS5jbi9pZHAvcHJvZmlsZS9TQU1MMi9QT1NUL1NTTyIgSUQ9ImExNGM1MGUzOGEwNWRmMzIwZjNmODBiMTgzYTRiNWMxZDM4OTVlNmUzIiBJc3N1ZUluc3RhbnQ9IjIwMjEtMTItMjhUMTU6Mjg6MzhaIiBQcm90b2NvbEJpbmRpbmc9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpiaW5kaW5nczpIVFRQLVBPU1QiIFZlcnNpb249IjIuMCI+CiAgIDxzYW1sOklzc3Vlcj5JQU1TaG93Y2FzZTwvc2FtbDpJc3N1ZXI+Cjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg=="/>
    <noscript>
        <button type="submit" class="btn">Submit</button>
    </noscript>
</form>

</body>
</html>

2.SAMLResponse解码

返回的SAMLResponse是Base64编码后的结果,可通过工具解码查看。

3.签名

如果想要SAMLRequest带签名,使用如下工具,之后步骤同上。

2.png

4.解密

3.png

SAML对接配置勾选加密后,返回的信息需要对应的私钥解密才能查看。解密工具使用如下:

4.png

二、java版实现

因为其他模式都可以通过工具简化操作,所以没有具体的实现,该部分为 HTTP-Artifact 示例。

1.回调接口

@GetMapping("/sp/artifact")
public String test(final HttpServletRequest request, final HttpServletResponse response) {

    Artifact artifact = buildArtifactFromRequest(request);
    log.info("create artifact: [{}]", artifact.getArtifact());

    ArtifactResolve artifactResolve = buildArtifactResolve(artifact);
    log.info("create artifactResolve");

    ArtifactResponse artifactResponse = sendAndReceiveArtifactResolve(artifactResolve, response);
    log.info("ArtifactResponse received");
    log.info("ArtifactResponse: ");
    OpenSAMLUtils.logSAMLObject(artifactResponse);

    return artifact.getArtifact();
}

2.生成artifact

/**
 * SAML消息中有敏感信息
*/
private Artifact buildArtifactFromRequest(final HttpServletRequest req) {
    Artifact artifact = OpenSAMLUtils.buildSAMLObject(Artifact.class);
    artifact.setArtifact(req.getParameter("SAMLart"));
    return artifact;
}

3.生成ArtifactResolve

private ArtifactResolve buildArtifactResolve(final Artifact artifact) {
    ArtifactResolve artifactResolve = OpenSAMLUtils.buildSAMLObject(ArtifactResolve.class);
    //Issuer:发送方的身份表示,同AuthnRequest中的issuer;
    Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class);
    issuer.setValue(SPConstants.SP_ENTITY_ID);
    artifactResolve.setIssuer(issuer);

    //Time of the Request
    artifactResolve.setIssueInstant(new DateTime());
    //ID of the request:
    artifactResolve.setID(OpenSAMLUtils.generateSecureRandomId());
    //destination URL
    //  artifactResolve.setDestination(IDPConstants.ARTIFACT_RESOLUTION_SERVICE);

    artifactResolve.setArtifact(artifact);

    return artifactResolve;
}

4.发送 ArtifactResolve 和获取 ArtifactResponse

/**
 * 使用SOAP协议发送 ArtifactResolve
 */
private ArtifactResponse sendAndReceiveArtifactResolve(final ArtifactResolve artifactResolve, HttpServletResponse servletResponse) {
    try {

        MessageContext<ArtifactResolve> contextout = new MessageContext<ArtifactResolve>();

        contextout.setMessage(artifactResolve);
        //加入数据签名以增强安全性
        SignatureSigningParameters signatureSigningParameters = new SignatureSigningParameters();
        signatureSigningParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
        signatureSigningParameters.setSigningCredential(no.steras.opensamlbook.sp.SPCredentials.getCredential());
        signatureSigningParameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

        SecurityParametersContext securityParametersContext = contextout.getSubcontext(SecurityParametersContext.class, true);
        if (securityParametersContext != null) {
            securityParametersContext.setSignatureSigningParameters(signatureSigningParameters);
        }
        log.info("artifactResolve signature signing");

        //创建InOutOperationContext来处理输入输出的信息
        InOutOperationContext<ArtifactResponse, ArtifactResolve> context = new ProfileRequestContext<ArtifactResponse, ArtifactResolve>();
        context.setOutboundMessageContext(contextout);


        //为了能发送SOAP消息,还需要设置SOAP Client。
        // 这个Client将会调用消息的处理器,编码器以及解码等来传送消息
        AbstractPipelineHttpSOAPClient<SAMLObject, SAMLObject> soapClient = new AbstractPipelineHttpSOAPClient<SAMLObject, SAMLObject>() {
            @Nonnull
            protected HttpClientMessagePipeline newPipeline() throws SOAPException {
                //创建输入输出用的编码器和解码器
                HttpClientRequestSOAP11Encoder encoder = new HttpClientRequestSOAP11Encoder();
                HttpClientResponseSOAP11Decoder decoder = new HttpClientResponseSOAP11Decoder();
                //创建管道
                BasicHttpClientMessagePipeline pipeline = new BasicHttpClientMessagePipeline(
                        encoder,
                        decoder
                );
                //为输出的内容签名
                pipeline.setOutboundPayloadHandler(new SAMLOutboundProtocolMessageSigningHandler());
                return pipeline;
            }};

        // HTTP帮助SOAPClient编码和解码
        HttpClientBuilder clientBuilder = new HttpClientBuilder();

        soapClient.setHttpClient(clientBuilder.buildClient());
        soapClient.send(IDPConstants.ARTIFACT_RESOLUTION_SERVICE, context);
        DateTime time = new DateTime();
        log.info("time: {}", time);
        log.info("final artifactResolve:");
        OpenSAMLUtils.logSAMLObject(context.getOutboundMessageContext().getMessage());
        log.info("send artifactResolve");

        return context.getInboundMessageContext().getMessage();
    } catch (SecurityException e) {
        throw new RuntimeException(e);
    } catch (ComponentInitializationException e) {
        throw new RuntimeException(e);
    } catch (MessageEncodingException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

}

5.OpenSAMLUtils

public class OpenSAMLUtils {
    private static Logger logger = LoggerFactory.getLogger(OpenSAMLUtils.class);
    private static RandomIdentifierGenerationStrategy secureRandomIdGenerator;

    static {
        secureRandomIdGenerator = new RandomIdentifierGenerationStrategy();

    }

    public static <T> T buildSAMLObject(final Class<T> clazz) {
        T object = null;
        try {
            XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
            QName defaultElementName = (QName)clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
            object = (T)builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName);
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Could not create SAML object");
        } catch (NoSuchFieldException e) {
            throw new IllegalArgumentException("Could not create SAML object");
        }

        return object;
    }

    public static String generateSecureRandomId() {
        return secureRandomIdGenerator.generateIdentifier();
    }

    public static void logSAMLObject(final XMLObject object) {
        Element element = null;

        if (object instanceof SignableSAMLObject && ((SignableSAMLObject)object).isSigned() && object.getDOM() != null) {
            element = object.getDOM();
        } else {
            try {
                Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object);
                out.marshall(object);
                element = object.getDOM();

            } catch (MarshallingException e) {
                logger.error(e.getMessage(), e);
            }
        }

        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            StreamResult result = new StreamResult(new StringWriter());
            DOMSource source = new DOMSource(element);

            transformer.transform(source, result);
            String xmlString = result.getWriter().toString();

            logger.info(xmlString);
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
    }
}

6.证书获取

public class SPCredentials {
    private static final String KEY_STORE_PASSWORD = "SPKeystore";
    private static final String KEY_STORE_ENTRY_PASSWORD = "SPKeystore";
    private static final String KEY_STORE_PATH = "/SPKeystore.jks";
    private static final String KEY_ENTRY_ID = "SPKeystore";

    private static final Credential credential;

    private static FilesystemMetadataProvider spMetaDataProvider;

    // KeyStoreCredentialResolver
    static {
        try {
            KeyStore keystore = readKeystoreFromFile(KEY_STORE_PATH, KEY_STORE_PASSWORD);
            Map<String, String> passwordMap = new HashMap<String, String>();
            passwordMap.put(KEY_ENTRY_ID, KEY_STORE_ENTRY_PASSWORD);
            KeyStoreCredentialResolver resolver = new KeyStoreCredentialResolver(keystore, passwordMap);

            Criterion criterion = new EntityIdCriterion(KEY_ENTRY_ID);
            CriteriaSet criteriaSet = new CriteriaSet();
            criteriaSet.add(criterion);

            credential = resolver.resolveSingle(criteriaSet);

        } catch (ResolverException e) {
            throw new RuntimeException("Something went wrong reading credentials", e);
        }
    }

    private static KeyStore readKeystoreFromFile(String pathToKeyStore, String keyStorePassword) {
        try {
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream inputStream = SPCredentials.class.getResourceAsStream(pathToKeyStore);
            keystore.load(inputStream, keyStorePassword.toCharArray());
            inputStream.close();
            return keystore;
        } catch (Exception e) {
            throw new RuntimeException("Something went wrong reading keystore", e);
        }
    }

    public static Credential getCredential() {
        return credential;
    }


}
©2020 锐捷网络股份有限公司 all right reserved,powered by Gitbook该文章修订时间: 2025-02-18 14:26:09

results matching ""

    No results matching ""