saml example

OpenSAML Examples

Will Provost

August 14, 2009

In this article I present a few simple examples of managing SAML 2.0 content using the OpenSAML library, version 2.2.3. I have found OpenSAML to be an excellent tool, but lacking somewhat for documentation and especially for code examples. I hope that the code here will be helpful: it is beginner-level stuff, and the aim is that it will clarify some of the fundamental practices for those just getting familiar with the library.

All code here is drawn from my course, Securing Java Web Services, in which OpenSAML -- and SAML generally -- are part of a bigger picture that includes XML cryptography, WS-Security, WS-SecurityPolicy, and other standards, as well as other tools including Metro/WSIT and OpenSSO.

Having explained what it is, I want also to be clear about what this article is not. It's not a tutorial, either on SAML or OpenSAML. I also am not claiming any sort of blessing from the OpenSAML project or its authors; for all I know they would develop splitting headaches upon seeing the code here.

And, to that point: not all the code here takes full advantage of all the tools available within OpenSAML. One item that comes to mind is that I use classic JAXP code to conjure a DOM document builder; OpenSAML implements a parser pool, and for most purposes that will be a better choice.

You get the idea. Mostly, as I've been craving examples along these lines and had to build them myself, I thought they might prove useful to others. It's no more or less than that.
Getting the Code

Okay, let's get to it: to see the source files, build them, and test them, download one of the following ZIP files, and unpack it anywhere you like:
A small archive (~100k) with just the source files, make files, and javadocs -- choose this only if you already have OpenSAML 2.2.3, and the necessary SLF JAR(s) that are required at runtime, and are prepared to copy JAR files into place to get the build working; or if you just want a look at the code and don't need to build it
A much larger archive (~10meg) that includes the required OpenSAML JARs in the right places for the build -- I recommend this in spite of the more significant download time, as it builds and runs right out of the box

You'll see a simple tree /OpenSAML that forms a small Java code project, including an Ant build.xml. Source files are found in the src directory, and javadocs in doc. If you chose the larger bundle to download, then the required OpenSAML JARs are already in place in lib, and the Xerces parser and supporting JARs (on which OpenSAML insists at runtime) is in endorsed.

If you got the little archive, copy the following files from your OpenSAML distribution:
Make a subdirectory lib and copy in:
bcprov-ext-jdk15-1.40.jar
commons-codec-1.3.jar
commons-collections-3.1.jar
commons-httpclient-3.1.jar
commons-lang-2.1.jar
jargs-1.0.jar
jcip-annotations-1.0.jar
jcl-over-slf4j-1.5.5.jar
joda-time-1.5.2.jar
log4j-over-slf4j-1.5.5.jar
not-yet-commons-ssl-0.3.9.jar
opensaml-2.2.3.jar
openws-1.2.2.jar
slf4j-api-1.5.6.jar
slf4j-jdk14-1.5.6.jar
slf4j-nop-1.5.6.jar
velocity-1.5.jar
xmlsec-1.4.2.jar
xmltooling-1.2.0.jar
Make a subdirectory endorsed and copy in:
resolver-2.9.1.jar
serializer-2.9.1.jar
xalan-2.7.1.jar
xercesImpl-2.9.1.jar
xml-apis-2.9.1.jar

In any case, you'll also see several subdirectories with example SAML content (which was produced using these Java applications).

Also, to build and run, the example code requires two supporting tools:
The Java 6 Developers Kit
Ant 1.6
Setup

Be sure that your executable path includes the Java bin directory and the Ant bin directory.

You can then build the code project by running ant from the OpenSAML directory. You'll see a new build directory with class files.
Code Design

A handful of Java classes in package cc.saml are organized in an inheritance hierarchy, and each of these has a main method for testing out some part of the code:
SAML.java is a general utility for working with OpenSAML. Its main method doesn't do much that's all that interesting: it can read and re-write a file with SAML content. But methods here are useful for many common tasks, such as building a <Subject> or converting between XMLObject and DOM representations.
SAMLAssertion.java lets you invoke methods each of which either reads or writes a certain kind of SAML assertion: authentication, attribute, or authorization-decision. This and the remaining subclasses are less generally useful, but serve as decent coding examples and might even be reusable via copy-and-paste.
SAMLProtocol.java follows the pattern of SAMLAssertion.java, but writes queries and responses of those same three types.
SAMLBinding.java does pretty much the same thing but wraps its output in a SOAP envelope and body according to the SAML 2.0 SOAP binding.

There is also a SAMLSignature.java, which we don't discuss further in this article because it isn't really an OpenSAML example. It's just DOM code that signs a SAML assertion, request, or response, and can verify a signature and check a pre-set trust store as well. It uses the SAML.java utility, but only for testing purposes via its own main method.

Packages cc.security and cc.xml provide support for, respectively: reading keystores and trust stores, and pretty-printing XML output.

We've not bundled the javadoc, but you can create it by running makejavadoc.bat (or run similar commands on non-Windows systems). This will put the docs in a subdirectory doc.
General Utilities

cc.saml.SAML can serve a few possible purposes, and we'll look at the code in sections accordingly. Skip over the main method for now, and we'll come back to it when we're ready to test the code. First, it wraps the default bootstrapping of OpenSAML such that any load of this class assures that OpenSAML is ready to roll. See the static initializer block for this, and also notice that it initializes an ID generator.
DefaultBootstrap.bootstrap ();
generator = new SecureRandomIdentifierGenerator ();


Instances of this class can be used to build new SAML content, and such instances can be created with an issuer URL that will automatically be used in many of the utility methods. See the constructors for this; they also initialize a private document builder. (See the earlier note about the option of using an OpenSAML parser pool.)

The next method is one of the most generally useful: it's a shortcut to create XML objects using the XML toolkit embedded in OpenSAML. This system is robust, flexible, and extensible; it is also a bit of work to use for most purposes, and so the create method simplifies the programming model in a small way, by assuming one QName for the schema type and resulting element, and by getting the builder and creating the object, all in one swoop:
@SuppressWarnings ("unchecked")
public <T> T create (Class<T> cls, QName qname)
{
return (T) ((XMLObjectBuilder)
Configuration.getBuilderFactory ().getBuilder (qname)).buildObject (qname);
}


We'll see usage examples very soon.

The next set of methods wraps uses of the toolkit's marshaller and unmarshaller system, again wrapping the behavior of finding the right marshaller/unmarshaller for a given object or element, and doing the requested marshalling/unmarshalling. In the order found in the source code these are addToElement, asDOMDocument, printToFile, fromElement, and readFromFile. Each is useful for a slightly different situation.

Next are a set of factory methods for progressively larger chunks of SAML content, starting with a method to create an <Issuer> element from the issuer URL with which the utility object was created. Here too is our first example of creating new XMLObjects from scratch, using the create method:

result = create (Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
result.setValue (issuerURL);


The next method creates a complete subject structure, based on a name, name format, and confirmation method. Only the name is required; the confirmation method can be null, sender-vouches, or bearer, while HOK would be a bad idea since this method isn't built to take the necessary key information and pack it into the XML structure. In terms of technique, createSubject doesn't do anything we haven't already seen: it's mostly creating new XMLObjects of specific types and then calling mutators on those objects to assemble the tree. (OpenSAML's design is really nice and intuitive here, and if you know the SAML structure you want to assemble, it's usually quick work to find the right methods and arguments to put it together.)
public Subject createSubject
(String username, String format, String confirmationMethod)
{
NameID nameID = create (NameID.class, NameID.DEFAULT_ELEMENT_NAME);
nameID.setValue (username);
if (format != null)
nameID.setFormat (format);

Subject subject = create (Subject.class, Subject.DEFAULT_ELEMENT_NAME);
subject.setNameID (nameID);

if (confirmationMethod != null)
{
SubjectConfirmation confirmation = create
(SubjectConfirmation.class, SubjectConfirmation.DEFAULT_ELEMENT_NAME);
confirmation.setMethod (CM_PREFIX + confirmationMethod);

subject.getSubjectConfirmations ().add (confirmation);
}

return subject;
}


The next set of methods create assertions and responses, given various pieces and parts. More of the same techniques here, really, though the number of overloads of createResponse might be a surprise. Some of these, keep in mind, are for producing error responses, and then some will work with an inResponseTo parameter and bake that into the XML structure.

Finally there's a set of methods that create specific assertion types. These are narrower in their purpose (and are aimed at specific lab exercises in the training course whence this code comes). createAttributeAssertion can either build a list of attributes from a simple name-value map, or can leave this list empty to be built using the addAttribute helper method. Note the code comments in this latter method that explain the use of the XSAny type in lieu of AttributeValue.

We can test out some of these methods from this class' main method. Try the following, some of which uses prepared files in the Assertion subdirectory:

run SAML
Usage: java cc.saml.SAML <inputFile> [<outputFile>]

run SAML Assertion/Authn.xml Rewritten.xml
type Rewritten.xml
<?xml version="1.0" encoding="UTF-8" ?>

<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="Assertion12345789"
IssueInstant="2009-07-15T15:42:36.750Z"
Version="2.0"
>
<saml:Issuer>http://mycom.com/MyJavaAuthnService</saml:Issuer>
<saml:Subject>
<saml:NameID>harold_dt</saml:NameID>
</saml:Subject>
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>

run SAML Assertion/Authn.xml
<?xml version="1.0" encoding="UTF-8" ?>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="Assertion12345789"
IssueInstant="2009-07-15T15:42:36.750Z"
Version="2.0"
>
<saml:Issuer>http://mycom.com/MyJavaAuthnService</saml:Issuer>
<saml:Subject>
<saml:NameID>harold_dt</saml:NameID>
</saml:Subject>
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>


Two final tests drive the createAuthnAssertion and createAttributeAssertion methods; they are not shown in the usage statement. Each involves some hard-coded values and generates a pre-defined assertion directly to the console:
run SAML generate authn
<?xml version="1.0" encoding="UTF-8" ?>

<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_76e026126bc35c0f825eced51d6bd43b"
IssueInstant="2009-08-13T19:54:55.562Z"
Version="2.0"
>
<saml:Issuer>http://saml.r.us/AssertingParty</saml:Issuer>
<saml:Subject>
<saml:NameID>harold_dt</saml:NameID>
<saml:SubjectConfirmation
Method="urn:oasis:names:tc:SAML:2.0:cm:sender-vouches"
/>
</saml:Subject>
<saml:Conditions
NotBefore="2009-08-13T19:54:45.562Z"
NotOnOrAfter="2009-08-13T20:24:55.562Z"
/>
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>

run SAML generate attr
<?xml version="1.0" encoding="UTF-8" ?>

<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_706381eabe57febe9a041d0119850f41"
IssueInstant="2009-08-13T19:57:39.171Z"
Version="2.0"
>
<saml:Issuer>http://saml.r.us/AssertingParty</saml:Issuer>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
>louisdraper@abc.gov</saml:NameID>
</saml:Subject>
<saml:Conditions
NotBefore="2009-08-13T19:57:29.171Z"
NotOnOrAfter="2009-08-13T20:27:39.171Z"
/>
<saml:AttributeStatement>
<saml:Attribute
Name="securityClearance"
>
<saml:AttributeValue>C2</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute
Name="roles"
>
<saml:AttributeValue>editor,reviewer</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>

Reading and Writing Assertions

cc.saml.SAMLAssertion extends cc.saml.SAML, but in practice it doesn't use much of the base class code, instead offering individual methods for reading and writing assertions that do the same sorts of things, but that are more self-contained, and possibly more readable as start-to-finish examples of building or parsing SAML structures. They do rely on the base class for readFromFile and printToFile methods.

The createXXXAssertion methods don't do anything we haven't already seen. But the reading methods are new, if not all that surprising. Consider readAuthenticationAssertion, which expects the contents of a given file to be an authentication assertion and simply reads out the name, name format, and authentication context classes that it finds there:
public void readAuthnAssertion (String filename)
throws Exception
{
Assertion assertion = (Assertion) readFromFile (filename);
NameID nameID = assertion.getSubject ().getNameID ();

System.out.println ("Assertion issued by " +
assertion.getIssuer ().getValue ());
System.out.println ("Subject name: " + nameID.getValue ());
System.out.println (" (Format " + nameID.getFormat () + ")");

System.out.println ("Authentication context classes found:");
for (Statement statement : assertion.getStatements ())
if (statement instanceof AuthnStatement)
System.out.println (" " + ((AuthnStatement) statement)
.getAuthnContext ().getAuthnContextClassRef ()
.getAuthnContextClassRef ());
}


Note that we get the character content of an Issuer by calling getValue, which is actually a method on the base type NameIDType. Also, while most assertions will have but one statement, it's legal to have several, and so OpenSAML follows the assertions schema by exposing a List<Statement>, and we loop over that, looking for an instance of the type that we're actually prepared to parse.

A few tests:
run SAMLAssertion
Usage: java cc.saml.SAMLAssertion
<write|read> <authn|attr|authz> <filename>

run SAMLAssertion read authn Assertion/Authn.xml
Assertion issued by http://mycom.com/MyJavaAuthnService
Subject name: harold_dt
(Format null)
Authentication context classes found:
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport

run SAMLAssertion read attr Assertion/Attribute.xml
Assertion issued by http://mycom.com/MyJavaAttributeService
Subject name: ga489Slge8+0nio9=
(Format urn:oasis:names:tc:SAML:2.0:nameid-format:transient)
Attributes found:
FullName: William Whitford Provost
JobTitle: Grand Poobah

run SAMLAssertion write attr
<?xml version="1.0" encoding="UTF-8" ?>

<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="Assertion12345789"
IssueInstant="2009-08-14T15:06:17.062Z"
Version="2.0"
>
<saml:Issuer>http://mycom.com/MyJavaAttributeService</saml:Issuer>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
>ga489Slge8+0nio9=</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute
Name="FullName"
>
<saml:AttributeValue>William Whitford Provost</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute
Name="JobTitle"
>
<saml:AttributeValue>Grand Poobah</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>

Writing Queries and Responses

cc.saml.SAMLProtocol extends cc.saml.SAMLAssertion, and has its own methods for producing canned queries and responses. The SAML query model being distinct from the assertions model, this class produces its own queries by more or less the same approach as the createXXX methods in the base class.

For responses, we do a couple new things. First, we have a single method printResponse, which just wraps a given assertion; this is in keeping with the SAML response model, which unlike the query model doesn't try to invent much new content but just serves as a vehicle for assertions traveling from an asserting to a relying party. Second, the method allows for a filename to be given, which it will parse as representing a prior query. It will loosely simulate the actual process of responding to that query, by grabbing the query ID and making it the InResponseTo value.
Response response = createResponse (assertion);

Issuer issuer = create (Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue ("http://somecom.com/SomeJavaAssertingParty");
response.setIssuer (issuer);

if (filename != null)
try
{
RequestAbstractType query = (RequestAbstractType)
readFromFile (filename + QUERY_SUFFIX);
response.setInResponseTo (query.getID ());
}
...


Tests:
run SAMLProtocol
Usage: java cc.saml.SAMLProtocol
<query|response> <authn|attr|authz> <simple-name>

run SAMLProtocol query authz AuthzTest
type AuthzTestQuery.xml
<?xml version="1.0" encoding="UTF-8" ?>

<samlp:AuthzDecisionQuery
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="AuthzQuery12345789"
IssueInstant="2009-08-14T15:22:03.250Z"
Resource="http://mycom.com/Repository/Private"
Version="2.0"
>
<saml:Issuer
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
>http://somecom.com/SomeJavaRelyingParty</saml:Issuer>
<saml:Subject
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
>
<saml:NameID>harold_dt</saml:NameID>
</saml:Subject>
<saml:Action
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
Namespace="urn:oasis:names:tc:SAML:1.0:action:rwedc"
>read</saml:Action>
</samlp:AuthzDecisionQuery>

run SAMLProtocol response authz AuthzTest
type AuthzTestResponse.xml
<?xml version="1.0" encoding="UTF-8" ?>

<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_11ff951ee156e8ad0d04e000013cac68"
InResponseTo="AuthzQuery12345789"
IssueInstant="2009-08-14T15:22:13.531Z"
Version="2.0"
>
<saml:Issuer
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
>http://somecom.com/SomeJavaAssertingParty</saml:Issuer>
<samlp:Status>
<samlp:StatusCode
Value="urn:oasis:names:tc:SAML:2.0:status:Success"
/>
</samlp:Status>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="Assertion12345789"
IssueInstant="2009-08-14T15:22:13.312Z"
Version="2.0"
>
<saml:Issuer>http://mycom.com/MyJavaAuthorizationService</saml:Issuer>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
>ga489Slge8+0nio9=</saml:NameID>
</saml:Subject>
<saml:AuthzDecisionStatement
Decision="Permit"
Resource="http://mycom.com/Repository/Private"
>
<saml:Action
Namespace="urn:oasis:names:tc:SAML:1.0:action:rwedc"
>read</saml:Action>
</saml:AuthzDecisionStatement>
</saml:Assertion>
</samlp:Response>

The SOAP Binding

cc.saml.SAMLBinding then extends SAMLProtocol in one simple way: it wraps the queries and responses in SOAP envelopes and bodies. Not much here is all that surprising; we even do the same response simulation, now expecting to encounter a SOAP envelope when reading into the given file for a query ID.
run SAMLBinding
Usage: java cc.saml.SAMLBinding
<request|response> <authn|attr|authz> [<simple-name>]

run SAMLBinding request authn AuthnTest
run SAMLBinding response authn AuthnTest
type AuthnTestResponse.xml
<?xml version="1.0" encoding="UTF-8" ?>

<soap11:Envelope
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/"
>
<soap11:Body>
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_b70e75ba90b00a7b8789087e6eb6c236"
InResponseTo="AuthnQuery12345789"
IssueInstant="2009-08-14T18:11:26.968Z"
Version="2.0"
>
<saml:Issuer
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
>http://somecom.com/SomeJavaAssertingParty</saml:Issuer>
<samlp:Status>
<samlp:StatusCode
Value="urn:oasis:names:tc:SAML:2.0:status:Success"
/>
</samlp:Status>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="Assertion12345789"
IssueInstant="2009-08-14T18:11:26.609Z"
Version="2.0"
>
<saml:Issuer>http://mycom.com/MyJavaAuthnService</saml:Issuer>
<saml:Subject>
<saml:NameID>harold_dt</saml:NameID>
</saml:Subject>
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>
</soap11:Body>
</soap11:Envelope>

Contact

I hope this has been helpful. With any questions, suggestions, criticism, or other feedback, please write to me at provost@capcourse.com.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值