SSO单点登录,实现对接SAML 协议对接IDP, 实现可拆解的SP服务

文章目录

  • 1.概述
  • 2. SAML协议介绍
    • 2.1 相关参考资料
    • 2.2 主要的概念
    • 2.3 SP发起单点登录
    • 2.4 IDP发起SSO单点登录
  • 3. 系统接口设计文档
    • 3.1. 相关接口
      • 3.1.1 获取某个登录页面发起SSO 仅供测试使用,(它是一个SP发起可操作的入口)
      • 3.1.2 生成SAML 请求接口, (当用户未登录的状态下, 任何一个资源可以重定向到该接口完成SAML认证)
      • 3.1.3 ACS 回调接口, 当认证成功后跳转到系统处理内部登录的接口
      • 3.1.4 saml 退出登录接口
    • 3.2. 代码示例
      • 3.2.1 导包
      • 3.2.2 配置
      • 3.2.3 微软IDP 配置 SP配置
      • 3.2.4 示例代码
  • 4.附件资料

1.概述

​ 主流的单点登录有 CAS ,Auth2.0, SAML 等,用户可以登录一次就直接使用多个SP(Service Provider 业务提供方)的服务,免去了每个应用都要登录的烦恼,

CAS ,Auth2.0 代码都有不做赘述, 本次讲微软的 SAML协议单点登录, 传统的单点登录 都是使用Filter 实现的, 底层都做了分装 很难去拆开, 如 spring-security 框架 就有对 SAML 的集成, 但 我们不能使用这种方式, 对接一个单点登录我们不可能引入一个框架, 并且无法实现 一个系统为多个单点登录提供服务(一个SP无法为多个IDP提供服务);

2. SAML协议介绍

2.1 相关参考资料

参考资料: https://docs.microsoft.com/zh-cn/azure/active-directory/saas-apps/saml-toolkit-tutorial

​ https://help.aliyun.com/document_detail/160431.html?scm=20140722.184.2.173#h2-78q-k9u-yay

2.2 主要的概念

  • IDP (Identity Provider身份提供方,及 微软的身份认证服务)

  • SP(Service Provider 业务提供方, 自己开发的系统系统…)

  • 浏览器(用户操作的浏览器)

2.3 SP发起单点登录

SSO单点登录,实现对接SAML 协议对接IDP, 实现可拆解的SP服务_第1张图片

具体流程:

  1. 用户访问SP一个受保护的资源路径 /user/getUserInfo

  2. SP 接收到用户请求后判断用户是否登录, 如果未登录泽生成 SAML请求(走 sso登录流程), 如果登录过直接访问

  3. SP生成 SAML请求, 用base64编码 发送给 浏览器 302 重定向到IDP进行登录

    -- 这是一个完整的 sp生成给 idp登录的 saml request , 其中 SAMLRequest 是saml请求
    https://login.microsoftonline.com/***/saml2?SAMLRequest=****
    
    解码的时候先使用 URL解码后再使用base64 解码 结果
    <samlp:AuthnRequest
        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="****" Version="2.0" IssueInstant="2022-09-06T07:21:14Z" Destination="https://login.microsoftonline.com/***/saml2" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://localhost:8080/loginSuccess">
        <saml:Issuer>urn:***:cloudcomputingsaml:Issuer>
    samlp:AuthnRequest>
    
    
  4. IDP接收到SAML 请求解析请求并指引用户登录, 登录成功后 IDP向浏览器 返回 SAMLResponse

      <form method="post" action="https://sp.example.com/SAML2/SSO/POST" ...>
        <input type="hidden" name="SAMLResponse" value="response" />
        <input type="hidden" name="RelayState" value="opaque" />
        ...
        <input type="submit" value="Submit" />
      form>
    
  5. 表单中的 SAMLResponse 为base64编码的 saml服务器返回的结果 示例

    <samlp:Response ID="_5a61d58d-b36d-4bdd-b92b-5c88663fcd5b" Version="2.0" IssueInstant="2022-09-06T08:20:27.536Z" Destination="http://localhost:8080/loginSuccess" InResponseTo="ONELOGIN_95b541d2-7c89-4733-925e-72d40e12dfa4"
        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
        <Issuer
            xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://sts.windows.net/***/
        Issuer>
        <samlp:Status>
            <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
        samlp:Status>
        <Assertion ID="_f6f587cb-9449-4d21-9325-c0fed47bfb00" IssueInstant="2022-09-06T08:20:27.521Z" Version="2.0"
            xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
            <Issuer>https://sts.windows.net/**/Issuer>
            <Signature
                xmlns="http://www.w3.org/2000/09/xmldsig#">
                <SignedInfo>
                    <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
                    <Reference URI="#_f6f587cb-9449-4d21-9325-c0fed47bfb00">
                        <Transforms>
                            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        Transforms>
                        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                        <DigestValue>**=DigestValue>
                    Reference>
                SignedInfo>
                <SignatureValue>RtA/UEFZ/Spvz/2xWyIG97j3/u9d/**/Bg==SignatureValue>
                <KeyInfo>
                    <X509Data>
                        <X509Certificate>***7X509Certificate>
                    X509Data>
                KeyInfo>
            Signature>
            <Subject>
                <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">abcc@***.comNameID>
                <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                    <SubjectConfirmationData InResponseTo="ONELOGIN_95b541d2-7c89-4733-925e-72d40e12dfa4" NotOnOrAfter="2022-09-06T09:20:27.427Z" Recipient="http://localhost:8080/loginSuccess"/>
                SubjectConfirmation>
            Subject>
            <Conditions NotBefore="2022-09-06T08:15:27.427Z" NotOnOrAfter="2022-09-06T09:20:27.427Z">
                <AudienceRestriction>
                    <Audience>urn:***:cloudcomputingAudience>
                AudienceRestriction>
            Conditions>
            <AttributeStatement>
                <Attribute Name="http://schemas.microsoft.com/identity/claims/tenantid">
                    <AttributeValue>***AttributeValue>
                Attribute>
                <Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier">
                    <AttributeValue>***AttributeValue>
                Attribute>
                <Attribute Name="http://schemas.microsoft.com/identity/claims/displayname">
                    <AttributeValue>aAttributeValue>
                Attribute>
                <Attribute Name="http://schemas.microsoft.com/identity/claims/identityprovider">
                    <AttributeValue>https://sts.windows.net/**/AttributeValue>
                Attribute>
                <Attribute Name="http://schemas.microsoft.com/claims/authnmethodsreferences">
                    <AttributeValue>http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/passwordAttributeValue>
                Attribute>
                <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
                    <AttributeValue>bAttributeValue>
                Attribute>
                <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname">
                    <AttributeValue>cAttributeValue>
                Attribute>
                <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
                    <AttributeValue>abcc@***.onmicrosoft.comAttributeValue>
                Attribute>
            AttributeStatement>
            <AuthnStatement AuthnInstant="2022-09-06T02:31:48.985Z" SessionIndex="_f6f587cb-9449-4d21-9325-c0fed47bfb00">
                <AuthnContext>
                    <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordAuthnContextClassRef>
                AuthnContext>
            AuthnStatement>
        Assertion>
    samlp:Response>
    
  6. 浏览器将IDP返回的 SAML响应转发给 SP的 ACS( assertion consumer service) 地址处理

  7. SP接收到返回的SAML 返回结果后 解码, 并按照自己系统的方式登录, 然后返回给客户之前访问还没有权限的资源列表

2.4 IDP发起SSO单点登录

SSO单点登录,实现对接SAML 协议对接IDP, 实现可拆解的SP服务_第2张图片

原理差不多理解了第一种这个就简单了 不在赘述

3. 系统接口设计文档

3.1. 相关接口

3.1.1 获取某个登录页面发起SSO 仅供测试使用,(它是一个SP发起可操作的入口)

/user/sso/saml/getTestSsoPage

3.1.2 生成SAML 请求接口, (当用户未登录的状态下, 任何一个资源可以重定向到该接口完成SAML认证)

/user/sso/saml/generateSamlRequest

请求参数
callbackAddress  回调接口, 必须是绝对路径, 选填, 当登录成功后会访问这个路径, 如果不填写此参数 登录成功后 返回用户浏览器一个固定的默认页面

3.1.3 ACS 回调接口, 当认证成功后跳转到系统处理内部登录的接口

/user/sso/saml/SSOCallback

由浏览器自动发起调用, 解析saml请求
创建用户并完成本系统的登录
登录成功后如果有回调地址泽调用回调地址

3.1.4 saml 退出登录接口

退出登录可以使用我们之前的退出登录但是要求绝对路径配置在 idp服务

3.2. 代码示例

3.2.1 导包

我们使用 onelogin 框架进行生成saml 请求 和解析结果

<dependency>
    <groupId>com.onelogingroupId>
    <artifactId>java-samlartifactId>
    <version>2.9.0version>
dependency>

3.2.2 配置


#  Service Provider Data that we are deploying
#  Identifier of the SP entity  (must be a URI)
# sp 唯一标识 在idp 页面查找
onelogin.saml2.sp.entityid = sp:my:system

# 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 = http://localhost:8080/user/sso/saml/SSOCallback


# Specifies info about Logout service
# URL Location where the  from the IdP will be returned or where to send the 
onelogin.saml2.sp.single_logout_service.url = https://testtt/user/sso/saml/SSOCallback


# sp 发起请求加密证书 可选 是否开启请求加密, 当开启加密时 saml 请求会多两个参数 Signature 和 SigAlg
onelogin.saml2.security.authnrequest_signed = false
# 加密算法
onelogin.saml2.security.signature_algorithm = http://www.w3.org/2000/09/xmldsig#rsa-sha1
# 秘钥
onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----\nMIICQjCCAaugAwIBAgIBADANBgkqhkiG9w0BAQ0FADA+MQswCQYDVQQGEwJ1czELMAkGA1UECAwCQ0ExDjAMBgNVBAoMBUtZVk9TMRIwEAYDVQQDDAlLWVZPU1RFU1QwHhcNMTYwOTMwMDkxNTE1WhcNMTcwOTMwMDkxNTE1WjA+MQswCQYDVQQGEwJ1czELMAkGA1UECAwCQ0ExDjAMBgNVBAoMBUtZVk9TMRIwEAYDVQQDDAlLWVZPU1RFU1QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMgHww0rFliHv/8yAfeWTgz3OIDbYGj13AWKxhKHHElmzViYYj6AcGX7iF406RVAFYDPabquY0Axopvx6oSQzelt7eolRp8prCnJpQw0G99Bet6ke/qrRC4kj7FieEOF8PTVju9qfsIDJ12iLYOz6V2L/5KiXj1NKAeUipeU7pMjAgMBAAGjUDBOMB0GA1UdDgQWBBTV9oLgjYy9I7nnx3AgdER4HXJ7bjAfBgNVHSMEGDAWgBTV9oLgjYy9I7nnx3AgdER4HXJ7bjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAIqGGqznJxUtVvrVLigBOWRS8ZeIgIOoTPyS/8+N/eRNEsgg/fVinfHddRRl9aO3x1xxeYqydJX/LM7C8CcY/XFMBJK3BNBV9+RIxsFwSruGIt4Zlj7mjiNl0AAUCnI9yCDX3YMVQkgobEhgOFKVNvFJ8N2LJYinfV8V54nqh8/H\n-----END CERTIFICATE-----
onelogin.saml2.sp.privatekey = -----BEGIN PRIVATE KEY-----MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMgHww0rFliHv/8yAfeWTgz3OIDbYGj13AWKxhKHHElmzViYYj6AcGX7iF406RVAFYDPabquY0Axopvx6oSQzelt7eolRp8prCnJpQw0G99Bet6ke/qrRC4kj7FieEOF8PTVju9qfsIDJ12iLYOz6V2L/5KiXj1NKAeUipeU7pMjAgMBAAECgYAB9aGBhYSIhXvqAhFm3YASPHG/QWbmYFIxc7SRBeXPCkTEs2ly90silJ43XglSffg39NAyVfb+7/2JzYrglZKOzGffqLYdy8xJ5NqDrXrGO8JiS0F83e4zE1DptHBy4p8G38lkJN6iz0Fcpq4quOy9ChwoTW1x6ncLQ4ejBOqSwQJBAOOOxPE+nVapxnjo1DBYerUVlOHYL2RCluwB1SB6Ry5m90rYqm0j6kHqSj7am75dwj9/ZmS15Em7nepcSO9/LpMCQQDhCDDuvNbLAB5GNG4/ZWa6Z6jOsIFXVCFiB3jT3/MkiJi7QRBACNPPhnvYOzgSeHsPaa2aROvKau0GVSqJr1MxAkB5q19v4du8d6AwC8VQaC6L3hMxwiZsxBHv9HbhG6Atlk5IzZoqKtbZEY1LGBXH7lerHdJArOR37AHeSiORMn5TAkBZtvpLM94ucI2hT6XkgHjEOC+Et7nZJyFoA7KYReCZ4BuEDBx+awaG5gbZ3kIsmvv02RztNC0NNjPpImsjGVMBAkA09SgzzFkzhyrQWaVaYTiQRklNx5kM2flroxfJaH8xN7R7K3NSbUOU/88VCOsrHqu+AR2WzB7cHIXpya4Zq/Ef-----END PRIVATE KEY-----
onelogin.saml2.sp.x509certNew=




# Identity Provider Data that we want connect with our SP
# Identifier of the IdP entity  (must be a URI)
# idp 配置文件 地址
onelogin.saml2.idp.entityid = https://sts.windows.net/***/

# SSO endpoint info of the IdP. (Authentication Request protocol)
# URL Target of the IdP where the SP will send the Authentication Request Message
# idp 登录地址
onelogin.saml2.idp.single_sign_on_service.url = https://login.microsoftonline.com/***/saml2

# SLO endpoint info of the IdP.
# URL Location of the IdP where the SP will send the SLO Request
# idp 退出登录地址
onelogin.saml2.idp.single_logout_service.url = https://login.microsoftonline.com/***/saml2

# Public x509 certificate of the IdP
# IDP 证书在 idp页面下载
onelogin.saml2.idp.x509cert = -----BEGIN CERTIFICATE-----\nMIIC8DCCAdigAwIBAgIQOv78vXFsy7VDSxCzswFDfzANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0yMjA5MDExMDEwMTZaFw0yNTA5MDExMDEwMTVaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQgU1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAttc0hL2JyfT1EvktPQjtDHmlikmuMTtXCrsJtYV3fK2xTwrP/KEBjdzAYbQQ5AYCJm61+eIAjKZH+hV7waOw0hL6ohE9/MHuZeuuovjWREsb48/BV2IKFV1nJ3Nsu7anDdJnJbpQ8x9jnuHx9OLfnH+3vC6x6xNzBHuKOb1ndXmE/SJshWh4GDmAE5fct1lrcR+jxGacxiMaWBVxxv2rBqJrsUKJmwR23MvAvUKS2mm6J/dZeUvW5LEI2S/k8QtJjIfOaf+yzqy2oUbex7We4vODCuZCtKpBLufTe/m8xY5tKbyIkpTccy42M9oQP1tOImf7bSAbGNc3pv5HpHVsMQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBiYdc/oxKfa1zrWzrA3Uu4hM4HfV94x/NT95byf5NUSR3nBmf2/87cIRxtHD0+CA4XB6vSGEsFzIM0AfUDV385LQ0f8OnW774aNxvtKy9HMNXWhddaJ9N/xSOAtBPo/eh+sSbzAx0j0MOe4KnmBNBq9Gh8ZbCJzhHgzvuleqymlUVmtHEDB8LOJwHcx+tlnLVeqlNRCRCH6ihROBssnc/lyj9w1metbvhXGUJLUMbiGHLsnpEX0xSh2ZIf8QBNo54P9QXBj8Pwfh8I99j+gWsnDlndjRlAiPe8qvMP9LikdZQj4WpmNbGOC67lQN6d++S0RgvOXRGxHifHAP609eR7\n-----END CERTIFICATE-----

3.2.3 微软IDP 配置 SP配置

附件资料参考 "配置AAD指南.pdf" 文件

基本 SAML 配置
    标识符(实体 ID)	sp:my:system
    回复 URL (断言使用者服务 URL)	https://www.my.com
    登录 URL	https://testtt/user/sso/saml/SSOCallback


IDP 给我们的信息
    登录 URL 
    Azure AD 标识符
    注销 URL
    
    应用联合元数据 URL
    证书(base64)
    证书(Raw)
    联合元数据 XML
    
上文中所涉及的域名全部改为线上域名 必须https

3.2.4 示例代码


/**
 * @Description:
 * @Author: sun yalong
 * @Version: 1.0
 */
@RequestMapping("/user/sso/saml")
@RestController
public class SAMLSSOController {

    private static final String PROTECTED_RESOURCES_URL = "PROTECTED_RESOURCES_URL";
    private static final String CALLBACK_ADDRESS = "callbackAddress";

    /**
     * 返回saml 登录请求的页面
     *
     * @param request
     * @return
     */
    @GetMapping("getTestSsoPage")
    public String loginPage(HttpServletRequest request){
        return "点击生成 SAML 登录请求设置";
    }

    /**
     * 生成请求并重定向到 sso 单点登录
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping({"generateSamlRequest"})
    public void genSamlRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 将受保护的资源url 先保存起来等登录成功后直接重定向, 要求绝对路径
        String protectedResourcesUrl = request.getParameter(CALLBACK_ADDRESS);
        if (StringUtils.isNotBlank(protectedResourcesUrl)) {
            request.getSession().setAttribute(PROTECTED_RESOURCES_URL,protectedResourcesUrl);
        }

        // 生成saml 请求 并完成重定向至 idp服务器
        String url = getUrl(request, response);
        response.setStatus(302);
        response.setHeader("Location",url);
    }


    /**
     * 登录成功回调函数
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping({"SSOCallback"})
    public String loginSuccess(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Auth auth = new Auth("config/config.min.properties", request, response);
        //  Auth(Saml2Settings settings, HttpServletRequest request, HttpServletResponse response)
        auth.processResponse();
        String lastResponseXML = auth.getLastResponseXML();
        System.out.println(lastResponseXML);

        // 解析 idp返回的xml
        // 判断系统中是否有这个用户 无泽创建
        // 登录本系统 参考 com.everhomes.user.service.ShenZhenUniversityServiceImpl#authorize
        // 重定向到用户之前访问的资源
        String protectedResourcesUrl = (String) request.getSession().getAttribute(PROTECTED_RESOURCES_URL);
        if(StringUtils.isNotBlank(protectedResourcesUrl)){
            request.getSession().removeAttribute(PROTECTED_RESOURCES_URL);
            response.setStatus(302);
            response.setHeader("Location",protectedResourcesUrl);
            return null;
        }
        return lastResponseXML;
    }




    public String getUrl(HttpServletRequest request, HttpServletResponse response)throws Exception{
        Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min.properties").build();
//        Map params = new HashMap<>();
//        Saml2Settings settings = new SettingsBuilder().fromValues(params).build();


        Auth auth = new Auth(settings, request, response);
        // 获取IDP和 重定向内容
        String target = auth.login("", new AuthnRequestParams(false, false, false), true);
        // 生成xml
//         String authNRequestStr = getSAMLRequestFromURL(target);
        return target;
    }

    /**
     * 获取 saml 登录请求的 url
     *
     * @param url
     * @return
     * @throws URISyntaxException
     * @throws UnsupportedEncodingException
     */
    private String getSAMLRequestFromURL(String url) throws URISyntaxException, UnsupportedEncodingException {
        String xml = "";
        URI uri = new URI(url);
        String query = uri.getQuery();
        String[] pairs = query.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf("=");
            if (pair.substring(0, idx).equals("SAMLRequest")) {
                xml = Util.base64decodedInflated(pair.substring(idx + 1));
            }
        }
        return xml;
    }



}

4.附件资料

onelogin: https://github.com/onelogin/java-saml/tags

https://help.aliyun.com/document_detail/160431.html?scm=20140722.184.2.173#h2-78q-k9u-yay

你可能感兴趣的:(microsoft)