Repository: cxf Updated Branches: refs/heads/master 97f3ac75f -> 4660cd8ca
Refactored STS REST implementation to be able to return different token formats Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/ad71b7a7 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/ad71b7a7 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/ad71b7a7 Branch: refs/heads/master Commit: ad71b7a77803354ceff3c4bcdcc8baf8364a24cb Parents: 97f3ac7 Author: Colm O hEigeartaigh <[email protected]> Authored: Fri Feb 12 15:23:28 2016 +0000 Committer: Colm O hEigeartaigh <[email protected]> Committed: Fri Feb 12 15:23:28 2016 +0000 ---------------------------------------------------------------------- .../cxf/sts/rest/RESTSecurityTokenService.java | 21 ++- .../sts/rest/RESTSecurityTokenServiceImpl.java | 93 +++++++++- .../cxf/systest/sts/rest/STSRESTTest.java | 181 +++++++++++++++++-- 3 files changed, 272 insertions(+), 23 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/ad71b7a7/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenService.java ---------------------------------------------------------------------- diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenService.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenService.java index 0766862..3768e16 100644 --- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenService.java +++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenService.java @@ -59,14 +59,27 @@ public interface RESTSecurityTokenService { @GET @Path("{tokenType}") - @Produces({ - MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON - }) - Response getToken(@PathParam("tokenType") String tokenType, @QueryParam("keyType") String keyType, + @Produces(MediaType.APPLICATION_XML) + Response getXMLToken(@PathParam("tokenType") String tokenType, @QueryParam("keyType") String keyType, @QueryParam("claim") List<String> requestedClaims, @QueryParam("appliesTo") String appliesTo, @QueryParam("wstrustResponse") @DefaultValue("false") boolean wstrustResponse); + @GET + @Path("{tokenType}") + @Produces("application/json;qs=0.8") + Response getJSONToken(@PathParam("tokenType") @DefaultValue("jwt") String tokenType, + @QueryParam("keyType") String keyType, + @QueryParam("claim") List<String> requestedClaims, + @QueryParam("appliesTo") String appliesTo); + + @GET + @Path("{tokenType}") + @Produces("text/plain;qs=0.9") + Response getPlainToken(@PathParam("tokenType") String tokenType, @QueryParam("keyType") String keyType, + @QueryParam("claim") List<String> requestedClaims, + @QueryParam("appliesTo") String appliesTo); + @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON http://git-wip-us.apache.org/repos/asf/cxf/blob/ad71b7a7/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenServiceImpl.java ---------------------------------------------------------------------- diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenServiceImpl.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenServiceImpl.java index dd01d0a..b13f54a 100644 --- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenServiceImpl.java +++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/rest/RESTSecurityTokenServiceImpl.java @@ -19,11 +19,15 @@ package org.apache.cxf.sts.rest; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.logging.Logger; +import java.util.zip.Deflater; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; @@ -31,9 +35,14 @@ import javax.xml.bind.JAXBElement; import org.w3c.dom.Document; import org.w3c.dom.Element; - +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.Base64Exception; +import org.apache.cxf.common.util.Base64Utility; +import org.apache.cxf.common.util.CompressionUtils; +import org.apache.cxf.common.util.PropertyUtils; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.jaxrs.ext.MessageContext; +import org.apache.cxf.message.Message; import org.apache.cxf.phase.PhaseInterceptorChain; import org.apache.cxf.security.SecurityContext; import org.apache.cxf.security.transport.TLSSessionInfo; @@ -48,6 +57,7 @@ import org.apache.cxf.ws.security.sts.provider.model.RequestSecurityTokenType; import org.apache.cxf.ws.security.sts.provider.model.RequestedSecurityTokenType; import org.apache.cxf.ws.security.sts.provider.model.UseKeyType; import org.apache.cxf.ws.security.trust.STSUtils; +import org.apache.wss4j.common.util.DOM2Writer; import org.apache.wss4j.dom.WSConstants; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.keys.content.X509Data; @@ -60,6 +70,7 @@ public class RESTSecurityTokenServiceImpl extends SecurityTokenServiceImpl imple private static final String CLAIM_TYPE = "ClaimType"; private static final String CLAIM_TYPE_NS = "http://schemas.xmlsoap.org/ws/2005/05/identity"; + private static final Logger LOG = LogUtils.getL7dLogger(RESTSecurityTokenServiceImpl.class); static { DEFAULT_CLAIM_TYPE_MAP = new HashMap<String, String>(); @@ -94,9 +105,10 @@ public class RESTSecurityTokenServiceImpl extends SecurityTokenServiceImpl imple private List<String> defaultClaims; private boolean requestClaimsOptional = true; + private boolean useDeflateEncoding = true; @Override - public Response getToken(String tokenType, String keyType, + public Response getXMLToken(String tokenType, String keyType, List<String> requestedClaims, String appliesTo, boolean wstrustResponse) { RequestSecurityTokenResponseType response = @@ -110,12 +122,46 @@ public class RESTSecurityTokenServiceImpl extends SecurityTokenServiceImpl imple } RequestedSecurityTokenType requestedToken = getRequestedSecurityToken(response); + return Response.ok(requestedToken.getAny()).build(); + } + + @Override + public Response getJSONToken(String tokenType, String keyType, + List<String> requestedClaims, String appliesTo) { + if (!"jwt".equals(tokenType)) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + RequestSecurityTokenResponseType response = + issueToken(tokenType, keyType, requestedClaims, appliesTo); + + RequestedSecurityTokenType requestedToken = getRequestedSecurityToken(response); + + // Discard the XML Wrapper + create a new JSON Wrapper + String token = ((Element)requestedToken.getAny()).getTextContent(); + return Response.ok(new JSONWrapper(token)).build(); + } + + @Override + public Response getPlainToken(String tokenType, String keyType, + List<String> requestedClaims, String appliesTo) { + RequestSecurityTokenResponseType response = + issueToken(tokenType, keyType, requestedClaims, appliesTo); + + RequestedSecurityTokenType requestedToken = getRequestedSecurityToken(response); if ("jwt".equals(tokenType)) { // Discard the wrapper here return Response.ok(((Element)requestedToken.getAny()).getTextContent()).build(); } else { - return Response.ok(requestedToken.getAny()).build(); + // Base-64 encode the token + return it + try { + String encodedToken = + encodeToken(DOM2Writer.nodeToString((Element)requestedToken.getAny())); + return Response.ok(encodedToken).build(); + } catch (Exception ex) { + LOG.warning(ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } } } @@ -168,7 +214,7 @@ public class RESTSecurityTokenServiceImpl extends SecurityTokenServiceImpl imple JAXBElement<UseKeyType> useKey = of.createUseKey(useKeyType); request.getAny().add(useKey); } catch (XMLSecurityException ex) { - // TODO + LOG.warning(ex.getMessage()); } } } @@ -337,4 +383,43 @@ public class RESTSecurityTokenServiceImpl extends SecurityTokenServiceImpl imple return PhaseInterceptorChain.getCurrentMessage(); } + public void setUseDeflateEncoding(boolean deflate) { + useDeflateEncoding = deflate; + } + + protected String encodeToken(String assertion) throws Base64Exception { + byte[] tokenBytes = assertion.getBytes(StandardCharsets.UTF_8); + + if (useDeflateEncoding) { + tokenBytes = CompressionUtils.deflate(tokenBytes, getDeflateLevel(), true); + } + StringWriter writer = new StringWriter(); + Base64Utility.encode(tokenBytes, 0, tokenBytes.length, writer); + return writer.toString(); + } + + private static int getDeflateLevel() { + Integer level = null; + + Message m = PhaseInterceptorChain.getCurrentMessage(); + if (m != null) { + level = PropertyUtils.getInteger(m, "deflate.level"); + } + if (level == null) { + level = Deflater.DEFLATED; + } + return level; + } + + private static class JSONWrapper { + private String token; + + public JSONWrapper(String token) { + this.token = token; + } + + public String getToken() { + return token; + } + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/ad71b7a7/services/sts/systests/basic/src/test/java/org/apache/cxf/systest/sts/rest/STSRESTTest.java ---------------------------------------------------------------------- diff --git a/services/sts/systests/basic/src/test/java/org/apache/cxf/systest/sts/rest/STSRESTTest.java b/services/sts/systests/basic/src/test/java/org/apache/cxf/systest/sts/rest/STSRESTTest.java index b25a204..cae4f0c 100644 --- a/services/sts/systests/basic/src/test/java/org/apache/cxf/systest/sts/rest/STSRESTTest.java +++ b/services/sts/systests/basic/src/test/java/org/apache/cxf/systest/sts/rest/STSRESTTest.java @@ -19,7 +19,10 @@ package org.apache.cxf.systest.sts.rest; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -38,6 +41,8 @@ import org.w3c.dom.Element; import org.apache.cxf.Bus; import org.apache.cxf.bus.spring.SpringBusFactory; +import org.apache.cxf.common.util.Base64Utility; +import org.apache.cxf.common.util.CompressionUtils; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; @@ -117,7 +122,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); Response response = client.get(); @@ -149,7 +154,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml1.1"); Response response = client.get(); @@ -181,7 +186,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml1.1"); client.query("keyType", SYMMETRIC_KEY_KEYTYPE); @@ -223,7 +228,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); client.query("keyType", PUBLIC_KEY_KEYTYPE); @@ -265,7 +270,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml1.1"); client.query("keyType", BEARER_KEYTYPE); @@ -305,7 +310,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); client.query("appliesTo", DEFAULT_ADDRESS); @@ -338,7 +343,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); client.query("appliesTo", "https://localhost:8081/tripleit/"); @@ -365,7 +370,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); // First check that the role isn't usually in the generated token @@ -427,7 +432,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); client.query("wstrustResponse", "true"); @@ -703,7 +708,44 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { } @org.junit.Test - public void testIssueJWTToken() throws Exception { + public void testIssueSAML2TokenPlain() throws Exception { + SpringBusFactory bf = new SpringBusFactory(); + URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); + + Bus bus = bf.createBus(busFile.toString()); + SpringBusFactory.setDefaultBus(bus); + SpringBusFactory.setThreadDefaultBus(bus); + + String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; + WebClient client = WebClient.create(address, busFile.toString()); + + client.accept("text/plain"); + client.path("saml2.0"); + + Response response = client.get(); + String encodedAssertion = response.readEntity(String.class); + assertNotNull(encodedAssertion); + + byte[] deflatedToken = Base64Utility.decode(encodedAssertion); + InputStream inputStream = CompressionUtils.inflate(deflatedToken); + Document doc = + StaxUtils.read(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + // Process the token + List<WSSecurityEngineResult> results = processToken(doc.getDocumentElement()); + + assertTrue(results != null && results.size() == 1); + SamlAssertionWrapper assertion = + (SamlAssertionWrapper)results.get(0).get(WSSecurityEngineResult.TAG_SAML_ASSERTION); + assertTrue(assertion != null); + assertTrue(assertion.getSaml2() != null && assertion.getSaml1() == null); + assertTrue(assertion.isSigned()); + + bus.shutdown(true); + } + + @org.junit.Test + public void testIssueJWTTokenPlain() throws Exception { SpringBusFactory bf = new SpringBusFactory(); URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); @@ -714,7 +756,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/json").accept("application/json"); + client.accept("text/plain"); client.path("jwt"); Response response = client.get(); @@ -736,7 +778,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/json").accept("application/json"); + client.accept("text/plain"); client.path("jwt"); client.query("appliesTo", DEFAULT_ADDRESS); @@ -759,7 +801,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/json").accept("application/json"); + client.accept("text/plain"); client.path("jwt"); // First check that the role isn't usually in the generated token @@ -849,7 +891,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("application/xml"); client.path("saml2.0"); // 1. Get a token via GET @@ -928,7 +970,7 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; WebClient client = WebClient.create(address, busFile.toString()); - client.type("application/xml").accept("application/xml"); + client.accept("text/plain"); client.path("jwt"); // 1. Get a token via GET @@ -991,6 +1033,115 @@ public class STSRESTTest extends AbstractBusClientServerTestBase { bus.shutdown(true); } + @org.junit.Test + public void testIssueJWTTokenXMLWrapper() throws Exception { + SpringBusFactory bf = new SpringBusFactory(); + URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); + + Bus bus = bf.createBus(busFile.toString()); + SpringBusFactory.setDefaultBus(bus); + SpringBusFactory.setThreadDefaultBus(bus); + + String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; + WebClient client = WebClient.create(address, busFile.toString()); + + client.accept("application/xml"); + client.path("jwt"); + + Response response = client.get(); + Document assertionDoc = response.readEntity(Document.class); + assertNotNull(assertionDoc); + + // Discard XML wrapper + validateJWTToken(assertionDoc.getDocumentElement().getFirstChild().getTextContent(), null); + } + + @org.junit.Test + public void testIssueJWTTokenJSONWrapper() throws Exception { + SpringBusFactory bf = new SpringBusFactory(); + URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); + + Bus bus = bf.createBus(busFile.toString()); + SpringBusFactory.setDefaultBus(bus); + SpringBusFactory.setThreadDefaultBus(bus); + + String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; + WebClient client = WebClient.create(address, busFile.toString()); + + client.accept("application/json"); + client.path("jwt"); + + client.get(); + } + + @org.junit.Test + public void testDefaultSAMLFormat() throws Exception { + SpringBusFactory bf = new SpringBusFactory(); + URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); + + Bus bus = bf.createBus(busFile.toString()); + SpringBusFactory.setDefaultBus(bus); + SpringBusFactory.setThreadDefaultBus(bus); + + String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; + WebClient client = WebClient.create(address, busFile.toString()); + + client.accept("*"); + client.path("saml"); + + Response response = client.get(); + // It should be XML + Document doc = response.readEntity(Document.class); + assertNotNull(doc); + } + + @org.junit.Test + public void testDefaultJWTFormat() throws Exception { + SpringBusFactory bf = new SpringBusFactory(); + URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); + + Bus bus = bf.createBus(busFile.toString()); + SpringBusFactory.setDefaultBus(bus); + SpringBusFactory.setThreadDefaultBus(bus); + + String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; + WebClient client = WebClient.create(address, busFile.toString()); + + client.accept("*"); + client.path("jwt"); + + Response response = client.get(); + // It should be XML + Document doc = response.readEntity(Document.class); + assertNotNull(doc); + } + + @org.junit.Test + public void testIssueSAMLTokenWithWrongAcceptType() throws Exception { + SpringBusFactory bf = new SpringBusFactory(); + URL busFile = STSRESTTest.class.getResource("cxf-client.xml"); + + Bus bus = bf.createBus(busFile.toString()); + SpringBusFactory.setDefaultBus(bus); + SpringBusFactory.setThreadDefaultBus(bus); + + String address = "https://localhost:" + STSPORT + "/SecurityTokenService/token"; + WebClient client = WebClient.create(address, busFile.toString()); + + client.accept("application/json"); + client.path("saml2.0"); + + Response response = client.get(); + try { + response.readEntity(Document.class); + fail("Failure expected on an bad accept type"); + } catch (Exception ex) { + // expected + } + + bus.shutdown(true); + } + private Element validateSAMLSecurityTokenResponse( RequestSecurityTokenResponseType securityResponse, boolean saml2 ) throws Exception {
