前文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的角度再看身份鑒別過程
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解。
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é)果,其取值包括:
- Minimum,最少策略,滿足這個(gè)方式或者比它更安全方式就通過驗(yàn)證;
- Better,更優(yōu)策略,需要滿足比這個(gè)方式更為安全的方式才能通過驗(yàn)證;
- Exact,精準(zhǔn)模式,必須滿足當(dāng)前方式才能通過驗(yàn)證;
- Maximum,最多策略,需要滿足安全性最強(qiáng)的方式才能通過認(rèn)證。
發(fā)送鑒別請求
使用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ā)送的目的地。SAMLEndpointContext是SAMLPeerEntityContext的子內(nèi)容。如何創(chuàng)建請見下一部分。
創(chuàng)建MessageContext
- 創(chuàng)建主環(huán)境上下文:
MessageContext context = new MessageContext();
- 設(shè)置要發(fā)送的消息(這里就是AuthnRequest)到主環(huán)境上下文:
context.setMessage(authnRequest);
- 創(chuàng)建SAMLPeerEntityContext and SAMLEndpointContext
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
- 創(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);
- 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ù)中。
- 創(chuàng)建HTTPRedirectDefalteEncoder,并將消息環(huán)境上下文賦予它,同時(shí)為其設(shè)置HTTPServletResponse
HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();
encoder.setMessageContext(context);
encoder.setHttpServletResponse(httpServletResponse);
- 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 URL和 AuthnRequest XML的實(shí)例:
接下來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,歡迎閱讀指正: