Start adding OpenID support
This commit is contained in:
parent
0ba7a63803
commit
fa3bd7f25d
|
@ -46,8 +46,15 @@
|
|||
<version>4.2.0</version>
|
||||
</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>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
|
@ -113,13 +120,19 @@
|
|||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Server -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>3.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -166,8 +179,14 @@
|
|||
<version>9.1.1.v20140108</version>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
|
@ -194,20 +213,17 @@
|
|||
<classifier>jdk15-sources</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TODO: why is this here? -->
|
||||
<dependency>
|
||||
<groupId>commons-httpclient</groupId>
|
||||
<artifactId>commons-httpclient</artifactId>
|
||||
<version>3.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>directory-naming</groupId>
|
||||
<artifactId>naming-java</artifactId>
|
||||
<version>0.8</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
|
@ -215,6 +231,23 @@
|
|||
<version>${hamcrest_version}</version>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
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 void authenticate(HttpServletRequest request) throws AuthenticationException;
|
||||
public void authenticate(HttpServletRequest request) throws AuthenticationException;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.server;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
@ -82,7 +82,6 @@ import ca.uhn.fhir.util.VersionUtil;
|
|||
|
||||
public class RestfulServer extends HttpServlet {
|
||||
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
/*
|
||||
|
@ -22,23 +24,25 @@ import ca.uhn.fhir.rest.server.Constants;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Represents an <b>HTTP 401 Client Unauthorized</b> response, which means that
|
||||
* the client needs to provide credentials, or has provided invalid credentials.
|
||||
* Represents an <b>HTTP 401 Client Unauthorized</b> response, which means that the client needs to provide credentials, or has provided invalid credentials.
|
||||
*/
|
||||
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;
|
||||
|
||||
public AuthenticationException() {
|
||||
super(STATUS_CODE, "Client unauthorized");
|
||||
}
|
||||
public AuthenticationException() {
|
||||
super(STATUS_CODE, "Client unauthorized");
|
||||
}
|
||||
|
||||
public AuthenticationException(String theMessage) {
|
||||
super(STATUS_CODE, theMessage);
|
||||
}
|
||||
public AuthenticationException(String theMessage) {
|
||||
super(STATUS_CODE, theMessage);
|
||||
}
|
||||
|
||||
public AuthenticationException(String theMessage, ParseException theCause) {
|
||||
super(STATUS_CODE, theMessage, theCause);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package ca.uhn.fhir.rest.server.security;
|
||||
|
||||
public interface ISecurityOutcome {
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -65,7 +65,7 @@ IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "ht
|
|||
annotationClient.registerInterceptor(loggingInterceptor);
|
||||
|
||||
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
|
||||
annotationClient.registerInterceptor(loggingInterceptor);
|
||||
genericClient.registerInterceptor(loggingInterceptor);
|
||||
//END SNIPPET: logging
|
||||
}
|
||||
|
||||
|
|
|
@ -71,8 +71,8 @@ import ca.uhn.fhir.rest.annotation.Validate;
|
|||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
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.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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" );
|
||||
}
|
||||
}
|
|
@ -7,8 +7,10 @@
|
|||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--
|
||||
<logger name="org.eclipse" additivity="false">
|
||||
</logger>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace">
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -6,10 +6,10 @@
|
|||
<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="/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>
|
||||
</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>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-tinder-plugin</artifactId>
|
||||
<packaging>maven-plugin</packaging>
|
||||
<!-- <packaging>jar</packaging> -->
|
||||
|
|
4
pom.xml
4
pom.xml
|
@ -75,16 +75,18 @@
|
|||
<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>
|
||||
<jscience_version>4.3.1</jscience_version>
|
||||
<junit_version>4.11</junit_version>
|
||||
<logback_version>1.1.1</logback_version>
|
||||
<maven_javadoc_plugin_version>2.9.1</maven_javadoc_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>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<slf4j_version>1.7.7</slf4j_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>
|
||||
<jscience_version>4.3.1</jscience_version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
|
|
Loading…
Reference in New Issue