前文OpenSAML 使用引导 I : 简介介绍了OpenSAML的基础概况, 本文将从Service Provider(SP)角度出发,讲解如何使用OpenSAML如申请身份鉴别请求(AuthnRequest),并从IDP出得到断言的引用标识——SAML Artifact
相关阅读SAML2.0入门指南,
源码地址:https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git
1、从OpenSAML的角度再看身份鉴别过程
1. 用户尝试获取访问权限
用户对SP中的资源进行请求。这一步中,SP会做出判断是否要需要鉴别用户身份。一般而言,如果当前用户在SP上不存在已经被认证的session信息,就需要对用户提出身份鉴别请求。
2. 用户被重定向到IDP
如果需要对用户进行身份鉴别,SP将会创建AuthnRequest对象指明要用户要如何才能被鉴别。这个AuthnRequest将会被以URL参数的形式编码到HTTP请求中,然后通过浏览器重定向到IDP。
3. 用户身份别鉴别
IDP解码获得AuthnRequest并根据其中的要求来鉴别用户身份。
4. 已鉴别的用户被发送回SP
如果用户通过身份鉴别,IDP会将认证信息和一个标识联系起来,这个标识被称为SAML制品(SAML Artifact,简称制品)。这个制品也是以URL参数的形式加入到HTTP请求中,然后重定向发回SP。
5. SP请求认证信息
SP创建ArtifactResolve对象,并将Artifact包含在其中。这个ArtifactResolve对象通过SOAP请求发送到IDP。
6. IDP响应请求返回认证信息
IDP接受到ArtifactResolve并将Artifact抽离出来。IDP通过ArtifactResponse响应SOAP请求,ArtifactResponse中包含认证信息,以SAML断言格式包含在其中。
2. 第一步:用户拦截
这里讨论鉴别过程的第一步。实际上,用户拦截并不是认证过程的一部分,但是确实这一步却决定着鉴别过程是否会发生。
用户的交互以拦截非认证的用户去尝试获取SP资源时开始。对于Java Web应用而言,Servlet过滤器是一个很好的选择去做这样的拦截。过滤器检查是否当前用户已被认证,如果已被验证则允许访问,反之要启动身份验证流程。
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 {
//将本来要访问的目标路径保存到Session
setGotoURLOnSession(httpServletRequest);
redirectUserForAuthentication(httpServletResponse);
}
}
}
如果用户已经通过身份鉴别,则session中会有AUTHENTICATED_SESSION_ATTRIBUTE,此时用户是已经被认证的,过滤器应该不对该操作做任何处理;反之,如果AUTHENTICATED_SESSION_ATTRIBUTE并不存在则意味着需要开启鉴别流程:保留当前的目标URL,然后重定向到IDP。
3. 第二步,鉴别请求
这一部分才是SAML身份鉴别流程的开始。SP通过发送SAML AuthnRequest到IDP,来请求鉴别用户身份。这里将以最常见的的方法HTTP重定向为例来讲解。
1. 构建AuthnRequest对象
AuthnRequest authnRequest = OpenSAMLUtils
.buildSAMLObject(AuthnRequest.class);
其中如下属性需要设置:
- 请求时间:该对象创建的时间,以判断其时效性,
authnRequest.setIssueInstant(new DateTime());
- 目标URL:AuthnRequest的目标地址,IDP地址,
authnRequest.setDestination(getIPDDestination());
- 传输SAML断言所使用的绑定:也就是用何种协议来使用Artifact取回真正的认证信息,
authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
- SP地址: 也就是SAML断言返回的地址
authnRequest
.setAssertionConsumerServiceURL(getAssertionConsumerEndpoint());
- 请求的ID:为当前请求设置ID,一般为随机数,
authnRequest.setID(OpenSAMLUtils.generateSecureRandomId());
注意:获得安全随机数的方法:
RandomIdentifierGenerationStrategy secureRandomIdGenerator = new RandomIdentifierGenerationStrategy();
String id = secureRandomIdGenerator.generateIdentifier();
- Issuer: 发行人信息,也就是SP的ID,一般是SP的URL
Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class);
issuer.setValue(SPConstants.SP_ENTITY_ID);
authnRequest.setIssuer(issuer);
- NameID:IDP对于用户身份的标识;NameID policy是SP关于NameID是如何被创建的说明;Format指明SP需要返回什么类型的标识(SAML Artifact);属性AllowCreate指明IDP是否被允许当发现用户不存在时创建用户账号。
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),这里要特别说明如下两种:
- 持久标识(Persistent Identifier):一个随机的ID标识被分配给用户,以避免暴露用户的真实账户。无论用户何时登入,都会返回相同的标识。SP可以将这个标识和本地的用户账号绑定;
- 临时标识(Transient Identifier):临时标识是一个和用户账户没有关系的随机标识,不会被重复使用,用户每次登陆所返回的标识都是不一样的。
- 请求认证上下文(requested Authentication Context):
SP对于认证的要求,包含SP希望IDP如何验证用户,也就是IDP要依据什么来验证用户身份。
authnRequest.setRequestedAuthnContext(buildRequestedAuthnContext());
可以如此获得*RequestedAuthnContext *
RequestedAuthnContext requestedAuthnContext = OpenSAMLUtils
.buildSAMLObject(RequestedAuthnContext.class);
- authnContextClassRef
authnContextClassRef代表着鉴别方式的一个选项。比如一个网站同时支持口令认证和Kerberos两种方式,则口令认证和Kerberos就是两个authnContextClassRef选项。请求认证上下文中就可以同时包含这两个。
AuthnContextClassRef passwordAuthnContextClassRef
= OpenSAMLUtils.buildSAMLObject(AuthnContextClassRef.class);
passwordAuthnContextClassRef
.setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX);
requestedAuthnContext.getAuthnContextClassRefs()
.add(passwordAuthnContextClassRef);
requestedAuthnContext
.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);
同时请求认证上下文也可能有多个,如果是这样的情况他们就要安装优先级排列。
Comparison代表着如何IDP要如何依据所给出的鉴别方式选项处理鉴别结果,其取值包括:
- Minimum,最少策略,满足这个方式或者比它更安全方式就通过验证;
- Better,更优策略,需要满足比这个方式更为安全的方式才能通过验证;
- Exact,精准模式,必须满足当前方式才能通过验证;
- Maximum,最多策略,需要满足安全性最强的方式才能通过认证。
发送鉴别请求
使用HTTP重定向绑定(HTTP Redirect Binding)将鉴别请求发送到Idp。 AuthnRequest以参数的形式附加在HTTP请求中,但是没有强制要求需要对其签名,不过为了信息的完整性强烈建议这样做,签名的结果作为一个独立的URL参数传输,以便于验证。同时建议使用HTTPS来保证数据传输的完整性和机密性。
为了帮助数字签名以及序列化参数以发送重定向消息,OpenSAML提供了HTTPRedirectDefalteEncoder,它将帮助我们来对于AuthnRequest进行序列化和签名,并把消息和用户一起重定向到Idp。
OpenSAML message encoders是对SAML消息传输的一种抽象封装,HTTPRedirectDefalteEncoder也是如此,它是对HTTP重定向绑定过程的抽象。
编码器(encoder)用来处理数据对象,也就是消息上下文(MessageContext),其中包含消息的信息内容和传输细节。
Message Context
在新版OpenSAML中,Message Context相关的类如下:
- MessageContext:主类,主环境上下文;
- SAMLPeerEntityContext:关于传输对端实体的信息,对于IDP就是SP,对于SP就是IDP;通常该对象不包括很多信息,但是会包含一个和多个子内容;
- SAMLEndpointContext:端点信息;
- SecurityParametersContext:关于签名和加密的信息;
- SAMLMessageInfoContext:记录issue和ID等基本信息。
以上contexts都是主环境上下文的子内容创建的,以下使用实例:
MessageContext context = new MessageContext();
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
getSubcontext方法的最后一个参数表示如果存在该信息不存在是否创建,当然也可以使用setter方法来设置:
endpointContext.setEndpoint(getIPDEndpoint());
一般而言,SAMLEndpointContext
是AuthnRequest所必须的,用以指明消息发送的目的地。SAMLEndpointContext是SAMLPeerEntityContext的子内容。如何创建请见下一部分。
创建MessageContext
- 创建主环境上下文:
MessageContext context = new MessageContext();
- 设置要发送的消息(这里就是AuthnRequest)到主环境上下文:
context.setMessage(authnRequest);
- 创建SAMLPeerEntityContext and SAMLEndpointContext
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
- 创建目的地端点并将其设置到SAMLEndpointContext
SingleSignOnService endpoint = OpenSAMLUtils.buildSAMLObject(SingleSignOnService.class);
endpoint.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
endpoint.setLocation(getIPDSSODestination());
context.setPeerEntityEndpoint(endpoint);
endpointContext.setEndpoint(endpoint);
- SecurityParametersContext是可选项,但强烈建议创建它来签名参数信息
SignatureSigningParameters signatureSigningParameters = new SignatureSigningParameters();
signatureSigningParameters.setSigningCredential(SPCredentials.getCredential());
signatureSigningParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
context.getSubcontext(SecurityParametersContext.class,true)
.setSignatureSigningParameters(signatureSigningParameters);
SecurityParametersContext被设置后,HTTPRedirectDefalteEncoder会自动帮我们对AuthnRequest签名,并添加签名结果和签名算法到URL参数中。
- 创建HTTPRedirectDefalteEncoder,并将消息环境上下文赋予它,同时为其设置HTTPServletResponse
HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();
encoder.setMessageContext(context);
encoder.setHttpServletResponse(httpServletResponse);
- encoder被初始化,然后调用encode发送消息。encode方法将会压缩消息(先使用RC1951-DEFLATE Compressed Data Format Specification version 作为默认方法压缩数据,在对压缩后的数据信息Base64编码),生成签名,添加结果到URL并从定向用户到Idp.
encoder.initialize();
encoder.encode();
以下是redirect URL和 AuthnRequest XML的实例:
接下来SP将使用SOAP协议将Artifact发给IDP换取断言信息(Assertion),由于篇幅有限,这部分内容将会在后面的文章中讲解,敬请期待,欢迎关注和指教。
最后给出源码地址 https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git
更多关于SAML协议的是实现的内容,请参见本人编写的一系列教程文章,其介绍如何使用OpenSAML,欢迎阅读指正: