I have a Glassfish 4 webserver (running locally atm for testing) with a servlet, need to be able to request a string from a different domain using Javascript.
Searching for a solution, people suggest enabling CORS on my web server, by creating a Filter class (implementing ContainerRequestFilter and ContainerResponseFilter).
I've tried creating such a class, but the client still gets the same error; "No 'Access-Control-Allow-Origin' header is present on the requested resource".
What am I doing wrong?
Code:
class: CrossDomainFilter.java
package server;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
#Provider
#CORSBinding
public class CrossDomainFilter implements ContainerRequestFilter, ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request) throws IOException {
System.out.println("filter(request) called !!");
request.getHeaders().add("Access-Control-Allow-Origin", "*");
request.getHeaders().add("Access-Control-Allow-Headers",
"Authorization");
if (request.getMethod().equals("OPTIONS")) {
System.out.println("OPTIONS is requested!!!!!!!!!!!!!");
}
if (request.getMethod().equals("GET")) {
System.out.println("GET is requested!!!!!!!!!!!!!");
}
if (request.getMethod().equals("POST")) {
System.out.println("POST is requested!!!!!!!!!!!!!");
}
if (request.getMethod().equals("DELETE")) {
System.out.println("DELETE is requested!!!!!!!!!!!!!");
}
if (request.getMethod().equals("PUT")) {
System.out.println("PUT is requested!!!!!!!!!!!!!");
}
}
#Override
public void filter(ContainerRequestContext arg0,
ContainerResponseContext arg1) throws IOException {
System.out.println("filter(request, response) called !!");
}
}
Javascript function making the request
function httpRequest(url) {
var request = new XMLHttpRequest();
request.open("GET", url, false);
request.setRequestHeader('Access-Control-Allow-Origin', '*');
request.send(null);
return (request.status == 404) ? url + " not found!" : request.responseText;
}
EDIT:
Ended up using this: https://bitbucket.org/thetransactioncompany/cors-filter
I can now make javascript requests to my server cross-domain. But I would still like to understand what's wrong with my previous attempts...
You are adding headers to the Request instead of the Response. Request is the data from the client to the server while the Response is the data from the server to the client. The headers need to be set by the server to tell the browser that the resource is allowed (or not) be access by a particular domain.
With your example, you're adding the headers to the response before dispatch by the servlet. You also don't need the header in the javascript request.
Here's a pretty good resource with more detail on the different headers and use cases. http://www.html5rocks.com/en/tutorials/cors/
Related
I'm working on a react frontend using REST to communicate with a JAVA backend (Thorntail microservice), secured by Keycloak. Using GET and POST there's no problem, but trying to DELETE returns different errors depending on my settings. My default CorsFilter.java file has no settings:
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
#Provider
public class CorsFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) {
}
}
My React REST function:
static deleteCourier = function (origin, courierNumber, endpoint, data, requestListener) {
keycloak.updateToken(5).then(function () {
const request = new XMLHttpRequest();
request.open("DELETE", HTTP_SCHEME + "://" + backendUrl + endpoint + "/" + origin + "/" + courierNumber);
request.setRequestHeader('Authorization', 'Bearer' + keycloak.token);
request.send();
}).catch(function () {
console.log('Failed to refresh the token, or the session has expired');
keycloak.init({onLoad: 'login-required'}).success(function (authenticated) {
if (authenticated) {
Rest.deleteCourier(origin, courierNumber, endpoint, data, requestListener);
} else {
console.log('kc not authenticated');
}
}).error(function () {
console.log('kc init failed');
});
}).then(function () {
Rest.updateData(data, requestListener, endpoint)
});
};
Calling the REST API of the backend:
import javax.ejb.EJB;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/device")
public class DeviceEndpoint {
#DELETE
#Path("/{origin}/{courierNumber}")
public Response delete(
#PathParam("origin") String origin,
#PathParam("courierNumber") String courierNumber) {
try {
deviceBF.removeCourier(origin, courierNumber);
} catch (PhoenixClientException e) {
return Response.ok(new WebApplicationException(e)).build();
}
return Response.ok().build();
}
}
In Keycloak "Web Origins" is set to "*".
Using this setup GET and POST work as intended. When trying to delete a device the network tab in my browser shows me as status: "(canceled)" and (Copied as fetch from Googles Chrome):
fetch("http://192.168.0.66:8282/auth/realms/customer/protocol/openid-connect/auth?response_type=code&client_id=mystique&redirect_uri=http%3A%2F%2F192.168.0.66%3A8080%2Fmystique%2Frest%2Fdevice%2Fdts%2Fd&state=22a1dba6-e12e-4852-8cba-0f86a4799d4f&login=true&scope=openid", {"credentials":"omit","referrer":"http://localhost:3000/","referrerPolicy":"no-referrer-when-downgrade","body":null,"method":"DELETE","mode":"cors"});
And the console output is:
Access to XMLHttpRequest at 'http://192.168.0.66:8080/mystique/rest/device/dts/d' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
GET and POST requests send OPTIONS request before the real request. In this case no such OPTIONS request is sent.
When i put a console output to my REST API I can see that it is not called. But the path should be ok. Tested the REST API via Postman and everything worked.
If I modify the CorsFilter.java file and add for example:
responseContext.getHeaders().add(
"Access-Control-Allow-Origin", "http://localhost:3000");
... the browser console shows to me:
Access to XMLHttpRequest at 'http://192.168.0.66:8080/mystique/rest/websocket-auth/token' from origin 'http://localhost:3000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:3000, http://localhost:3000', but only one is allowed.
I've searched my code and also all my config files for the second entry and found nothing. I don't have any clue where it's from.
Adding the following to the CorsFilter.java seems to have no effect.
responseContext.getHeaders().add(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
In addition to the previously described behaviour the device, the courier is belonging to, is deleted. I'm not sure if this is corresponding to the problem with CORS or if it is a single problem. Changing a devices' data without deleting a courier (which is also done via REST) works as intended. But with the deletion of a courier before the update deletes my device.
I hope anybody can help me with this.
This question already has answers here:
Cross Origin Resource sharing issue even when all the CORS headers are present
(2 answers)
Closed 5 years ago.
I want to send data form angular 4 to java servlet but I can't send because doesn't pass access control. I want to insert data to db with java servlet
this my code
front-end: data.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Headers, RequestOptions, ResponseOptions } from '#angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class DataService {
result: any;
private url = '//localhost:8080/my-java-web/';
constructor(private _http: Http) { }
addBookWithPromise(user: object): Promise<object> {
const headers = new Headers({ 'Content-Type': 'application/json',
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods":" GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Credentials": "true"});
const options = new ResponseOptions({ headers: headers });
return this._http.post(this.url + 'loginsuccess', user,
options).toPromise()
.then(this.extractData)
.catch(this.handleErrorPromise);
}
private extractData(res: Response) {
const body = res.json();
return body.data || {};
}
private handleErrorObservable (error: Response | any) {
console.error(error.message || error);
return Observable.throw(error.message || error);
}
private handleErrorPromise (error: Response | any) {
console.error(error.message || error);
return Promise.reject(error.message || error);
}
}
backend: java servlet
public class LoginSuccess extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
response.addHeader("Access-Control-Allow-Origin","*");
response.addHeader("Access-Control-Allow-Methods"," GET, POST, OPTIONS");
response.addHeader("Access-Control-Allow-Headers","Content-Type");
response.addHeader("Access-Control-Allow-Credentials", "true");
PrintWriter out = response.getWriter();
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("Success" +username);
Thank You Very Much
You'll need to read up on the CORS protocol.
I wrote a blog post a while back about implementing CORS. It's based on the use of the Spring framework (specifically Spring Boot), not the Servlet API directly, but it does have a fairly extensive explanation of how CORS works.
Your specific problem is that you are only handling POSTs.
The CORS protocol involves the web browser making an OPTIONS request to your server.
It is this OPTIONS request that must have the Access-Control-Allow-Origin and related headers returned in the response.
If the browser sees those headers in the response, it will then do the POST.
If it does not see those headers in the response to the OPTIONS request, you'll get an HTTP error, reading something like "No 'Access-Control-Allow-Origin' header is present on the requested resource", and the POST request will not be made.
My Jersey CORS request is not functioning for POST, but works for GET requests. The headers are being mapped to Jersey requests as shown in the below screenshot of a GET request to the same resource.
However, doing a POST to the below method makes me end up with XMLHttpRequest cannot load http://production.local/api/workstation. Origin http://workstation.local:81 is not allowed by Access-Control-Allow-Origin.
Here's a screenshot of network activity:
Details on failed POST request:
Here's my resource:
#Path("/workstation")
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public class WorkstationResource {
#InjectParam
WorkstationService workstationService;
#POST
public WorkstationEntity save (WorkstationEntity workstationEntity) {
workstationService.save(workstationEntity);
return workstationEntity;
}
#GET
#Path("/getAllActive")
public Collection<WorkflowEntity> getActive () {
List<WorkflowEntity> workflowEntities = new ArrayList<WorkflowEntity>();
for(Workflow workflow : Production.getWorkflowList()) {
workflowEntities.add(workflow.getEntity());
}
return workflowEntities;
}
}
My CORS filter:
public class ResponseCorsFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
Response.ResponseBuilder responseBuilder = Response.fromResponse(response.getResponse());
responseBuilder
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, HEAD");
String reqHead = request.getHeaderValue("Access-Control-Request-Headers");
if(null != reqHead && !reqHead.equals(null)){
responseBuilder.header("Access-Control-Allow-Headers", reqHead);
}
response.setResponse(responseBuilder.build());
return response;
}
}
My Jersey configuration in my Main class:
//add jersey servlet support
ServletRegistration jerseyServletRegistration = ctx.addServlet("JerseyServlet", new SpringServlet());
jerseyServletRegistration.setInitParameter("com.sun.jersey.config.property.packages", "com.production.resource");
jerseyServletRegistration.setInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters", "com.production.resource.ResponseCorsFilter");
jerseyServletRegistration.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE.toString());
jerseyServletRegistration.setInitParameter("com.sun.jersey.config.feature.DisableWADL", Boolean.TRUE.toString());
jerseyServletRegistration.setLoadOnStartup(1);
jerseyServletRegistration.addMapping("/api/*");
While I thought this was a CORS issue, turns out it was a Jersey issue...
org.glassfish.grizzly.servlet.ServletHandler on line 256 handles an exception...
FilterChainInvoker filterChain = getFilterChain(request);
if (filterChain != null) {
filterChain.invokeFilterChain(servletRequest, servletResponse);
} else {
servletInstance.service(servletRequest, servletResponse);
}
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "service exception:", ex);
customizeErrorPage(response, "Internal Error", 500);
}
In my log, all I see is service exception: with nothing after it. When I debug this line, I end up seeing the error javax.servlet.ServletException: org.codehaus.jackson.map.JsonMappingException: Conflicting setter definitions for property "workflowProcess": com.production.model.entity.WorkstationEntity#setWorkflowProcess(1 params) vs com.production.model.entity.WorkstationEntity#setWorkflowProcess(1 params) which gives me something I can actually work with.
It's hard to tell and hard to debug since it's the browser that produces that error upon inspecting the response (header).
Even upon very close inspection your code looks fine and sane except that Access-Control-Allow-Headers is or may be set twice in filter(). While RFC 2616 (HTTP 1.1) Section 4.2 does basically permit it given certain conditions are met I wouldn't gamble here. You have no control over how browser X version N handles this.
Instead of setting the same header twice with different values rather append the 2nd set of values to the existing header.
I got a HttpServletRequest request in my Spring Servlet which I would like to forward AS-IS (i.e. GET or POST content) to a different server.
What would be the best way to do it using Spring Framework?
Do I need to grab all the information and build a new HTTPUrlConnection? Or there is an easier way?
Discussions of whether you should do forwarding this way aside, here's how I did it:
package com.example.servlets;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Enumeration;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.example.servlets.GlobalConstants;
#SuppressWarnings("serial")
public class ForwardServlet extends HttpServlet {
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
forwardRequest("GET", req, resp);
}
#Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) {
forwardRequest("POST", req, resp);
}
private void forwardRequest(String method, HttpServletRequest req, HttpServletResponse resp) {
final boolean hasoutbody = (method.equals("POST"));
try {
final URL url = new URL(GlobalConstants.CLIENT_BACKEND_HTTPS // no trailing slash
+ req.getRequestURI()
+ (req.getQueryString() != null ? "?" + req.getQueryString() : ""));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
final Enumeration<String> headers = req.getHeaderNames();
while (headers.hasMoreElements()) {
final String header = headers.nextElement();
final Enumeration<String> values = req.getHeaders(header);
while (values.hasMoreElements()) {
final String value = values.nextElement();
conn.addRequestProperty(header, value);
}
}
//conn.setFollowRedirects(false); // throws AccessDenied exception
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(hasoutbody);
conn.connect();
final byte[] buffer = new byte[16384];
while (hasoutbody) {
final int read = req.getInputStream().read(buffer);
if (read <= 0) break;
conn.getOutputStream().write(buffer, 0, read);
}
resp.setStatus(conn.getResponseCode());
for (int i = 0; ; ++i) {
final String header = conn.getHeaderFieldKey(i);
if (header == null) break;
final String value = conn.getHeaderField(i);
resp.setHeader(header, value);
}
while (true) {
final int read = conn.getInputStream().read(buffer);
if (read <= 0) break;
resp.getOutputStream().write(buffer, 0, read);
}
} catch (Exception e) {
e.printStackTrace();
// pass
}
}
}
Obviously this could use a bit of work with regard to error handling and the like but it was functional. I stopped using it, however, because it was easier in my case to make calls directly to the CLIENT_BACKEND than to deal with cookies, auth, etc. across two distinct domains.
I also needed to do the same, and after some non optimal with Spring controllers and RestTemplate, I found a better solution: Smiley's HTTP Proxy Servlet. The benefit is, it really does AS-IS proxying, just like Apache's mod_proxy, and it does it in a streaming way, without caching the full request/response in the memory.
Simply, you register a new servlet to the path you want to proxy to another server, and give this servlet the target host as an init parameter. If you are using a traditional web application with a web.xml, you can configure it like following:
<servlet>
<servlet-name>proxy</servlet-name>
<servlet-class>org.mitre.dsmiley.httpproxy.ProxyServlet</servlet-class>
<init-param>
<param-name>targetUri</param-name>
<param-value>http://target.uri/target.path</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>proxy</servlet-name>
<url-pattern>/mapping-path/*</url-pattern>
</servlet-mapping>
or, of course, you can go with the annotation config.
If you are using Spring Boot, it is even easier: You only need to create a bean of type ServletRegistrationBean, with the required configuration:
#Bean
public ServletRegistrationBean proxyServletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(
new ProxyServlet(), "/mapping-path/*");
bean.addInitParameter("targetUri", "http://target.uri/target.path");
return bean;
}
This way, you can also use the Spring properties that are available in the environment.
You can even extend the class ProxyServlet and override its methods to customize request/response headers etc, in case you need.
Update: After using Smiley's proxy servlet for some time, we had some timeout issues, it was not working reliably. Switched to Zuul from Netflix, didn't have any problems after that. A tutorial on configuring it with Spring Boot can be found on this link.
Unfortunately there is no easy way to do this. Basically you'll have to reconstruct the request, including:
correct HTTP method
request parameters
requests headers (HTTPUrlConnection doesn't allow to set arbitrary user agent, "Java/1.*" is always appended, you'll need HttpClient)
body
That's a lot of work, not to mention it won't scale since each such proxy call will occupy one thread on your machine.
My advice: use raw sockets or netty and intercept HTTP protocol on the lowest level, just replacing some values (like Host header) on the fly. Can you provide more context, why so you need this?
#RequestMapping(value = "/**")
public ResponseEntity route(HttpServletRequest request) throws IOException {
String body = IOUtils.toString(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
try {
ResponseEntity<Object> exchange = restTemplate.exchange(firstUrl + request.getRequestURI(),
HttpMethod.valueOf(request.getMethod()),
new HttpEntity<>(body),
Object.class,
request.getParameterMap());
return exchange;
} catch (final HttpClientErrorException e) {
return new ResponseEntity<>(e.getResponseBodyAsByteArray(), e.getResponseHeaders(), e.getStatusCode());
}
}
If you are forced to use spring, please check the rest template method exchange to proxy requests to a third party service.
Here you can find a working example.
Use Spring Cloud Gateway
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-mvc</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Controller
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.mvc.ProxyExchange;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
#RestController
public class Proxy extends BaseController {
private String prefix="/proxy";
private String Base="localhost:8080/proxy"; //destination
void setHeaders(ProxyExchange<?> proxy){
proxy.header("header1", "val1"); //add additional headers
}
#GetMapping("/proxy/**")
public ResponseEntity<?> proxyPath(#RequestParam MultiValueMap<String,String> allParams, ProxyExchange<?> proxy) throws Exception {
String path = proxy.path(prefix); //extract sub path
proxy.header("Cache-Control", "no-cache");
setHeaders(proxy);
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(Base + path).queryParams(allParams).build();
return proxy.uri(uriComponents.toUri().toString()).get();
}
#PutMapping("/proxy/**")
public ResponseEntity<?> proxyPathPut(ProxyExchange<?> proxy) throws Exception {
String path = proxy.path(prefix);
setHeaders(proxy);
return proxy.uri(Base + path).put();
}
Preface
This is my first attempt at a Filter, be gentle.
Project Description
I am trying to finalize a build for a SSO for several of our applications and I seem to be hitting a wall. The webapp I am attempting to connect to uses the "Authentication" header to determine user credentials within the application. I have built a Filter with hopes of setting the header before it is passed on to the webapp.
The Problem
The code passes eclipse validation, compiles, loads to Tomcat, and passes through to the webapp. The only thing that is missing is the Authentication header.
What am I missing/doing wrong?
AuthenticationFilter source
package xxx.xxx.xxx.xxx.filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import xxx.xxx.xxx.ConfigFile;
import xxx.xxx.xxx.Console;
import xxx.xxx.xxx.FalseException;
import xxx.xxx.activity.EncryptUtil;
public class AuthenticationFilter implements Filter {
public ConfigFile config;
public void init(FilterConfig arg0) throws ServletException {
config = new ConfigFile("C:/config.properties");
}
public void doFilter(ServletRequest sRequest, ServletResponse sResponse, FilterChain filterChain) throws IOException, ServletException {
Console.debug("AuthenticationFilter.doFilter() triggered.");
ServletRequestWrapper request = new ServletRequestWrapper((HttpServletRequest) sRequest);
HttpServletResponse response = (HttpServletResponse) sResponse;
HttpSession session = request.getSession();
Cookie cookie = null;
try {
if (request.getParameter("logout") != null) {
session.invalidate();
throw new FalseException("Logout recieved");
}
String auth = request.getHeader("Authorization");
if (auth == null) {
Console.debug("Authorization Header not found.");
// get cookie --COOKIE NAME--
Cookie[] cookies = request.getCookies();
if (cookies == null) {
throw new FalseException("Cookies not set.");
}
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(config.getProperty("authentication.cookie.name"))) {
cookie = cookies[i];
}
}
if (cookie == null) {
throw new FalseException("Cannot find Cookie (" + config.getProperty("authentication.cookie.name") + ") on Client");
}
Console.debug("Cookie (" + config.getProperty("authentication.cookie.name") + ") found on Client. value="+cookie.getValue());
String decToken = decryptToken(cookie.getValue());
Console.debug("Decrypted Token: "+decToken);
Console.debug("Setting Authorization Header...");
request.setAttribute("Authorization", decToken);
request.addHeader("Authorization", decryptToken(cookie.getValue()));
Console.debug("Authorization Header set.");
Console.debug("Validating Authorization Header value: "+request.getHeader("Authorization"));
}
}catch (FalseException e) {
Console.msg(e.getMessage() + ", giving the boot.");
response.sendRedirect(config.getProperty("application.login.url"));
} catch (Exception e) {
Console.error(e);
}
Console.debug("AuthenticationFilter.doFilter() finished.");
filterChain.doFilter(request, response);
}
public void destroy() {
}
private String decryptToken(String encToken) {
String token = null;
token = EncryptUtil.decryptFromString(encToken);
return token;
}
}
web.xml source
<web-app>
<filter>
<filter-name>AuthenticationFilter</filter-name>
<display-name>AuthenticationFilter</display-name>
<description></description>
<filter-class>com.xxx.xxx.xxx.filters.AuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
ServletRequestWrapper Source
package com.xxx.xxx.xxx.filters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
public class ServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {
public ServletRequestWrapper(HttpServletRequest request) {
super(request);
headerMap = new HashMap();
}
private Map headerMap;
public void addHeader(String name, String value) {
headerMap.put(name, new String(value));
}
public Enumeration getHeaderNames() {
HttpServletRequest request = (HttpServletRequest) getRequest();
List list = new ArrayList();
for (Enumeration e = request.getHeaderNames(); e.hasMoreElements();) {
list.add(e.nextElement().toString());
}
for (Iterator i = headerMap.keySet().iterator(); i.hasNext();) {
list.add(i.next());
}
return Collections.enumeration(list);
}
public String getHeader(String name) {
Object value;
if ((value = headerMap.get("" + name)) != null)
return value.toString();
else
return ((HttpServletRequest) getRequest()).getHeader(name);
}
}
Debug Log
LoginServlet.doGet() triggered.
[DEBUG] : Authenticate.isClientLoggedIn() triggered.
xxx url : https://xxx.xxx.xxx/xxx/home.action
[DEBUG] : Authenticate.isClientLoggedIn() status code: 401
Unauthorized User.
Client IS NOT logged in.
-- Fill out Login Form, submit --
LoginServlet.doPost() triggered.
[DEBUG] : Authenticate.isClientLoggedIn() triggered.
xxx url : https://xxx.xxx.xxx./xxx/home.action
[DEBUG] : Authenticate.isClientLoggedIn() status code: 401
Unauthorized User.
Client IS NOT logged in.
Client (--USERID--) attempting basic authentication with password(--PASSWORD--).
[DEBUG] : BasicAuthentication.touch(http://localhost:PORT/vu/loginCheck.html, --USERID--, --PASSWORD--) triggered.
[DEBUG] : BasicAuthentication.touch() response code: 200
Client (--USERID--) has been logged IN.
Client (--USERID--) basic authentication finished, Client is logged in.
Client (--USERID--) logged in successfully.
[DEBUG] : Cookie (xxx_token) Set: 1e426f19ebdfef05dec6544307addc75401ecdc908a3c7e6df5336c744--SECRET--
[DEBUG] : Redirecting client to https://xxx.xxx.xxx/xxx/home.action
-- Redirected to webapp, filter recieves --
[DEBUG] : AuthenticationFilter.doFilter() triggered.
[DEBUG] : Authorization Header not found. << Initical check to see if user is already logged in to site
[DEBUG] : Cookie (xxx_token) found on Client. value=1e426f19ebdfef05dec6544307addc75401ecdc908a3c7e6df5336c744--SECRET--
[DEBUG] : Decrypted Token: Basic --SECRET--
[DEBUG] : Setting Authorization Header...
[DEBUG] : Authorization Header set.
[DEBUG] : Validating Authorization Header value: Basic --SECRET-- << Value matches Decrypted Token
[DEBUG] : AuthenticationFilter.doFilter() finished.
-- Web Application errors out, unable to find Authorization header
Thanks for your help.
I'm adding a new answer, since it's completely different.
I did a test on my system. I copied your code, dumped the cookie test, and wrote a simple Servlet to dump things out for me.
And it worked fine, save for one caveat.
I don't know how your app is using this. But your ServletRequestWrapper implements getHeaderNames, and getHeader, but it does NOT implement getHeaders. I ran in to that problem as I used getHeaders to try and dump the request, and, of course, Authorization was missing.
So, you may want to look at your code closer to see if it is indeed not using getHeaders. If it is, it will "work fine", but completely skip the work you've done, and thus miss your Authorization header.
Here's my implementation, and it worked for me.
#Override
public Enumeration getHeaders(String name) {
Enumeration e = super.getHeaders(name);
if (e != null && e.hasMoreElements()) {
return e;
} else {
List l = new ArrayList();
if (headerMap.get(name) != null) {
l.add(headerMap.get(name));
}
return Collections.enumeration(l);
}
}
First, the most basic question (kind of an "is this plugged in" question), I assume that your cookies are all rooted in the same domain, and that you're not trying to get cross domain behavior here. Because cookies won't do that.
Beyond the cookie test, this looks fine. But it all hinges on the cookie test.
If you want to test the Authorization header, then you can simply short circuit the cookie test (i.e. it always passes) and populate the Authorization header with some valid value. This will, in the short term, test your whole Authorization scheme.
Once that's done/fixed, then you can focus on the cookie setting and delivery.
I also assume that you're not using Java EE Container based authentication, with Tomcat doing this check for you. In that case, a filter is simply "too late". The container will have already made it's decisions before your filter even gets called.
If you are using container based authentication, and your apps are on the same container, I would imagine Tomcat (or someone) has an SSO option at the container level. I know that Glassfish will do this for you out of the box. It should be straightforward to modify Tomcat artifacts (i.e. not portable Java EE/Servlet mechanisms) to implement this if that is the case.