OpenSAML 使用引導(dǎo) II : Service Provider 的實(shí)現(xiàn)之AuthnRequest

前文OpenSAML 使用引導(dǎo) I : 簡介介紹了OpenSAML的基礎(chǔ)概況, 本文將從Service Provider(SP)角度出發(fā),講解如何使用OpenSAML如申請身份鑒別請求(AuthnRequest),并從IDP出得到斷言的引用標(biāo)識——SAML Artifact

相關(guān)閱讀SAML2.0入門指南,
源碼地址:https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git

1、從OpenSAML的角度再看身份鑒別過程

SAML

1. 用戶嘗試獲取訪問權(quán)限

用戶對SP中的資源進(jìn)行請求。這一步中,SP會做出判斷是否要需要鑒別用戶身份。一般而言,如果當(dāng)前用戶在SP上不存在已經(jīng)被認(rèn)證的session信息,就需要對用戶提出身份鑒別請求。

2. 用戶被重定向到IDP

如果需要對用戶進(jìn)行身份鑒別,SP將會創(chuàng)建AuthnRequest對象指明要用戶要如何才能被鑒別。這個(gè)AuthnRequest將會被以URL參數(shù)的形式編碼到HTTP請求中,然后通過瀏覽器重定向到IDP。

3. 用戶身份別鑒別

IDP解碼獲得AuthnRequest并根據(jù)其中的要求來鑒別用戶身份。

4. 已鑒別的用戶被發(fā)送回SP

如果用戶通過身份鑒別,IDP會將認(rèn)證信息和一個(gè)標(biāo)識聯(lián)系起來,這個(gè)標(biāo)識被稱為SAML制品(SAML Artifact,簡稱制品)。這個(gè)制品也是以URL參數(shù)的形式加入到HTTP請求中,然后重定向發(fā)回SP。

5. SP請求認(rèn)證信息

SP創(chuàng)建ArtifactResolve對象,并將Artifact包含在其中。這個(gè)ArtifactResolve對象通過SOAP請求發(fā)送到IDP。

6. IDP響應(yīng)請求返回認(rèn)證信息

IDP接受到ArtifactResolve并將Artifact抽離出來。IDP通過ArtifactResponse響應(yīng)SOAP請求,ArtifactResponse中包含認(rèn)證信息,以SAML斷言格式包含在其中。

2. 第一步:用戶攔截

用戶攔截

這里討論鑒別過程的第一步。實(shí)際上,用戶攔截并不是認(rèn)證過程的一部分,但是確實(shí)這一步卻決定著鑒別過程是否會發(fā)生。

用戶的交互以攔截非認(rèn)證的用戶去嘗試獲取SP資源時(shí)開始。對于Java Web應(yīng)用而言,Servlet過濾器是一個(gè)很好的選擇去做這樣的攔截。過濾器檢查是否當(dāng)前用戶已被認(rèn)證,如果已被驗(yàn)證則允許訪問,反之要啟動身份驗(yàn)證流程。

public class AccessFilter implements Filter {
    private static Logger logger = LoggerFactory
.getLogger(AccessFilter.class);
    @Override
    public void init(FilterConfig filterConfig) throws ServletException{
        JavaCryptoValidationInitializer javaCryptoValidationInitializer = 
            new JavaCryptoValidationInitializer();
        try {       
            javaCryptoValidationInitializer.init();
        } catch (InitializationException e) {
            e.printStackTrace();
        } 
        try {
            InitializationService.initialize();
        } catch (InitializationException e) {
            new RuntimeException("Initialization failed");
    }

    public void doFilter(
        ServletRequest request,
        ServletResponse response, 
        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest =
            (HttpServletRequest) request;
        HttpServletResponse httpServletResponse =
            (HttpServletResponse) response;
        if (httpServletRequest.getSession().getAttribute(
        SPConstants.AUTHENTICATED_SESSION_ATTRIBUTE) != null) {
            chain.doFilter(request, response);
        } else {
        //將本來要訪問的目標(biāo)路徑保存到Session
        setGotoURLOnSession(httpServletRequest);
        redirectUserForAuthentication(httpServletResponse);
        }
    }
}

如果用戶已經(jīng)通過身份鑒別,則session中會有AUTHENTICATED_SESSION_ATTRIBUTE,此時(shí)用戶是已經(jīng)被認(rèn)證的,過濾器應(yīng)該不對該操作做任何處理;反之,如果AUTHENTICATED_SESSION_ATTRIBUTE并不存在則意味著需要開啟鑒別流程:保留當(dāng)前的目標(biāo)URL,然后重定向到IDP。

3. 第二步,鑒別請求

鑒別請求

這一部分才是SAML身份鑒別流程的開始。SP通過發(fā)送SAML AuthnRequest到IDP,來請求鑒別用戶身份。這里將以最常見的的方法HTTP重定向?yàn)槔齺碇v解。
AuthnRequest對象

1. 構(gòu)建AuthnRequest對象

AuthnRequest authnRequest = OpenSAMLUtils
    .buildSAMLObject(AuthnRequest.class);

其中如下屬性需要設(shè)置:

  • 請求時(shí)間:該對象創(chuàng)建的時(shí)間,以判斷其時(shí)效性,

authnRequest.setIssueInstant(new DateTime());

  • 目標(biāo)URL:AuthnRequest的目標(biāo)地址,IDP地址,

authnRequest.setDestination(getIPDDestination());

  • 傳輸SAML斷言所使用的綁定:也就是用何種協(xié)議來使用Artifact取回真正的認(rèn)證信息,

authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);

  • SP地址: 也就是SAML斷言返回的地址

authnRequest
.setAssertionConsumerServiceURL(getAssertionConsumerEndpoint());

  • 請求的ID:為當(dāng)前請求設(shè)置ID,一般為隨機(jī)數(shù),

authnRequest.setID(OpenSAMLUtils.generateSecureRandomId());
注意:獲得安全隨機(jī)數(shù)的方法:

RandomIdentifierGenerationStrategy secureRandomIdGenerator = new RandomIdentifierGenerationStrategy();
String id = secureRandomIdGenerator.generateIdentifier();
  • Issuer: 發(fā)行人信息,也就是SP的ID,一般是SP的URL
Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class);
issuer.setValue(SPConstants.SP_ENTITY_ID);
authnRequest.setIssuer(issuer);
  • NameID:IDP對于用戶身份的標(biāo)識;NameID policy是SP關(guān)于NameID是如何被創(chuàng)建的說明;Format指明SP需要返回什么類型的標(biāo)識(SAML Artifact);屬性AllowCreate指明IDP是否被允許當(dāng)發(fā)現(xiàn)用戶不存在時(shí)創(chuàng)建用戶賬號。
NameIDPolicy nameIDPolicy =
OpenSAMLUtils.buildSAMLObject(NameIDPolicy.class);
nameIDPolicy.setFormat(NameIDType.TRANSIENT);
nameIDPolicy.setAllowCreate(true);
authnRequest.setNameIDPolicy(nameIDPolicy);

NameID Formats:
在SAML中有多種NameID的格式存在,比如Kerberos,郵箱以及Windows域限定名稱(Windows Domain Qualified Name),這里要特別說明如下兩種:

  • 持久標(biāo)識(Persistent Identifier):一個(gè)隨機(jī)的ID標(biāo)識被分配給用戶,以避免暴露用戶的真實(shí)賬戶。無論用戶何時(shí)登入,都會返回相同的標(biāo)識。SP可以將這個(gè)標(biāo)識和本地的用戶賬號綁定;
  • 臨時(shí)標(biāo)識(Transient Identifier):臨時(shí)標(biāo)識是一個(gè)和用戶賬戶沒有關(guān)系的隨機(jī)標(biāo)識,不會被重復(fù)使用,用戶每次登陸所返回的標(biāo)識都是不一樣的。
  • 請求認(rèn)證上下文(requested Authentication Context):
    SP對于認(rèn)證的要求,包含SP希望IDP如何驗(yàn)證用戶,也就是IDP要依據(jù)什么來驗(yàn)證用戶身份。
authnRequest.setRequestedAuthnContext(buildRequestedAuthnContext());

可以如此獲得*RequestedAuthnContext *

RequestedAuthnContext requestedAuthnContext = OpenSAMLUtils
.buildSAMLObject(RequestedAuthnContext.class);
  • authnContextClassRef
    authnContextClassRef代表著鑒別方式的一個(gè)選項(xiàng)。比如一個(gè)網(wǎng)站同時(shí)支持口令認(rèn)證和Kerberos兩種方式,則口令認(rèn)證和Kerberos就是兩個(gè)authnContextClassRef選項(xiàng)。請求認(rèn)證上下文中就可以同時(shí)包含這兩個(gè)。
AuthnContextClassRef passwordAuthnContextClassRef 
    = OpenSAMLUtils.buildSAMLObject(AuthnContextClassRef.class);
passwordAuthnContextClassRef
    .setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX);
requestedAuthnContext.getAuthnContextClassRefs()
    .add(passwordAuthnContextClassRef);
requestedAuthnContext
    .setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);

同時(shí)請求認(rèn)證上下文也可能有多個(gè),如果是這樣的情況他們就要安裝優(yōu)先級排列。

Comparison代表著如何IDP要如何依據(jù)所給出的鑒別方式選項(xiàng)處理鑒別結(jié)果,其取值包括:

  1. Minimum,最少策略,滿足這個(gè)方式或者比它更安全方式就通過驗(yàn)證;
  2. Better,更優(yōu)策略,需要滿足比這個(gè)方式更為安全的方式才能通過驗(yàn)證;
  3. Exact,精準(zhǔn)模式,必須滿足當(dāng)前方式才能通過驗(yàn)證;
  4. Maximum,最多策略,需要滿足安全性最強(qiáng)的方式才能通過認(rèn)證。

發(fā)送鑒別請求

AuthnReques URL

使用HTTP重定向綁定(HTTP Redirect Binding)將鑒別請求發(fā)送到Idp。 AuthnRequest以參數(shù)的形式附加在HTTP請求中,但是沒有強(qiáng)制要求需要對其簽名,不過為了信息的完整性強(qiáng)烈建議這樣做,簽名的結(jié)果作為一個(gè)獨(dú)立的URL參數(shù)傳輸,以便于驗(yàn)證。同時(shí)建議使用HTTPS來保證數(shù)據(jù)傳輸?shù)耐暾院蜋C(jī)密性。

為了幫助數(shù)字簽名以及序列化參數(shù)以發(fā)送重定向消息,OpenSAML提供了HTTPRedirectDefalteEncoder,它將幫助我們來對于AuthnRequest進(jìn)行序列化和簽名,并把消息和用戶一起重定向到Idp。

OpenSAML message encoders是對SAML消息傳輸?shù)囊环N抽象封裝,HTTPRedirectDefalteEncoder也是如此,它是對HTTP重定向綁定過程的抽象。

編碼器(encoder)用來處理數(shù)據(jù)對象,也就是消息上下文(MessageContext),其中包含消息的信息內(nèi)容和傳輸細(xì)節(jié)。

Message Context

在新版OpenSAML中,Message Context相關(guān)的類如下:

  • MessageContext:主類,主環(huán)境上下文;
  • SAMLPeerEntityContext:關(guān)于傳輸對端實(shí)體的信息,對于IDP就是SP,對于SP就是IDP;通常該對象不包括很多信息,但是會包含一個(gè)和多個(gè)子內(nèi)容;
  • SAMLEndpointContext:端點(diǎn)信息;
  • SecurityParametersContext:關(guān)于簽名和加密的信息;
  • SAMLMessageInfoContext:記錄issue和ID等基本信息。

以上contexts都是主環(huán)境上下文的子內(nèi)容創(chuàng)建的,以下使用實(shí)例:

MessageContext context = new MessageContext();
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);

getSubcontext方法的最后一個(gè)參數(shù)表示如果存在該信息不存在是否創(chuàng)建,當(dāng)然也可以使用setter方法來設(shè)置:

endpointContext.setEndpoint(getIPDEndpoint());

一般而言,SAMLEndpointContext
AuthnRequest所必須的,用以指明消息發(fā)送的目的地。SAMLEndpointContextSAMLPeerEntityContext的子內(nèi)容。如何創(chuàng)建請見下一部分。

創(chuàng)建MessageContext

  1. 創(chuàng)建主環(huán)境上下文:
 MessageContext context = new MessageContext();
  1. 設(shè)置要發(fā)送的消息(這里就是AuthnRequest)到主環(huán)境上下文:
context.setMessage(authnRequest);
  1. 創(chuàng)建SAMLPeerEntityContext and SAMLEndpointContext
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
  1. 創(chuàng)建目的地端點(diǎn)并將其設(shè)置到SAMLEndpointContext
SingleSignOnService endpoint = OpenSAMLUtils.buildSAMLObject(SingleSignOnService.class);
endpoint.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); 
endpoint.setLocation(getIPDSSODestination()); 
context.setPeerEntityEndpoint(endpoint);
endpointContext.setEndpoint(endpoint);
  1. SecurityParametersContext是可選項(xiàng),但強(qiáng)烈建議創(chuàng)建它來簽名參數(shù)信息
SignatureSigningParameters signatureSigningParameters = new SignatureSigningParameters();
signatureSigningParameters.setSigningCredential(SPCredentials.getCredential());
signatureSigningParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
context.getSubcontext(SecurityParametersContext.class,true)
    .setSignatureSigningParameters(signatureSigningParameters);

SecurityParametersContext被設(shè)置后,HTTPRedirectDefalteEncoder會自動幫我們對AuthnRequest簽名,并添加簽名結(jié)果和簽名算法到URL參數(shù)中。

  1. 創(chuàng)建HTTPRedirectDefalteEncoder,并將消息環(huán)境上下文賦予它,同時(shí)為其設(shè)置HTTPServletResponse
HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();

encoder.setMessageContext(context);
encoder.setHttpServletResponse(httpServletResponse);
  1. encoder被初始化,然后調(diào)用encode發(fā)送消息。encode方法將會壓縮消息(先使用RC1951-DEFLATE Compressed Data Format Specification version 作為默認(rèn)方法壓縮數(shù)據(jù),在對壓縮后的數(shù)據(jù)信息Base64編碼),生成簽名,添加結(jié)果到URL并從定向用戶到Idp.
encoder.initialize();
encoder.encode();

以下是redirect URLAuthnRequest XML的實(shí)例:

redirect URL

AuthnRequest XML

接下來SP將使用SOAP協(xié)議將Artifact發(fā)給IDP換取斷言信息(Assertion),由于篇幅有限,這部分內(nèi)容將會在后面的文章中講解,敬請期待,歡迎關(guān)注和指教。
最后給出源碼地址 https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git

更多關(guān)于SAML協(xié)議的是實(shí)現(xiàn)的內(nèi)容,請參見本人編寫的一系列教程文章,其介紹如何使用OpenSAML,歡迎閱讀指正:

  1. OpenSAML 使用引導(dǎo) I : 簡介
  2. OpenSAML 使用引導(dǎo) II : Service Provider 的實(shí)現(xiàn)之AuthnRequest
  3. OpenSAML 使用引導(dǎo) III: Service Provider 的實(shí)現(xiàn)之Artifact與斷言
  4. OpenSAMl 使用引導(dǎo)IV: 安全特性
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容