Why does Apache CXF WS-Security implementation ignore GET requests - cxf

I'm exposing a service as a SOAP Webservice using Apache CXF 2.4.1.
I'm using CXF's WS-Security implementation to implement a custom authentication and authorization.
Everything is being wired with Spring.
My CallbackHandler is being properly called upon a normal SOAP request.
But when a request comes from a simple GET request, like calling the service from a browser with the url below, my handler is not called.
http://localhost:8080/ws/customerService/findById?id=1
Checking CXF's WS-Security code it is ignoring GET requests completely (code below).
Why?
public void handleMessage(SoapMessage msg) throws Fault {
if (msg.containsKey(SECURITY_PROCESSED) || isGET(msg)) {
return;
}
...
}

Related

Intercept routes without tracing

The application I'm working on is a middleware app that allows routing among tons of applications (mostly SOAP services).
We encountered saturation because of the automatic logs generated by Camel.
The log volume was reduced with the new interceptors. However, if a service is called inside a current route, all I got is the Request Body from the SendToEndpoint interceptor.
Given that all service calls in the application was made that way, I can not change the current routes.
Old interceptors:
getContext().setTracing(true); // <--- trace every step of all routes
interceptFrom().to("log:example");
configureRoutes() {
// route logic
}
New interceptors:
getContext().setTracing(false);
interceptFrom("cxf:*").to("log:example");
interceptSendToEndpoint("cxf:*").to("log:example");
configureRoutes() {
// route logic
}
Example of a route :
from("scheduler endpoint")
.to("DAO method to find the case from database")
.process(//First processor to call the SOAP service)
.to("SOAP endpoint")
.convertBodyTo(SOAP ResponseBody.class) <-- convert the MessageContentsList to SOAP response body generated from the WSDL
.process(//Second processor to check if the response code is OK in the SOAP response body);
How can I implement an interceptor that allows to log also the SOAP response body ?
Thank you for your help.
I dislike to use interceptors to this porpoise, I suggest you to use the EventNotifier interface, you just need to declare it as a bean in camel context and override the notify method.
See: https://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/spi/EventNotifier.html
Here is an usage example: http://camel.apache.org/eventnotifier-to-log-details-about-all-sent-exchanges.html
Note: Camel has some events that you can uss like: CamelContextCreated, ExchangeCreatedEvent, ExchangeSendingEvent, ExchangeSentEvent, ExchangeCompletedEvent, ExchangeFailedEvent, etc.

AngularJS SpringBoot no authentication csrf protection

My system uses AngularJS 1.6 and Spring Boot2.0. My front end is just a simple form that customers can use to buy tickets for an event. When the page loads it will GET the details of the current active event, then the users can fill the form to be POSTed. No login/registration so it's just like a google form. I've built them as separate projects so they will be running in different ports. Now I want to enable csrf protection but I can't seem to make it work.
I tried to follow this tutorial but without the authentication part: https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii. When I did this I encountered a "CORS header ‘Access-Control-Allow-Origin’ missing" on the GET event details part so I added a CORS filter:
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
chain.doFilter(request, response);
After adding that, my GET works but I encounter the same CORS error when I POST the customer's details. I can already see the XSRF-TOKEN in the cookie when I use postman but somehow my backend blocks the incoming POST. From my understanding, angular automatically uses a received XSRF token so I didn't modify anything in the frontend when implementing spring security.
I've also tried this one: https://sdqali.in/blog/2016/07/20/csrf-protection-with-spring-security-and-angular-js/. And the exact same result, if I just follow the tutorial, CORS error on GET then when I add simple CORS filter, CORS error on POST. My filters seems to get mixed up on run time.
I've tried playing around the codes in these tutorials along with some answers to related questions here in stack but all of them don't have to deal with the CORS problem.
To begin, you should check my comment on how to enable CORS with spring-boot and spring-security.
As for CSRF protection, spring-boot 2 with spring-security enable that directly. Yet, to use it with AngularJS, you need to follow this guide:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#csrf-cookie
We are also working on a project with a frontend AngularJS 1.6 and a backend spring-boot 2, but, as we didn't deploy our frontend and our backend on the same url, we got into a problem.
Example:
https://project.company.com/frontend
https://project.company.com/backend
The cookie generated by the server was set on the domain https://project.company.com with context /backend, which was unreadable from our frontend application in AngularJS.
To fix that, we forced our server to send a cookie with a context / so that it could be read from both applications.
#Configuration
#EnableWebSecurity
public class LdapAuthenticationSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
final CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
csrfTokenRepository.setCookiePath("/"); // Make sure that the cookie can be read from the backend and the frontend on the same domain.
http.csrf().csrfTokenRepository(csrfTokenRepository);
http.cors();
}
}
In the case where your applications are deployed on different domains, but with the same base like this:
https://frontend.company.com
https://backend.company.com
I would suggest to force the cookie send by the server with a domain like .company.com. But to do so, you will need to modify the way spring-security manage the cookie creation.

Intercepting cxf web service header with Apache Camel (Java DSL)

I created a web service client to handle cxf soap web services with apache camel.
String serviceUri = "cxf:http://localhost:10000/myservice?serviceClass=" +
MyRequest.class.getCanonicalName();
from(uri).to("mock:xyz");
The web service receives the soap call but throws an exception since the request requires a handling for wss.
org.apache.cxf.binding.soap.SoapFault: MustUnderstand headers: [{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security] are not understood.
The reason is, that the service requires ws security, which can be seen by lloking at the request.
<SOAP-ENV:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" SOAP-ENV:mustUnderstand="1">
I found out that I need to implement an interceptor to handle header properties.
My questions:
How can I add an interceptor to handle the header attributes with Camel Java-DSL?
Will this be sufficient to get rid of the SOAP Fault?
You can do it through
cxfEndpointConfigurer option #see: Camel-CXF configuration
(I use Spring (it is much easier)), but I guess for DSL URI will look like:
String serviceUri = "cxf:http://localhost:10000/myservice?serviceClass=" +
MyRequest.class.getCanonicalName() +
"&cxfEndpointConfigurer="+ MyConfigurer.class.getCanonicalName();
by implementing org.apache.camel.component.cxf.CxfEndpointConfigurer you have ability to add an Interceptor inside configureServer method
server.getEndpoint().getInInterceptors().add(new MyJAASLoginInterceptor());
if you run your Camel in container with JAAS (like JBOSS) you can use extension from
org.apache.cxf.interceptor.security.JAASLoginInterceptor
with needed callback handler.
Simple example which validates user/password from WSS header against JBOSS users:
public class MyJAASLoginInterceptor extends javax.security.auth.callback.JAASLoginInterceptor {
#Override
protected CallbackHandler getCallbackHandler(String name, String password) {
return new org.apache.cxf.interceptor.security.NamePasswordCallbackHandler(name, password, "setCredential");
}
}

Restlet CorsFilter with ChallengeAuthenticator

I'm building a RESTful API with the Restlet framework and need it to work with cross domain calls (CORS) as well as basic authentication.
At the moment I'm using the CorsFilter which does the job of making my webservice support CORS requests. But, when I try to use this with a simple ChallengeAuthenticator with HTTP Basic Authentication it won't work as I want it to (from a web site).
When I access the webservice directly via Chrome it works as intended, but when I try it in a small web application written in angularjs (jquery/javascript) and try to access the webservice it does not.
Basically what happens is that when a OPTIONS request is sent to my webservice it will not respond with the headers: 'Access-Control-Allow-Origin', 'Access-Control-Allow-Credentials', etc. as it should. Instead it is sending a respond with HTTP status code 401 saying that the authentication failed.. Is this because the authenticator is overriding the CorsFilter somehow?
My createInboundRoot method can be seen below.
#Override
public Restlet createInboundRoot() {
ChallengeAuthenticator authenticator = createAuthenticator();
RoleAuthorizer authorizer = createRoleAuthorizer();
Router router = new Router(getContext());
router.attach("/items", ItemsServerResource.class);
router.attach("/items/", ItemsServerResource.class);
Router baseRouter = new Router(getContext());
authorizer.setNext(ItemServerResource.class);
authenticator.setNext(baseRouter);
baseRouter.attach("/items/{itemID}", authorizer);
baseRouter.attach("", router);
// router.attach("/items/{itemID}", ItemServerResource.class);
CorsFilter corsFilter = new CorsFilter(getContext());
corsFilter.setNext(authenticator);
corsFilter.setAllowedOrigins(new HashSet(Arrays.asList("*")));
corsFilter.setAllowedCredentials(true);
return corsFilter;
}
(The authorizer and authenticator code is taken from the "official" restlet guide for authorization and authentication)
I've tried alot of changes to my code but none which given me any luck. But I noticed that when setting the argument "optional" in ChallengeAuthenticator to true (which "Indicates if the authentication success is optional") the CorsFilter does its job, but obviously the ChallengeAuthenticator does not care about authenticating the client and lets anything use the protected resources..
Has anyone had a similar problem? Or have you solved this (CORS + Authentication in Restlet) in any other way?
Thanks in advance!
I think that it's a bug of the Restlet CORS filter. As a matter of fact, the filter uses the method afterHandle to set the CORS headers. See the source code: https://github.com/restlet/restlet-framework-java/blob/4e8f0414b4f5ea733fcc30dd19944fd1e104bf74/modules/org.restlet/src/org/restlet/engine/application/CorsFilter.java#L119.
This means that the CORS processing is done after executing the whole processing chain (authentication, ...). So if your authentication failed, you will have a status code 401. It's actually the case since CORS preflighted requests don't send authentication hints.
For more details about using CORS with Restlet, you could have a look at this link: https://templth.wordpress.com/2014/11/12/understanding-and-using-cors/. This can provide you a workaround until this bug was fixed in Restlet itself.
I opened an issue in Github for your problem: https://github.com/restlet/restlet-framework-java/issues/1019.
Hope it helps,
Thierry
The CorsService (in 2.3.1 coming tomorrow) contains also a skippingResourceForCorsOptions property, that answers directly the Options request without transmitting the request to the underlying filters and server resources.

Handling SAML Redirects on AJAX Requests

I have several AngularJS apps all using Spring/Java and SAML 2.0 for SSO (leveraging the Spring Security SAML extension). My SSO id provider is OpenAM and everything is working pretty well. However, I am running into a situation when a user does a global logout from within one application but has other tabs open. Since these are single page web apps, a lot of functionality may still be usable in the orphaned tabs UNTIL, the user does something to invoke an ajax request. Of course, these AJAX requests get intercepted by the Spring Security SAML filters and triggers an authentication attempt via a REDIRECT to the OpenAM login URL. Of course, this wreaks havoc in the browser since redirects to another domain aren't allowed on AJAX requests. Furthermore, I can't really do anything with Angular's $http interceptors as the requests are 'canceled' and no quality information is available in the $http error callback function (such as a convenient 401/403 status code). All I know is that the request failed.
I don't want to assume that all bad $http requests are due to authentication problems (and do a $window.location.reload()) as there could be legitimate reasons for failure. My preference is to suppress the Spring Security redirect (to OpenAM login page) for ajax requests and, instead, send back a 401/403 status code. This would allow me to handle the error in the $http interceptor and do a full page load if it is an authentication failure, thus elegantly redirecting to the login page as if they were going to the site for the first time.
Any ideas for how to accomplish this?
The bean responsible for initialization of authentication and decision to return an HTTP error, perform a redirect, ... is an instance of AuthenticationEntryPoint. To change its behavior you can either:
customize the current SAMLEntryPoint (extend the commence method) and override the default behavior in case request is an AJAX call from Angular, so it returns an HTTP error instead of performing redirect to IDP
or define another security:http element in your Spring context (before the current one) which only covers your AJAX requests (e.g. with attribute pattern="/api/**") and uses an entry point which behaves in the way you want (see Http403ForbiddenEntryPoint)
Referring to a possible implementation of Vladimir's first bullet - taken from https://www.jasha.eu/blogposts/2015/10/saml-authentication-angularjs-spring-security.html
public class XhrSamlEntryPoint extends SAMLEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
if (isXmlHttpRequest(request) && e instanceof InsufficientAuthenticationException) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
super.commence(request, response, e);
}
private boolean isXmlHttpRequest(HttpServletRequest request) {
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
}
}
Keep in mind that X-Requested-With is not a mandatory header so the detection is not bullet-proof according to this answer. In my case since the backend was used with a SPA frontend, I removed the check of ajax call altogether.

Resources