OpenSAML 使用引导 II : Service Provider 的实现之AuthnRequest

  1. 云栖社区>
  2. 博客>
  3. 正文

OpenSAML 使用引导 II : Service Provider 的实现之AuthnRequest

登高且赋 2017-09-07 20:20:00 浏览1308
展开阅读全文

前文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的角度再看身份鉴别过程

SAML

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重定向为例来讲解。
AuthnRequest对象

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要如何依据所给出的鉴别方式选项处理鉴别结果,其取值包括:

  1. Minimum,最少策略,满足这个方式或者比它更安全方式就通过验证;
  2. Better,更优策略,需要满足比这个方式更为安全的方式才能通过验证;
  3. Exact,精准模式,必须满足当前方式才能通过验证;
  4. Maximum,最多策略,需要满足安全性最强的方式才能通过认证。

发送鉴别请求

AuthnReques URL

使用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所必须的,用以指明消息发送的目的地。SAMLEndpointContextSAMLPeerEntityContext的子内容。如何创建请见下一部分。

创建MessageContext

  1. 创建主环境上下文:
 MessageContext context = new MessageContext();
  1. 设置要发送的消息(这里就是AuthnRequest)到主环境上下文:
context.setMessage(authnRequest);
  1. 创建SAMLPeerEntityContext and SAMLEndpointContext
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
  1. 创建目的地端点并将其设置到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是可选项,但强烈建议创建它来签名参数信息
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参数中。

  1. 创建HTTPRedirectDefalteEncoder,并将消息环境上下文赋予它,同时为其设置HTTPServletResponse
HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();

encoder.setMessageContext(context);
encoder.setHttpServletResponse(httpServletResponse);
  1. encoder被初始化,然后调用encode发送消息。encode方法将会压缩消息(先使用RC1951-DEFLATE Compressed Data Format Specification version 作为默认方法压缩数据,在对压缩后的数据信息Base64编码),生成签名,添加结果到URL并从定向用户到Idp.
encoder.initialize();
encoder.encode();

以下是redirect URLAuthnRequest XML的实例:

redirect URL

AuthnRequest XML

接下来SP将使用SOAP协议将Artifact发给IDP换取断言信息(Assertion),由于篇幅有限,这部分内容将会在后面的文章中讲解,敬请期待,欢迎关注和指教。
最后给出源码地址 https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git

更多关于SAML协议的是实现的内容,请参见本人编写的一系列教程文章,其介绍如何使用OpenSAML,欢迎阅读指正:

  1. OpenSAML 使用引导 I : 简介
  2. OpenSAML 使用引导 II : Service Provider 的实现之AuthnRequest
  3. OpenSAML 使用引导 III: Service Provider 的实现之Artifact与断言
  4. OpenSAMl 使用引导IV: 安全特性

网友评论

登录后评论
0/500
评论
登高且赋
+ 关注