Spring Webflux: Remove WWW-authenticate header - basic-authentication

I am using Spring 5 Webflux with Basic Authentication.
Problem:
When I type a wrong username or password spring reponses with Http Status 401 and includes the www-authenticate: Basic realm="Realm" Http Header which causes the browser to pop up the basic auth box.
How to remove that HTTP Header in Spring 5 Webflux?
Do I have to do a custom Webfilter?

The code below is in Kotlin copied from my project. But the idea can be simply transfered into Java.
So the solution is tied around a custom Webfilter.
#Component
class HttpHeaderWebFilter: WebFilter {
override fun filter(exchange: ServerWebExchange, next: WebFilterChain): Mono<Void> {
return next.filter(exchange).then(Mono.defer {
val headers = exchange.response.headers
if (headers.containsKey("WWW-Authenticate")) {
headers.remove("WWW-Authenticate")
}
Mono.empty<Void>()
})
}
}

We can use the following
if (exchange.getRequest().getHeaders().containsKey("headerKey")) {
exchange.getRequest().mutate().header("headerKey", null, null);
}
We are using the double null, to overcome deprecated Overriding method.
If you are using Spring Framework 5.2, usage of single null is sufficient.

The reason why it sends that header is that the authenticationFailureHandler associated with the default httpBasic() configuration uses the configured entryPoint. If it's not configured, then it uses the default entryPoint (HttpBasicServerAuthenticationEntryPoint) which adds that header to the response.
So, to change this behavior you can set a custom entryPoint, for example:
.httpBasic().authenticationEntryPoint(HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))

Related

How can I log every request/response body in Armeria HTTP client

I tried two approaches to log every HTTP body produced/received by my armeria client:
using out-of-box LoggingClient decorator
decorator(LoggingClient.newDecorator())
creating custom logging decorator
decorator { delegate, ctx, req ->
ctx.log().whenRequestComplete().thenAccept { log -> logger.trace(log.toStringRequestOnly()) }
ctx.log().whenComplete().thenAccept { log -> logger.trace(log.toStringResponseOnly()) }
delegate.execute(ctx, req)
}
But I see in logs only headers and other technical information. How can I log requestContent/responseContent?
It's said in armeria documentation that these fields are available only for Thrift clients:
the serialization-dependent content object of the request. ThriftCall for Thrift. null otherwise.
the serialization-dependent content object of the response. ThriftReply for Thrift. null otherwise.
It's weird to me.
I had to add com.linecorp.armeria.client.logging.ContentPreviewingClient decorator in addition to logging decorator:
decorator(ContentPreviewingClient.newDecorator(1000))

Spring Cloud Contract for provider - setting optional header

I've created a contract on a provider side:
Contract.make {
request {
method 'GET'
url('/cars/car?id=3')
headers {
header(accept(), "application/hal+json")
header(SOME OTHER HEADER)
}
}
response {
...
}
}
Unfortunately one of my customers do not send request with "header(SOME OTHER HEADER)". My question is how can I mark "header(SOME OTHER HEADER)" as optional?
You can have the value optional. Not the header itself. If the header is optional then create two separate contracts. One with the header and one without it.
I'm experiencing the same issue here. I need to include Access-Control-Allow-Origin header to the generated stub but if I add it to the contract the tests begins to fail becouse of:
Expecting:
<null>
to be equal to:
<"\"Access-Control-Allow-Origin\": \"*\"">
even if I annotate the RestController class with #CrossOrigin(value = "*")

spring MVC controller versioning

I have a spring boot application , which have a spring MVC controller. I am trying to version my rest api using Accept header.
The following is how my Controller looks like
RestController
#RequestMapping(value = "/private/")
public class AppleController {
private final AppleService appleService;
public AppleController(AppleService appleService) {
this.appleService = appleService;
}
#GetMapping(value = "apples/{id}", produces = "application/json; v=1.0",
headers = "Accept=application/json; v=1.0")
public ResponseEntity getByappleId(#PathVariable("id") Long appleId) {
System.out.println("version1");
GetByappleIdResponse response = appleService.findByappleId(appleId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
#GetMapping(value = "apples/{id}", produces = "application/json; v=2.0",
headers = "Accept=application/json; v=2.0")
public ResponseEntity getByappleId2(#PathVariable("id") Long appleId) {
System.out.println("version2");
GetByappleIdResponse response = appleService.findByappleId2(appleId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Irrespective of the version that I am passing in the Accept header when calling the API always "getByappleId" method is called, hence only version 1 response is returned.
Is there anything wrong in my controller ?
There are many options to implement versioning of REST API:
suggested in the comments approach for manually routing your request;
making version as a part of your Accept header value, f.e.:
(headers = "Accept=application/vnd.name.v1+json")
(headers = "Accept=application/vnd.name.v2+json")
making version as a part of your mapping:
#GetMapping("apples/v1/{id})"
#GetMapping("apples/v2/{id})
So you need to decide which way to go. Some useful links:
Versioning a REST API
Best practices for API versioning?
As described in this answer: https://stackoverflow.com/a/34427044/258813 (and mentioned in the comments) Spring does not support routing using the headers like that.
If you want to support routing via a version header, I would recommend a custom routing condition and annotation - certainly if you are building a large API, it will result in less code and a more elegant solution.
You would define some annotation like #ApiVersion(1) that you can add to any method that is also a request mapping and then add the custom routing condition and it will behave correctly.
I have described using custom routing conditions and annotations (based on subdomains - but that could easily be switched to check headers instead) here: http://automateddeveloper.blogspot.co.uk/2014/12/spring-mvc-custom-routing-conditions.html

Stop authentication at an early pipeline stage- unless going to /Login?

I'm writing a MessageHandler to authenticate a user.
If a request is not containing a special header , I want to block it at the MessageHandler stage.
But if the user wants to go to the Users/Login method, he will probably have no header (because he is not Login yet ).
The problem is that I don't want to block him at the [authorize] controller level.
It's pretty simple :
If he doesn't have the header and he is not on the way to login — BLOCK
If he doesn't have the header and he is on the way to login — only then - ALLOW
Question
1) At the MessaageHandler stage , how can I know that he is on a way to do login ? ( NB : I don't mention the {action} in the route. e.g. :
--
public class User :ApiController
{
[HttpPost]
public bool CheckLogin (....) //i'm not specifying action in the route
{
}
}
2) Looking at the command to read the header :
AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
But - Authorization != Authentication.
So why does web api reference the authorization header as an Authentication ?
The MessageHandler executes before routing has occurred. So at this stage you don't know yet which controller action will be executed.
One possibility would be to check the verb and the path being requested and perform the custom verification based on that:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string path = request.RequestUri.PathAndQuery;
if (request.Method == HttpMethod.Post && path.StartsWith("/api/checklogin", StringComparison.InvariantCultureIgnoreCase))
{
// Do not enforce the presence of the custom header
return base.SendAsync(request, cancellationToken);
}
// Check for the presence of your custom header
}
So why does web api reference the authorization header as an Authentication ?
At HTTP level, the header is called Authorization.
I believe you are trying to reinvent the wheel while it is already there. You have Autorize and AllowAnonymous (for your Login action) and then you could have a custom authentication filter to read the header and set up the Principal for the request lifetime.
The reason for that is that the term authorization header has been always used in the context of HTTP header-based authentication. Someone who used the tern for the first time was probably not aware that authentication header would probably be slightly more appropriate.

Customize HTTP codes and error message in JBoss AS 7

can anyone tell me how i can customize http codes and reasonphrase in JBoss AS 7?
basically i have a REST webservice that returns a nonstandard status code '499' with reasonphrase 'app error'
In standalone.xml, I set the org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER to true under systemproperties, but AS still overrides the HTTP error message.
There seems to be a mistake in JBoss documentation, the correct property name is:
org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER
So in the standalone you should have something like this:
<system-properties>
<property name="org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER" value="true"/>
</system-properties>
I assume that the REST service is interpretted using RestEasy.
That provides a nice feature of injecting a HTTP response object using #Context:
The #Context annotation allows you to inject instances of javax.ws.rs.core.HttpHeaders, javax.ws.rs.core.UriInfo, javax.ws.rs.core.Request, javax.servlet.HttpServletRequest, javax.servlet.HttpServletResponse, javax.servlet.ServletConfig, javax.servlet.ServletContext, and javax.ws.rs.core.SecurityContext objects.
#Path("/")
public class MyService {
#Context org.jboss.resteasy.spi.HttpResponse response;
#GET #Path("/") public void myMethod(){
response.sendError(499, "The file was censored by NSA.")
}
}
But maybe you should rather consider using a proprietary HTTP header:
response.getOutputHeaders().putSingle("X-MyApp-Error",
"499 Our server is down and admin is on holiday. Mañana.");

Resources