Repository: cxf Updated Branches: refs/heads/master f823ad0f8 -> 6909358de
Updates to OAuth2 code request filters and simplifying the big_query demo code (still work in progress) Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/6909358d Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/6909358d Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/6909358d Branch: refs/heads/master Commit: 6909358dee4beaa00b493b728dd7689331173d2a Parents: f823ad0 Author: Sergey Beryozkin <[email protected]> Authored: Wed May 20 14:35:09 2015 +0100 Committer: Sergey Beryozkin <[email protected]> Committed: Wed May 20 14:35:09 2015 +0100 ---------------------------------------------------------------------- .../java/demo/jaxrs/server/BigQueryService.java | 117 ++----------------- .../main/webapp/WEB-INF/applicationContext.xml | 16 ++- .../oauth2/client/ClientCodeRequestFilter.java | 74 +++++++----- .../oidc/rp/OidcClientCodeRequestFilter.java | 24 +++- .../security/oidc/rp/OidcSecurityContext.java | 52 +++++++++ 5 files changed, 143 insertions(+), 140 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/6909358d/distribution/src/main/release/samples/jax_rs/big_query/src/main/java/demo/jaxrs/server/BigQueryService.java ---------------------------------------------------------------------- diff --git a/distribution/src/main/release/samples/jax_rs/big_query/src/main/java/demo/jaxrs/server/BigQueryService.java b/distribution/src/main/release/samples/jax_rs/big_query/src/main/java/demo/jaxrs/server/BigQueryService.java index 79d73b8..3e6a20f 100644 --- a/distribution/src/main/release/samples/jax_rs/big_query/src/main/java/demo/jaxrs/server/BigQueryService.java +++ b/distribution/src/main/release/samples/jax_rs/big_query/src/main/java/demo/jaxrs/server/BigQueryService.java @@ -18,132 +18,39 @@ */ package demo.jaxrs.server; -import java.net.URI; -import java.util.Collections; - -import javax.ws.rs.Consumes; import javax.ws.rs.GET; -import javax.ws.rs.POST; +import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; -import javax.ws.rs.core.UriInfo; -import org.apache.cxf.jaxrs.client.WebClient; -import org.apache.cxf.rs.security.oauth2.client.Consumer; -import org.apache.cxf.rs.security.oauth2.client.OAuthClientUtils; -import org.apache.cxf.rs.security.oauth2.common.AccessTokenGrant; -import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; -import org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrant; -import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; import org.apache.cxf.rs.security.oidc.common.IdToken; import org.apache.cxf.rs.security.oidc.common.UserInfo; -import org.apache.cxf.rs.security.oidc.rp.UserInfoClient; +import org.apache.cxf.rs.security.oidc.rp.OidcClientTokenContext; @Path("/service") public class BigQueryService { - @Context - private UriInfo uriInfo; - @Context - private HttpHeaders httpHeaders; - - private String authorizationServiceUri; - private WebClient accessTokenServiceClient; - private UserInfoClient tokenClient; - private Consumer consumer; - - @GET - @Path("/oidc/rp/start") - public Response startUserAuthentication() { - URI indexUri = uriInfo.getBaseUriBuilder().path("index.html").build(); - return Response.seeOther(indexUri).build(); - } - - @POST - @Path("/oidc/rp/complete") - @Consumes("application/octet-stream") - @Produces("application/xml,application/json,text/html") - public Response completeUserAuthentication(String code) { - return doCompleteBigQuery(code, null, true); - } - - @GET - @Path("/bigquery") - public Response startBiqQuery() { - - StringBuilder scopes = new StringBuilder(); - scopes.append("openid email profile"); - // Add application specific scopes if any - - URI loc = OAuthClientUtils.getAuthorizationURI(authorizationServiceUri, - consumer.getKey(), getRedirectUri(), uriInfo.getAbsolutePath() - .toString(), scopes.toString()); - - Response r = Response.seeOther(loc).build(); - return r; - } - @GET @Path("/bigquery/complete") @Produces("application/xml,application/json,text/html") - public Response completeBigQuery(@QueryParam("code") String code, - @QueryParam("state") String state) { - return doCompleteBigQuery(code, state, false); - } + public Response completeBigQuery(@Context OidcClientTokenContext context) { + // This IdToken check can be skipped and UserInfo checked for null instead + // given that UserInfo can only be obtained if IdToken is valid; shown here + // to demonstrate the properties of OidcClientTokenContext + IdToken idToken = context.getIdToken(); + if (idToken == null) { + throw new NotAuthorizedException(Response.Status.UNAUTHORIZED); + } + + UserInfo userInfo = context.getUserInfo(); - private Response doCompleteBigQuery(String code, String state, - boolean postMessage) { - - // Get the access token - ClientAccessToken at = getClientAccessToken(code, postMessage); - - // Expect and validate id_token - IdToken idToken = tokenClient.getIdToken(at, consumer.getKey()); - - // Get User Profile - UserInfo userInfo = tokenClient.getUserInfo(at, idToken); - - // Complete the request, use 'at' to access some other user's API, - // return the response to the user ResponseBuilder rb = Response.ok().type("application/json"); Response r = rb.entity( "{\"email\":\"" + userInfo.getProperty("email") + "\"}") .build(); return r; } - - public void setAccessTokenServiceClient(WebClient accessTokenServiceClient) { - this.accessTokenServiceClient = accessTokenServiceClient; - } - - private String getRedirectUri() { - return uriInfo.getBaseUriBuilder().path("/service/bigquery/complete") - .build().toString(); - } - - private ClientAccessToken getClientAccessToken(String code, boolean postMessage) { - AccessTokenGrant grant = new AuthorizationCodeGrant(code); - String redirectUri = postMessage ? "postmessage" : getRedirectUri(); - return OAuthClientUtils.getAccessToken(accessTokenServiceClient, - consumer, grant, Collections.singletonMap( - OAuthConstants.REDIRECT_URI, redirectUri), false); - } - - public void setUserInfoClient(UserInfoClient client) { - this.tokenClient = client; - } - - public void setAuthorizationServiceUri(String authorizationServiceUri) { - this.authorizationServiceUri = authorizationServiceUri; - } - - public void setConsumer(Consumer consumer) { - this.consumer = consumer; - } - } http://git-wip-us.apache.org/repos/asf/cxf/blob/6909358d/distribution/src/main/release/samples/jax_rs/big_query/src/main/webapp/WEB-INF/applicationContext.xml ---------------------------------------------------------------------- diff --git a/distribution/src/main/release/samples/jax_rs/big_query/src/main/webapp/WEB-INF/applicationContext.xml b/distribution/src/main/release/samples/jax_rs/big_query/src/main/webapp/WEB-INF/applicationContext.xml index 598e42f..ab23cac 100644 --- a/distribution/src/main/release/samples/jax_rs/big_query/src/main/webapp/WEB-INF/applicationContext.xml +++ b/distribution/src/main/release/samples/jax_rs/big_query/src/main/webapp/WEB-INF/applicationContext.xml @@ -21,7 +21,6 @@ http://cxf.apache.org/schemas/configuration/security.xsd"> - <!-- Restaurant Reservations Application --> <!-- <http:conduit name="*.http-conduit"> <http:client ConnectionTimeout="3000000" ReceiveTimeout="3000000"/> @@ -99,18 +98,27 @@ <property name="key" value="${client_id}"/> <property name="secret" value="${client_secret}"/> </bean> - - <bean id="bigQueryService" class="demo.jaxrs.server.BigQueryService"> + + <bean id="oidcRequestFilter" class="org.apache.cxf.rs.security.oidc.rp.OidcClientCodeRequestFilter"> + <property name="scopes" value="openid email profile"/> <property name="accessTokenServiceClient" ref="atServiceClient"/> <property name="userInfoClient" ref="userInfoClient"/> <property name="consumer" ref="consumer"/> <property name="authorizationServiceUri" value="https://accounts.google.com/o/oauth2/auth"/> - </bean> + <property name="startUri" value="service/bigquery"/> + <property name="completeUri" value="service/bigquery/complete"/> + </bean> + + <bean id="bigQueryService" class="demo.jaxrs.server.BigQueryService"/> <jaxrs:server id="bigQueryServer" address="/"> <jaxrs:serviceBeans> <ref bean="bigQueryService"/> </jaxrs:serviceBeans> + <jaxrs:providers> + <ref bean="oidcRequestFilter"/> + <bean class="org.apache.cxf.rs.security.oauth2.client.ClientTokenContextProvider"/> + </jaxrs:providers> <jaxrs:features> <ref bean="loggingFeature"/> </jaxrs:features> http://git-wip-us.apache.org/repos/asf/cxf/blob/6909358d/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/client/ClientCodeRequestFilter.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/client/ClientCodeRequestFilter.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/client/ClientCodeRequestFilter.java index 3e3b4ca..b6eee3e 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/client/ClientCodeRequestFilter.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/client/ClientCodeRequestFilter.java @@ -59,16 +59,14 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { private Consumer consumer; private ClientCodeStateManager clientStateManager; private ClientTokenContextManager clientTokenContextManager; - private WebClient accessTokenService; + private WebClient accessTokenServiceClient; private boolean decodeRequestParameters; private long expiryThreshold; + private String redirectUri; @Override public void filter(ContainerRequestContext rc) throws IOException { - SecurityContext sc = rc.getSecurityContext(); - if (sc == null || sc.getUserPrincipal() == null) { - throw ExceptionUtils.toNotAuthorizedException(null, null); - } + checkSecurityContextStart(rc.getSecurityContext()); UriInfo ui = rc.getUriInfo(); String absoluteRequestUri = ui.getAbsolutePath().toString(); @@ -82,7 +80,7 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { } if (!sameUriRedirect && absoluteRequestUri.endsWith(startUri)) { - ClientTokenContext request = getClientTokenContext(); + ClientTokenContext request = getClientTokenContext(rc); if (request != null) { setClientCodeRequest(request); if (completeUri != null) { @@ -90,51 +88,65 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { } return; } - Response codeResponse = createCodeResponse(rc, sc, ui); + Response codeResponse = createCodeResponse(rc, ui); rc.abortWith(codeResponse); } else if (absoluteRequestUri.endsWith(completeUri)) { - processCodeResponse(rc, sc, ui); + processCodeResponse(rc, ui); + checkSecurityContextEnd(rc.getSecurityContext()); } } - private Response createCodeResponse(ContainerRequestContext rc, SecurityContext sc, UriInfo ui) { - MultivaluedMap<String, String> redirectState = createRedirectState(rc, sc, ui); - String redirectScope = redirectState.getFirst(OAuthConstants.SCOPE); + protected void checkSecurityContextStart(SecurityContext sc) { + checkSecurityContextEnd(sc); + } + private void checkSecurityContextEnd(SecurityContext sc) { + if (sc == null || sc.getUserPrincipal() == null) { + throw ExceptionUtils.toNotAuthorizedException(null, null); + } + } + + private Response createCodeResponse(ContainerRequestContext rc, UriInfo ui) { + MultivaluedMap<String, String> redirectState = createRedirectState(rc, ui); + String theState = redirectState != null ? redirectState.getFirst(OAuthConstants.SCOPE) : null; + String redirectScope = redirectState != null ? redirectState.getFirst(OAuthConstants.SCOPE) : null; String theScope = redirectScope != null ? redirectScope : scopes; URI uri = OAuthClientUtils.getAuthorizationURI(authorizationServiceUri, consumer.getKey(), getAbsoluteRedirectUri(ui).toString(), - redirectState.getFirst(OAuthConstants.STATE), + theState, theScope); return Response.seeOther(uri).build(); } private URI getAbsoluteRedirectUri(UriInfo ui) { - if (completeUri != null) { + if (redirectUri != null) { + return URI.create(redirectUri); + } else if (completeUri != null) { return completeUri.startsWith("http") ? URI.create(completeUri) : ui.getBaseUriBuilder().path(completeUri).build(); } else { return ui.getAbsolutePath(); } } - protected void processCodeResponse(ContainerRequestContext rc, SecurityContext sc, UriInfo ui) { + protected void processCodeResponse(ContainerRequestContext rc, UriInfo ui) { MultivaluedMap<String, String> params = toRequestState(rc, ui); String codeParam = params.getFirst(OAuthConstants.AUTHORIZATION_CODE_VALUE); ClientAccessToken at = null; if (codeParam != null) { AccessTokenGrant grant = new AuthorizationCodeGrant(codeParam, getAbsoluteRedirectUri(ui)); - at = OAuthClientUtils.getAccessToken(accessTokenService, consumer, grant); + at = OAuthClientUtils.getAccessToken(accessTokenServiceClient, consumer, grant); } - ClientTokenContext tokenContext = initializeClientTokenContext(at, params); + ClientTokenContext tokenContext = initializeClientTokenContext(rc, at, params); if (at != null && clientTokenContextManager != null) { clientTokenContextManager.setClientTokenContext(mc, tokenContext); } setClientCodeRequest(tokenContext); } - private ClientTokenContext initializeClientTokenContext(ClientAccessToken at, + protected ClientTokenContext initializeClientTokenContext(ContainerRequestContext rc, + ClientAccessToken at, MultivaluedMap<String, String> params) { - ClientTokenContext tokenContext = createTokenContext(at); + ClientTokenContext tokenContext = createTokenContext(rc, at); ((ClientTokenContextImpl)tokenContext).setToken(at); if (clientStateManager != null) { MultivaluedMap<String, String> state = clientStateManager.fromRedirectState(mc, params); @@ -145,7 +157,7 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { } - protected ClientTokenContext createTokenContext(ClientAccessToken at) { + protected ClientTokenContext createTokenContext(ContainerRequestContext rc, ClientAccessToken at) { return new ClientTokenContextImpl(); } @@ -153,8 +165,7 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { JAXRSUtils.getCurrentMessage().setContent(ClientTokenContext.class, request); } - private MultivaluedMap<String, String> createRedirectState(ContainerRequestContext rc, SecurityContext sc, - UriInfo ui) { + private MultivaluedMap<String, String> createRedirectState(ContainerRequestContext rc, UriInfo ui) { if (clientStateManager == null) { return null; } @@ -180,10 +191,10 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { } sb.append(s); } - setScopeString(sb.toString()); + setScopes(sb.toString()); } - public void setScopeString(String scopesString) { - this.scopes = scopesString; + public void setScopes(String scopes) { + this.scopes = scopes; } public void setStartUri(String relStartUri) { @@ -198,8 +209,8 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { this.completeUri = completeUri; } - public void setAccessTokenService(WebClient accessTokenService) { - this.accessTokenService = accessTokenService; + public void setAccessTokenServiceClient(WebClient accessTokenServiceClient) { + this.accessTokenServiceClient = accessTokenServiceClient; } public void setClientCodeStateManager(ClientCodeStateManager manager) { @@ -220,7 +231,7 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { this.decodeRequestParameters = decodeRequestParameters; } - private ClientTokenContext getClientTokenContext() { + protected ClientTokenContext getClientTokenContext(ContainerRequestContext rc) { ClientTokenContext ctx = null; if (clientTokenContextManager != null) { ctx = clientTokenContextManager.getClientTokenContext(mc); @@ -228,7 +239,7 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { ClientAccessToken newAt = refreshAccessTokenIfExpired(ctx.getToken()); if (newAt != null) { clientTokenContextManager.removeClientTokenContext(mc, ctx); - ClientTokenContext newCtx = initializeClientTokenContext(newAt, ctx.getState()); + ClientTokenContext newCtx = initializeClientTokenContext(rc, newAt, ctx.getState()); clientTokenContextManager.setClientTokenContext(mc, newCtx); ctx = newCtx; } @@ -241,7 +252,7 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { if (at.getRefreshToken() != null && ((expiryThreshold > 0 && OAuthUtils.isExpired(at.getIssuedAt(), at.getExpiresIn() - expiryThreshold)) || OAuthUtils.isExpired(at.getIssuedAt(), at.getExpiresIn()))) { - return OAuthClientUtils.refreshAccessToken(accessTokenService, consumer, at); + return OAuthClientUtils.refreshAccessToken(accessTokenServiceClient, consumer, at); } return null; } @@ -249,4 +260,9 @@ public class ClientCodeRequestFilter implements ContainerRequestFilter { public void setExpiryThreshold(long expiryThreshold) { this.expiryThreshold = expiryThreshold; } + + public void setRedirectUri(String redirectUri) { + // Can be set to something like "postmessage" in some flows + this.redirectUri = redirectUri; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/6909358d/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java index aba4d3c..57cc2de 100644 --- a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java @@ -18,6 +18,10 @@ */ package org.apache.cxf.rs.security.oidc.rp; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.SecurityContext; + +import org.apache.cxf.jaxrs.utils.ExceptionUtils; import org.apache.cxf.rs.security.oauth2.client.ClientCodeRequestFilter; import org.apache.cxf.rs.security.oauth2.client.ClientTokenContext; import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; @@ -25,16 +29,32 @@ import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; public class OidcClientCodeRequestFilter extends ClientCodeRequestFilter { private UserInfoClient userInfoClient; + private boolean userInfoRequired = true; @Override - protected ClientTokenContext createTokenContext(ClientAccessToken at) { + protected ClientTokenContext createTokenContext(ContainerRequestContext rc, ClientAccessToken at) { OidcClientTokenContextImpl ctx = new OidcClientTokenContextImpl(); if (at != null) { ctx.setIdToken(userInfoClient.getIdToken(at, getConsumer().getKey())); - ctx.setUserInfo(userInfoClient.getUserInfo(at, ctx.getIdToken())); + if (userInfoRequired) { + ctx.setUserInfo(userInfoClient.getUserInfo(at, ctx.getIdToken())); + } + rc.setSecurityContext(new OidcSecurityContext(ctx)); } + return ctx; } public void setUserInfoClient(UserInfoClient userInfoClient) { this.userInfoClient = userInfoClient; } + public void setUserInfoRequired(boolean userInfoRequired) { + this.userInfoRequired = userInfoRequired; + } + @Override + protected void checkSecurityContextStart(SecurityContext sc) { + // The SSO is managed out of band and the act of validating IdToken + // finalizes the authentication flow + if (sc != null && sc.getUserPrincipal() != null) { + throw ExceptionUtils.toNotAuthorizedException(null, null); + } + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/6909358d/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcSecurityContext.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcSecurityContext.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcSecurityContext.java new file mode 100644 index 0000000..f8b8045 --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcSecurityContext.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.oidc.rp; + +import javax.ws.rs.core.SecurityContext; + +import org.apache.cxf.common.security.SimpleSecurityContext; +import org.apache.cxf.jaxrs.utils.HttpUtils; +import org.apache.cxf.jaxrs.utils.JAXRSUtils; + +public class OidcSecurityContext extends SimpleSecurityContext implements SecurityContext { + private OidcClientTokenContext oidcContext; + public OidcSecurityContext(OidcClientTokenContext oidcContext) { + super(getUserName(oidcContext)); + this.oidcContext = oidcContext; + } + public OidcClientTokenContext getOidcContext() { + return oidcContext; + } + private static String getUserName(OidcClientTokenContext oidcContext) { + if (oidcContext.getUserInfo() != null) { + return oidcContext.getUserInfo().getEmail(); + } else { + return oidcContext.getIdToken().getSubject(); + } + } + @Override + public boolean isSecure() { + String value = HttpUtils.getEndpointAddress(JAXRSUtils.getCurrentMessage()); + return value.startsWith("https://"); + } + @Override + public String getAuthenticationScheme() { + return "OIDC"; + } +}
