Fix #406 - Allow arbitrary authentication realm
This commit is contained in:
parent
dd8b1cd979
commit
545b359697
|
@ -53,4 +53,14 @@ public class BasicSecurityInterceptor extends InterceptorAdapter
|
|||
}
|
||||
//END SNIPPET: basicAuthInterceptor
|
||||
|
||||
|
||||
|
||||
public void basicAuthInterceptorRealm() {
|
||||
//START SNIPPET: basicAuthInterceptorRealm
|
||||
AuthenticationException ex = new AuthenticationException();
|
||||
ex.addAuthenticateHeaderForRealm("myRealm");
|
||||
throw ex;
|
||||
//END SNIPPET: basicAuthInterceptorRealm
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
@ -1522,6 +1523,15 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
private void writeExceptionToResponse(HttpServletResponse theResponse, BaseServerResponseException theException) throws IOException {
|
||||
theResponse.setStatus(theException.getStatusCode());
|
||||
addHeadersToResponse(theResponse);
|
||||
if (theException.hasResponseHeaders()) {
|
||||
for (Entry<String, List<String>> nextEntry : theException.getResponseHeaders().entrySet()) {
|
||||
for (String nextValue : nextEntry.getValue()) {
|
||||
if (isNotBlank(nextValue)) {
|
||||
theResponse.addHeader(nextEntry.getKey(), nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
theResponse.setContentType("text/plain");
|
||||
theResponse.setCharacterEncoding("UTF-8");
|
||||
theResponse.getWriter().write(theException.getMessage());
|
||||
|
|
|
@ -44,5 +44,16 @@ public class AuthenticationException extends BaseServerResponseException {
|
|||
public AuthenticationException(String theMessage, Throwable theCause) {
|
||||
super(STATUS_CODE, theMessage, theCause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a <code>WWW-Authenticate</code> header to the response, of the form:<br/>
|
||||
* <code>WWW-Authenticate: Basic realm="theRealm"</code>
|
||||
*
|
||||
* @return Returns a reference to <code>this</code> for easy method chaining
|
||||
*/
|
||||
public AuthenticationException addAuthenticateHeaderForRealm(String theRealm) {
|
||||
addResponseHeader("WWW-Authenticate", "Basic realm=\"" + theRealm + "\"");
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
|
@ -70,13 +72,10 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
private List<String> myAdditionalMessages = null;
|
||||
private IBaseOperationOutcome myBaseOperationOutcome;
|
||||
private String myResponseBody;
|
||||
private Map<String, List<String>> myResponseHeaders;
|
||||
private String myResponseMimeType;
|
||||
private int myStatusCode;
|
||||
|
||||
public static void main (String[] args) {
|
||||
BaseServerResponseException.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -90,7 +89,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
myStatusCode = theStatusCode;
|
||||
myBaseOperationOutcome = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -107,7 +106,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -188,15 +187,26 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
myBaseOperationOutcome = theBaseOperationOutcome;
|
||||
}
|
||||
|
||||
public List<String> getAdditionalMessages() {
|
||||
return myAdditionalMessages;
|
||||
/**
|
||||
* Add a header which will be added to any responses
|
||||
*
|
||||
* @param theName The header name
|
||||
* @param theValue The header value
|
||||
* @return Returns a reference to <code>this</code> for easy method chaining
|
||||
* @since 2.0
|
||||
*/
|
||||
public BaseServerResponseException addResponseHeader(String theName, String theValue) {
|
||||
Validate.notBlank(theName, "theName must not be null or empty");
|
||||
Validate.notBlank(theValue, "theValue must not be null or empty");
|
||||
if (getResponseHeaders().containsKey(theName) == false) {
|
||||
getResponseHeaders().put(theName, new ArrayList<String>());
|
||||
}
|
||||
getResponseHeaders().get(theName).add(theValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP headers associated with this exception.
|
||||
*/
|
||||
public Map<String, String[]> getAssociatedHeaders() {
|
||||
return Collections.emptyMap();
|
||||
public List<String> getAdditionalMessages() {
|
||||
return myAdditionalMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,6 +226,21 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
return myResponseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map containing any headers which should be added to the outgoing
|
||||
* response. This methos creates the map if none exists, so it will never
|
||||
* return <code>null</code>
|
||||
*
|
||||
* @since 2.0 (note that this method existed in previous versions of HAPI but the method
|
||||
* signature has been changed from <code>Map<String, String[]></code> to <code>Map<String, List<String>></code>
|
||||
*/
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
if (myResponseHeaders == null) {
|
||||
myResponseHeaders = new HashMap<String, List<String>>();
|
||||
}
|
||||
return myResponseHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response.
|
||||
* <p>
|
||||
|
@ -233,6 +258,16 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
return myStatusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the exception have any headers which should be added to the outgoing response?
|
||||
*
|
||||
* @see #getResponseHeaders()
|
||||
* @since 2.0
|
||||
*/
|
||||
public boolean hasResponseHeaders() {
|
||||
return myResponseHeaders != null && myResponseHeaders.isEmpty() == false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client
|
||||
* implementations you should not call this method.
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
@ -20,7 +18,7 @@ import ca.uhn.fhir.rest.server.Constants;
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -48,11 +46,11 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
* Constructor
|
||||
*
|
||||
* @param theMessage
|
||||
* The message
|
||||
* The message
|
||||
* @param theOperationOutcome
|
||||
* The OperationOutcome resource to return to the client
|
||||
* The OperationOutcome resource to return to the client
|
||||
* @param theAllowedMethods
|
||||
* A list of allowed methods (see {@link #setAllowedMethods(RequestTypeEnum...)} )
|
||||
* A list of allowed methods (see {@link #setAllowedMethods(RequestTypeEnum...)} )
|
||||
*/
|
||||
public MethodNotAllowedException(String theMessage, IBaseOperationOutcome theOperationOutcome, RequestTypeEnum... theAllowedMethods) {
|
||||
super(STATUS_CODE, theMessage, theOperationOutcome);
|
||||
|
@ -63,9 +61,9 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
* Constructor
|
||||
*
|
||||
* @param theMessage
|
||||
* The message
|
||||
* The message
|
||||
* @param theAllowedMethods
|
||||
* A list of allowed methods (see {@link #setAllowedMethods(RequestTypeEnum...)} )
|
||||
* A list of allowed methods (see {@link #setAllowedMethods(RequestTypeEnum...)} )
|
||||
*/
|
||||
public MethodNotAllowedException(String theMessage, RequestTypeEnum... theAllowedMethods) {
|
||||
super(STATUS_CODE, theMessage);
|
||||
|
@ -76,9 +74,9 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
* Constructor
|
||||
*
|
||||
* @param theMessage
|
||||
* The message
|
||||
* The message
|
||||
* @param theOperationOutcome
|
||||
* The OperationOutcome resource to return to the client
|
||||
* The OperationOutcome resource to return to the client
|
||||
*/
|
||||
public MethodNotAllowedException(String theMessage, IBaseOperationOutcome theOperationOutcome) {
|
||||
super(STATUS_CODE, theMessage, theOperationOutcome);
|
||||
|
@ -88,7 +86,7 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
* Constructor
|
||||
*
|
||||
* @param theMessage
|
||||
* The message
|
||||
* The message
|
||||
*/
|
||||
public MethodNotAllowedException(String theMessage) {
|
||||
super(STATUS_CODE, theMessage);
|
||||
|
@ -101,22 +99,6 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
return myAllowedMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getAssociatedHeaders() {
|
||||
if (myAllowedMethods != null && myAllowedMethods.size() > 0) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (RequestTypeEnum next : myAllowedMethods) {
|
||||
if (b.length() > 0) {
|
||||
b.append(',');
|
||||
}
|
||||
b.append(next.name());
|
||||
}
|
||||
return Collections.singletonMap(Constants.HEADER_ALLOW, new String[] { b.toString() });
|
||||
} else {
|
||||
return super.getAssociatedHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the list of allowed HTTP methods (GET, POST, etc). This is provided in an <code>Allow</code> header, as required by the HTTP specification (RFC 2616).
|
||||
*/
|
||||
|
@ -129,6 +111,7 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
myAllowedMethods.add(next);
|
||||
}
|
||||
}
|
||||
updateAllowHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,6 +119,20 @@ public class MethodNotAllowedException extends BaseServerResponseException {
|
|||
*/
|
||||
public void setAllowedMethods(Set<RequestTypeEnum> theAllowedMethods) {
|
||||
myAllowedMethods = theAllowedMethods;
|
||||
updateAllowHeader();
|
||||
}
|
||||
|
||||
private void updateAllowHeader() {
|
||||
getResponseHeaders().remove(Constants.HEADER_ALLOW);
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (RequestTypeEnum next : myAllowedMethods) {
|
||||
if (b.length() > 0) {
|
||||
b.append(',');
|
||||
}
|
||||
b.append(next.name());
|
||||
}
|
||||
addResponseHeader(Constants.HEADER_ALLOW, b.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
@ -68,9 +69,9 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
|
|||
int statusCode = theException.getStatusCode();
|
||||
|
||||
// Add headers associated with the specific error code
|
||||
Map<String, String[]> additional = theException.getAssociatedHeaders();
|
||||
if (additional != null) {
|
||||
for (Entry<String, String[]> next : additional.entrySet()) {
|
||||
if (theException.hasResponseHeaders()) {
|
||||
Map<String, List<String>> additional = theException.getResponseHeaders();
|
||||
for (Entry<String, List<String>> next : additional.entrySet()) {
|
||||
if (isNotBlank(next.getKey()) && next.getValue() != null) {
|
||||
String nextKey = next.getKey();
|
||||
for (String nextValue : next.getValue()) {
|
||||
|
@ -79,7 +80,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String statusMessage = null;
|
||||
if (theException instanceof UnclassifiedServerFailureException) {
|
||||
String sm = theException.getMessage();
|
||||
|
@ -89,12 +90,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
|
|||
}
|
||||
|
||||
return response.streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, statusMessage, false, false);
|
||||
// theResponse.setStatus(statusCode);
|
||||
// theRequestDetails.getServer().addHeadersToResponse(theResponse);
|
||||
// theResponse.setContentType("text/plain");
|
||||
// theResponse.setCharacterEncoding("UTF-8");
|
||||
// theResponse.getWriter().append(theException.getMessage());
|
||||
// theResponse.getWriter().close();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
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.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class ServerExceptionDstu3Test {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerExceptionDstu3Test.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
public static BaseServerResponseException ourException;
|
||||
|
||||
@Test
|
||||
public void testAddHeadersNotFound() throws Exception {
|
||||
|
||||
OperationOutcome operationOutcome = new OperationOutcome();
|
||||
operationOutcome.addIssue().setCode(IssueType.BUSINESSRULE);
|
||||
|
||||
ourException = new ResourceNotFoundException("SOME MESSAGE");
|
||||
ourException.addResponseHeader("X-Foo", "BAR BAR");
|
||||
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(status.getStatusLine().toString());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(404, status.getStatusLine().getStatusCode());
|
||||
assertEquals("BAR BAR", status.getFirstHeader("X-Foo").getValue());
|
||||
assertThat(status.getFirstHeader("X-Powered-By").getValue(), containsString("HAPI FHIR"));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorize() throws Exception {
|
||||
|
||||
OperationOutcome operationOutcome = new OperationOutcome();
|
||||
operationOutcome.addIssue().setCode(IssueType.BUSINESSRULE);
|
||||
|
||||
ourException = new AuthenticationException().addAuthenticateHeaderForRealm("REALM");
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(status.getStatusLine().toString());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(401, status.getStatusLine().getStatusCode());
|
||||
assertEquals("Basic realm=\"REALM\"", status.getFirstHeader("WWW-Authenticate").getValue());
|
||||
} finally {
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> search() {
|
||||
throw ourException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.stringContainsInOrder;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -21,13 +22,11 @@ import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType;
|
|||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -51,7 +50,7 @@ public class UnclassifiedServerExceptionDstu3Test {
|
|||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(status.getStatusLine().toString());
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(477, status.getStatusLine().getStatusCode());
|
||||
|
|
|
@ -127,6 +127,11 @@
|
|||
Client that declares explicitly that it is searching/reading/etc for
|
||||
a custom type did not automatically parse into that type.
|
||||
</action>
|
||||
<action type="add" issue="406">
|
||||
Allow servers to specify the authentication realm of their choosing when
|
||||
throwing an AuthenticationException. Thanks to GitHub user @allanbrohansen
|
||||
for the suggestion!
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.6" date="2016-07-07">
|
||||
<action type="fix">
|
||||
|
|
|
@ -64,6 +64,21 @@
|
|||
<param name="id" value="basicAuthInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/SecurityInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
<subsection name="HTTP Basic Auth">
|
||||
|
||||
<p>
|
||||
Note that if you are implementing HTTP Basic Auth, you may want to
|
||||
return a <code>WWW-Authenticate</code> header with the response.
|
||||
The following snippet shows how to add such a header with a custom
|
||||
realm:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="basicAuthInterceptorRealm" />
|
||||
<param name="file" value="examples/src/main/java/example/SecurityInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
|
|
Loading…
Reference in New Issue