Start adding OpenID support

This commit is contained in:
James Agnew 2014-07-31 18:06:04 -04:00
parent 0ba7a63803
commit fa3bd7f25d
20 changed files with 742 additions and 33 deletions

View File

@ -46,8 +46,15 @@
<version>4.2.0</version> <version>4.2.0</version>
</dependency> </dependency>
<!-- <dependency> <groupId>org.codehaus.woodstox</groupId> <artifactId>stax2-api</artifactId> <version>3.1.3</version> </dependency> --> <!-- Only required for OpenID Connect Support -->
<dependency>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-client</artifactId>
<version>${mitreid-connect-version}</version>
<optional>true</optional>
</dependency>
<!-- Only required for narrative generator support -->
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>
@ -113,13 +120,19 @@
<artifactId>spring-beans</artifactId> <artifactId>spring-beans</artifactId>
<version>${spring_version}</version> <version>${spring_version}</version>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- Server --> <!-- Server -->
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version> <version>3.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
@ -166,8 +179,14 @@
<version>9.1.1.v20140108</version> <version>9.1.1.v20140108</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.1.1.v20140108</version>
<scope>test</scope>
</dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId> <artifactId>mockito-all</artifactId>
@ -194,20 +213,17 @@
<classifier>jdk15-sources</classifier> <classifier>jdk15-sources</classifier>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- TODO: why is this here? -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>directory-naming</groupId> <groupId>directory-naming</groupId>
<artifactId>naming-java</artifactId> <artifactId>naming-java</artifactId>
<version>0.8</version> <version>0.8</version>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
@ -215,6 +231,23 @@
<version>${hamcrest_version}</version> <version>${hamcrest_version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring_security_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring_security_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>
</dependencies> </dependencies>

View File

@ -25,8 +25,8 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
/** /**
* Created by dsotnikov on 3/7/2014. * @deprecated Use {@link ca.uhn.fhir.rest.server.security.ISecurityManager} instead
*/ */
public interface ISecurityManager { public interface ISecurityManager {
public void authenticate(HttpServletRequest request) throws AuthenticationException; public void authenticate(HttpServletRequest request) throws AuthenticationException;
} }

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.server;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
@ -82,7 +82,6 @@ import ca.uhn.fhir.util.VersionUtil;
public class RestfulServer extends HttpServlet { public class RestfulServer extends HttpServlet {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.server.exceptions; package ca.uhn.fhir.rest.server.exceptions;
import java.text.ParseException;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
/* /*
@ -22,23 +24,25 @@ import ca.uhn.fhir.rest.server.Constants;
* #L% * #L%
*/ */
/** /**
* Represents an <b>HTTP 401 Client Unauthorized</b> response, which means that * Represents an <b>HTTP 401 Client Unauthorized</b> response, which means that the client needs to provide credentials, or has provided invalid credentials.
* the client needs to provide credentials, or has provided invalid credentials.
*/ */
public class AuthenticationException extends BaseServerResponseException { public class AuthenticationException extends BaseServerResponseException {
public static final int STATUS_CODE = Constants.STATUS_HTTP_401_CLIENT_UNAUTHORIZED; public static final int STATUS_CODE = Constants.STATUS_HTTP_401_CLIENT_UNAUTHORIZED;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public AuthenticationException() { public AuthenticationException() {
super(STATUS_CODE, "Client unauthorized"); super(STATUS_CODE, "Client unauthorized");
} }
public AuthenticationException(String theMessage) { public AuthenticationException(String theMessage) {
super(STATUS_CODE, theMessage); super(STATUS_CODE, theMessage);
} }
public AuthenticationException(String theMessage, ParseException theCause) {
super(STATUS_CODE, theMessage, theCause);
}
} }

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.rest.server.security;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* Licensed 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.
* #L%
*/
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
/**
* Implementations of this interface provide authorization to incoming service calls
*
*/
public interface IResourceSecurity {
public ISecurityOutcome authenticate(HttpServletRequest request) throws AuthenticationException;
}

View File

@ -0,0 +1,5 @@
package ca.uhn.fhir.rest.server.security;
public interface ISecurityOutcome {
}

View File

@ -0,0 +1,162 @@
package ca.uhn.fhir.rest.server.security;
import java.text.ParseException;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
import org.mitre.oauth2.model.RegisteredClient;
import org.mitre.openid.connect.client.service.ClientConfigurationService;
import org.mitre.openid.connect.client.service.ServerConfigurationService;
import org.mitre.openid.connect.config.ServerConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class OpenIdConnectBearerTokenSecurityManager implements IResourceSecurity {
private static final String BEARER_PREFIX = "Bearer ";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OpenIdConnectBearerTokenSecurityManager.class);
@Autowired
private ClientConfigurationService myClientConfigurationService;
@Autowired
private ServerConfigurationService myServerConfigurationService;
private int myTimeSkewAllowance = 300;
private SymmetricCacheService symmetricCacheService;
private JWKSetCacheService validationServices;
public OpenIdConnectBearerTokenSecurityManager() {
symmetricCacheService = new SymmetricCacheService();
validationServices = new JWKSetCacheService();
}
@Override
public ISecurityOutcome authenticate(HttpServletRequest theRequest) throws AuthenticationException {
String token = theRequest.getHeader(Constants.HEADER_AUTHORIZATION);
if (token == null) {
throw new AuthenticationException("Not authorized (no authorization header found in request)");
}
if (!token.startsWith(BEARER_PREFIX)) {
throw new AuthenticationException("Not authorized (authorization header does not contain a bearer token)");
}
token = token.substring(BEARER_PREFIX.length());
SignedJWT idToken;
try {
idToken = SignedJWT.parse(token);
} catch (ParseException e) {
throw new AuthenticationException("Not authorized (bearer token could not be validated)", e);
}
// validate our ID Token over a number of tests
ReadOnlyJWTClaimsSet idClaims;
try {
idClaims = idToken.getJWTClaimsSet();
} catch (ParseException e) {
throw new AuthenticationException("Not authorized (bearer token could not be validated)", e);
}
String issuer = idClaims.getIssuer();
ServerConfiguration serverConfig = myServerConfigurationService.getServerConfiguration(issuer);
if (serverConfig == null) {
ourLog.error("No server configuration found for issuer: " + issuer);
throw new AuthenticationException("Not authorized (no server configuration found for issuer " + issuer + ")");
}
RegisteredClient clientConfig = myClientConfigurationService.getClientConfiguration(serverConfig);
if (clientConfig == null) {
ourLog.error("No client configuration found for issuer: " + issuer);
throw new AuthenticationException("Not authorized (no client configuration found for issuer " + issuer + ")");
}
// check the signature
JwtSigningAndValidationService jwtValidator = null;
JWSAlgorithm alg = idToken.getHeader().getAlgorithm();
if (alg.equals(JWSAlgorithm.HS256) || alg.equals(JWSAlgorithm.HS384) || alg.equals(JWSAlgorithm.HS512)) {
// generate one based on client secret
jwtValidator = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient());
} else {
// otherwise load from the server's public key
jwtValidator = validationServices.getValidator(serverConfig.getJwksUri());
}
if (jwtValidator != null) {
if (!jwtValidator.validateSignature(idToken)) {
throw new AuthenticationException("Not authorized (signature validation failed)");
}
} else {
ourLog.error("No validation service found. Skipping signature validation");
throw new AuthenticationException("Not authorized (can't determine signature validator)");
}
// check expiration
if (idClaims.getExpirationTime() == null) {
throw new AuthenticationException("Id Token does not have required expiration claim");
} else {
// it's not null, see if it's expired
Date now = new Date(System.currentTimeMillis() - (myTimeSkewAllowance * 1000));
if (now.after(idClaims.getExpirationTime())) {
throw new AuthenticationException("Id Token is expired: " + idClaims.getExpirationTime());
}
}
// check not before
if (idClaims.getNotBeforeTime() != null) {
Date now = new Date(System.currentTimeMillis() + (myTimeSkewAllowance * 1000));
if (now.before(idClaims.getNotBeforeTime())) {
throw new AuthenticationException("Id Token not valid untill: " + idClaims.getNotBeforeTime());
}
}
// check issued at
if (idClaims.getIssueTime() == null) {
throw new AuthenticationException("Id Token does not have required issued-at claim");
} else {
// since it's not null, see if it was issued in the future
Date now = new Date(System.currentTimeMillis() + (myTimeSkewAllowance * 1000));
if (now.before(idClaims.getIssueTime())) {
throw new AuthenticationException("Id Token was issued in the future: " + idClaims.getIssueTime());
}
}
return new ISecurityOutcome() {
};
}
public int getTimeSkewAllowance() {
return myTimeSkewAllowance;
}
public void setClientConfigurationService(ClientConfigurationService theClientConfigurationService) {
myClientConfigurationService = theClientConfigurationService;
}
public void setServerConfigurationService(ServerConfigurationService theServerConfigurationService) {
myServerConfigurationService = theServerConfigurationService;
}
public void setTimeSkewAllowance(int theTimeSkewAllowance) {
myTimeSkewAllowance = theTimeSkewAllowance;
}
public void setValidationServices(JWKSetCacheService theValidationServices) {
validationServices = theValidationServices;
}
}

View File

@ -65,7 +65,7 @@ IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "ht
annotationClient.registerInterceptor(loggingInterceptor); annotationClient.registerInterceptor(loggingInterceptor);
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
annotationClient.registerInterceptor(loggingInterceptor); genericClient.registerInterceptor(loggingInterceptor);
//END SNIPPET: logging //END SNIPPET: logging
} }

View File

@ -71,8 +71,8 @@ import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.annotation.VersionIdParam; import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.CodingListParam; import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider; import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;

View File

@ -0,0 +1,183 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class ServerSecurityTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerSecurityTest.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
@BeforeClass
public static void beforeClass() throws Exception {
ourCtx = new FhirContext(Patient.class);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
@Test
public void testContextWithSpace() throws Exception {
if (true) return;
int port = RandomServerPortProvider.findFreePort();
Server server = new Server(port);
RestfulServer restServer = new RestfulServer();
restServer.setFhirContext(ourCtx);
restServer.setResourceProviders(new DummyPatientResourceProvider());
// ServletHandler proxyHandler = new ServletHandler();
ServletHolder servletHolder = new ServletHolder(restServer);
WebAppContext wac = new WebAppContext();
wac.setWar("src/test/resources/securitytest_war");
// wac.addServlet(servletHolder, "/fhir/*");
ServletContextHandler ch = new ServletContextHandler();
ch.setContextPath("/");
ch.addServlet(servletHolder, "/fhir/*");
// ch.add
// ch.addFilter(org.springframework.web.filter.DelegatingFilterProxy.class, "/*", null);
ContextHandlerCollection contexts = new ContextHandlerCollection();
server.setHandler(wac);
// server.setHandler(ch);
server.start();
try {
String baseUri = "http://localhost:" + port + "/fhir";
String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001";
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader("Authorization", "Basic eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MDY4MzU3MjMsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvdWhudmVzYjAxZC51aG4ub24uY2E6MjUxODBcL3Vobi1vcGVuaWQtY29ubmVjdC1zZXJ2ZXJcLyIsImp0aSI6IjEwZTYwYWY1LTEyZmUtNDFhYy05MWQyLTliNWY0NzBiNGM5OSIsImlhdCI6MTQwNjgzMjEyM30.LaCAOmoM0ikkaalKBfccU_YE8NBXvT5M7L9ITfR86vEp5-3_kVeSrYHI4CjVUSDafyVwQF4x5eJVSZdtQxtEk9F1L6be_JQI84crqff53EKA10z7m9Lkc5jJFs6sd9cnlayhlBgxL7BNdSf0Bjs7aS6YMEcn9IY3RNSeQj7kjhs");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
assertEquals(1, bundle.getEntries().size());
} finally {
server.stop();
}
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
Patient patient = new Patient();
patient.setId("1");
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
idToPatient.put("1", patient);
}
{
Patient patient = new Patient();
patient.setId("2");
patient.getIdentifier().add(new IdentifierDt());
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00002");
patient.getName().add(new HumanNameDt());
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.getGender().setText("F");
idToPatient.put("2", patient);
}
return idToPatient;
}
@Search()
public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
for (Patient next : getIdToPatient().values()) {
for (IdentifierDt nextId : next.getIdentifier()) {
if (nextId.matchesSystemAndValue(theIdentifier)) {
return next;
}
}
}
return null;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Read()
public Patient getResourceById(@IdParam IdDt theId) {
return getIdToPatient().get(theId.getValue());
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
}
}

View File

@ -0,0 +1,15 @@
package ca.uhn.fhir.rest.server;
import javax.servlet.ServletException;
import ca.uhn.fhir.rest.server.ServerSecurityTest.DummyPatientResourceProvider;
public class ServerSecurityTestRestfulServlet extends RestfulServer
{
@Override
protected void initialize() throws ServletException {
setResourceProviders(new DummyPatientResourceProvider());
}
}

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.rest.server.security;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.hamcrest.core.StringContains;
import org.junit.Test;
import org.mitre.jose.keystore.JWKSetKeyStore;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
import org.mitre.oauth2.model.RegisteredClient;
import org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService;
import org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService;
import org.mitre.openid.connect.client.service.impl.StaticServerConfigurationService;
import org.mitre.openid.connect.config.ServerConfiguration;
import org.springframework.core.io.ClassPathResource;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
public class OpenIdConnectBearerTokenSecurityManagerTest {
@Test
public void testValidateToken() throws Exception {
StaticServerConfigurationService srv = new StaticServerConfigurationService();
srv.setServers(new HashMap<String, ServerConfiguration>());
ServerConfiguration srvCfg = new ServerConfiguration();
srvCfg.setJwksUri("AAAAAA");
srvCfg.setIssuer("http://localhost:8888/uhn-openid-connect/");
srv.getServers().put("http://localhost:8888/uhn-openid-connect/", srvCfg);
srv.afterPropertiesSet();
StaticClientConfigurationService cli = new StaticClientConfigurationService();
cli.setClients(new HashMap<String, RegisteredClient>());
cli.getClients().put("http://localhost:8888/uhn-openid-connect/", new RegisteredClient());
OpenIdConnectBearerTokenSecurityManager mgr = new OpenIdConnectBearerTokenSecurityManager();
mgr.setClientConfigurationService(cli);
mgr.setServerConfigurationService(srv);
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeader(Constants.HEADER_AUTHORIZATION)).thenReturn("Bearer " + "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MDY4NDE4NTgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0Ojg4ODhcL3Vobi1vcGVuaWQtY29ubmVjdFwvIiwianRpIjoiOTNiMzRjOTUtNTNiMC00YzZmLTkwYjEtYWVjODRjZTc3OGFhIiwiaWF0IjoxNDA2ODM4MjU4fQ.fYtwehPUulUYnDG_10bN6TNf7uw2FNUh_E40YagpITrVfXsV06pjU2YpNgy8nbSFmxY9IBH44UXTmMH9PLFiRn88WsPMSrUQbFCcvGIYwhqkRjGm_J1Y6oWIafUzCwZBCvk4Ne44p3DJRR6FSZRnnC850p55901DGQmNLe-rZJk3t0MHl6wySduqT3K1-Vbuq-7H6xLE10hKpLhSqBTghpQNKNjm48jm0sHcFa3ENWzyWPOmpNfzDKmJAYK2UnBtqNSJP6AJzVrJXqSu-uzasq0VOVcRU4n8b39vU1olbho1eKF0cfQlQwbrtvWipBJJSsRp_tmB9SV9BXhENxOFTw");
JWKSetCacheService val = mock(JWKSetCacheService.class);
JWKSetKeyStore keyStore = new JWKSetKeyStore();
keyStore.setLocation(new ClassPathResource("/svr_keystore.jwks"));
DefaultJwtSigningAndValidationService valSvc = new DefaultJwtSigningAndValidationService(keyStore);
when(val.getValidator("AAAAAA")).thenReturn(valSvc);
mgr.setValidationServices(val);
try {
mgr.authenticate(req);
fail();
} catch (AuthenticationException e) {
assertThat(e.getMessage(), StringContains.containsString("expired"));
}
mgr.setTimeSkewAllowance(10 * 365 * 24 * 60);
mgr.authenticate(req);
}
}

View File

@ -0,0 +1,18 @@
package ca.uhn.fhir.rest.server.security;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
@Override
public void commence( HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException ) throws IOException{
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
}
}

View File

@ -7,8 +7,10 @@
</encoder> </encoder>
</appender> </appender>
<!--
<logger name="org.eclipse" additivity="false"> <logger name="org.eclipse" additivity="false">
</logger> </logger>
-->
<!-- <!--
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace"> <logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace">

View File

@ -0,0 +1,133 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<bean id="fhirContext" class="ca.uhn.fhir.context.FhirContext">
</bean>
<bean id="authenticationEntryPoint"
class="ca.uhn.fhir.rest.server.sec.RestAuthenticationEntryPoint" />
<security:global-method-security pre-post-annotations="enabled" proxy-target-class="true" authentication-manager-ref="authenticationManager"/>
<security:http auto-config="false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint" authentication-manager-ref="authenticationManager"
pattern="/**">
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<security:custom-filter before="PRE_AUTH_FILTER" ref="openIdConnectAuthenticationFilter" />
</security:http>
<!--
<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/openid_connect_login" />
</bean>
-->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="openIdConnectAuthenticationProvider" />
</security:authentication-manager>
<bean
class="org.mitre.openid.connect.client.service.impl.StaticSingleIssuerService"
id="staticIssuerService">
<property name="issuer" value="http://uhnvesb01d.uhn.on.ca:25180/uhn-openid-connect-server/" />
</bean>
<bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OIDCAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="issuerService" ref="staticIssuerService" />
<property name="serverConfigurationService" ref="dynamicServerConfigurationService" />
<property name="clientConfigurationService" ref="dynamicClientConfigurationService" />
<property name="authRequestOptionsService" ref="staticAuthRequestOptionsService" />
<property name="authRequestUrlBuilder" ref="plainAuthRequestUrlBuilder" />
</bean>
<bean
class="org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService"
id="dynamicServerConfigurationService" />
<bean class="org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService" id="dynamicClientConfigurationService">
<property name="template">
<bean class="org.mitre.oauth2.model.RegisteredClient">
<property name="clientName" value="Simple Web App" />
<property name="scope">
<set value-type="java.lang.String">
<value>openid</value>
<value>email</value>
<value>address</value>
<value>profile</value>
<value>phone</value>
</set>
</property>
<property name="tokenEndpointAuthMethod" value="SECRET_BASIC" />
<property name="redirectUris">
<set>
<value>http://localhost:8080/</value>
</set>
</property>
</bean>
</property>
<!--
Registered Client Service. Uncomment this to save dynamically registered clients out to a
file on disk (indicated by the filename property) or replace this with another implementation
of RegisteredClientService. This defaults to an in-memory implementation of RegisteredClientService
which will forget and re-register all clients on restart.
-->
<!--
<property name="registeredClientService">
<bean class="org.mitre.openid.connect.client.service.impl.JsonFileRegisteredClientService">
<constructor-arg name="filename" value="/tmp/simple-web-app-clients.json" />
</bean>
</property>
-->
</bean>
<bean class="org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService" id="staticAuthRequestOptionsService">
<property name="options">
<map>
<!-- Entries in this map are sent as key-value parameters to the auth request -->
<!--
<entry key="display" value="page" />
<entry key="prompt" value="consent" />
-->
</map>
</property>
</bean>
<bean class="org.mitre.openid.connect.client.service.impl.PlainAuthRequestUrlBuilder" id="plainAuthRequestUrlBuilder" />
<bean id="openIdConnectAuthenticationProvider" class="org.mitre.openid.connect.client.OIDCAuthenticationProvider">
<property name="authoritiesMapper">
<bean class="org.mitre.openid.connect.client.NamedAdminAuthoritiesMapper">
<property name="admins" ref="namedAdmins" />
</bean>
</property>
</bean>
<util:set id="namedAdmins" value-type="org.mitre.openid.connect.client.SubjectIssuerGrantedAuthority">
<!--
This is an example of how to set up a user as an administrator: they'll be given ROLE_ADMIN in addition to ROLE_USER.
Note that having an administrator role on the IdP doesn't grant administrator access on this client.
These are values from the demo "openid-connect-server-webapp" project of MITREid Connect.
-->
<bean class="org.mitre.openid.connect.client.SubjectIssuerGrantedAuthority">
<constructor-arg name="subject" value="90342.ASDFJWFA" />
<constructor-arg name="issuer" value="http://localhost:8080/openid-connect-server-webapp/" />
</bean>
</util:set>
</beans>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5">
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
WEB-INF/app-ctx.xml
</param-value>
</context-param>
<servlet>
<servlet-name>fhir</servlet-name>
<servlet-class>ca.uhn.fhir.rest.server.ServerSecurityTestRestfulServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>fhir</servlet-name>
<url-pattern>/fhir/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -0,0 +1,11 @@
{
"keys": [
{
"d": "S0pTrzR78vyso2ufMTAJMPNpqSgF0jggquQ2crgBw_il-QQ5zAHzlrreGWDcvr94i6PAy4xQYRAAaO_ZtjO4kMmEKdJ-bgsAJiIrUAlJdv0JCK-j3G64R0EiwrSzXqyt3E6kiA14o5wYgs86RO7TV6dpFyNuSIoZJeNG-NBviPIwY222PX07xjaMk7tSQdlTP6Vg1EZV-i0AqYGE-Ivn2eaI0KvjzPWVXVki1F6J4yQzErxcVILgicAp6Eeqy24E3enmrsQozj54Sswjy8r_MrGf2iSaf0GedLsCXWSQ-uojWF6llfjL9Q5be65ztPkvN4cPGn0o_sHzqewvFnZuAQ",
"e": "AQAB",
"n": "kmjocunUF1bw2rSnBtiA9P2LJcuUdTSpgS8e2MccFv0VLNFd50P7JMxmLt_ildr040371QPflz0wdE_O8gypJr17RyJCg7mthCRpNLxkVHS8Pd3p0fzn_xlmrm4gkR033k7aTH3bB-usjq5Z8A6GjxlUp2Of5TRJ1Tj9287m9X2w65DDCcOQYC8horObEEUkchfpyf5PGCdkq-1U46_P66RcFuLjxaO6R5OWrVK8Zd-tHOG-O3-nn0eAHxFvtGDiYOVImQ7rWEriTDQdefiIIOM_m73xbtdVM2jEgQfn76u0TvK7nLmz1w4XvddOv2TatXmAtNmTpP16629eqnKcMw",
"kty": "RSA",
"kid": "rsa1"
}
]
}

View File

@ -6,10 +6,10 @@
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/> <wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/> <wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<dependent-module archiveName="hapi-fhir-jpaserver-base-0.5.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-jpaserver-base/hapi-fhir-jpaserver-base"> <dependent-module archiveName="hapi-fhir-jpaserver-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-jpaserver-base/hapi-fhir-jpaserver-base">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>
<dependent-module archiveName="hapi-fhir-base-0.5.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base"> <dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF"> <dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">

View File

@ -9,7 +9,6 @@
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-tinder-plugin</artifactId> <artifactId>hapi-tinder-plugin</artifactId>
<packaging>maven-plugin</packaging> <packaging>maven-plugin</packaging>
<!-- <packaging>jar</packaging> --> <!-- <packaging>jar</packaging> -->

View File

@ -75,16 +75,18 @@
<hibernate_validator_version>5.1.0.Final</hibernate_validator_version> <hibernate_validator_version>5.1.0.Final</hibernate_validator_version>
<jetty_version>9.1.1.v20140108</jetty_version> <jetty_version>9.1.1.v20140108</jetty_version>
<jetty_version>9.1.1.v20140108</jetty_version> <jetty_version>9.1.1.v20140108</jetty_version>
<jscience_version>4.3.1</jscience_version>
<junit_version>4.11</junit_version> <junit_version>4.11</junit_version>
<logback_version>1.1.1</logback_version> <logback_version>1.1.1</logback_version>
<maven_javadoc_plugin_version>2.9.1</maven_javadoc_plugin_version> <maven_javadoc_plugin_version>2.9.1</maven_javadoc_plugin_version>
<maven_site_plugin_version>3.3</maven_site_plugin_version> <maven_site_plugin_version>3.3</maven_site_plugin_version>
<mitreid-connect-version>1.1.8</mitreid-connect-version>
<mockito_version>1.9.5</mockito_version> <mockito_version>1.9.5</mockito_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j_version>1.7.7</slf4j_version> <slf4j_version>1.7.7</slf4j_version>
<spring_version>4.0.1.RELEASE</spring_version> <spring_version>4.0.1.RELEASE</spring_version>
<spring_security_version>3.2.4.RELEASE</spring_security_version>
<thymeleaf-version>2.1.3.RELEASE</thymeleaf-version> <thymeleaf-version>2.1.3.RELEASE</thymeleaf-version>
<jscience_version>4.3.1</jscience_version>
</properties> </properties>
<build> <build>