Desperately need help here securing a simple Apache CXF web service. Attempts with Spring Security is taking me no where so I need to find a different strategy. This is to implement authorization on a legacy Java service implemented for some of our clients.
This simple Apache CXF web service was created using Maven's cxf-jaxws-javafirst prototype.
It produced a web.xml and beans.xml file and sample code. Besides beans.xml which remains in default state, I have modified these entities as follows:
web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>WEB-INF/shiro.ini</param-value>
</context-param>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
and my Shiro.ini file looks like this:
# =======================
# Shiro INI configuration
# =======================
[main]
authc = org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
[users]
o = o, OPERATOR
a = a, ADMIN
s = s, SUPERVISOR
[roles]
SUPERVISOR = *
ADMIN = sayHiAdmin
OPERATOR = deleteAccounts
My simple webservice code is as follows:
import javax.jws.WebService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
#WebService(endpointInterface = "org.myCo.com.CxfShiroSecuredService.HelloWorld")
public class HelloWorldImpl implements HelloWorld {
public String sayHi(String text) {
if (isAuthorized("sayHi")) {
return "Successfully said hi " + text;
}
if (hasRole("OPERATOR")){
return "User is OPERATOR";
}
if (hasRole("ADMIN")){
return "User is OPERATOR";
}
throw new UnauthorizedException("Logged user does not have OPERATOR's permission");
}
public String sayHiAdmin(String text) {
if (isAuthorized("sayHiAdmin")) {
return "Successfully said hi Admin " + text;
}
throw new UnauthorizedException("Logged user does not have ADMIN permission");
}
public String deleteAccounts(String text) {
if (isAuthorized("deleteAccounts")) {
return "Successfully deleted accounts " + text;
}
throw new UnauthorizedException("Logged user does not have SUPERVISOR permission");
}
private Boolean isAuthorized(String operation){
Subject currentUser = SecurityUtils.getSubject();
return currentUser.isPermitted(operation); //currentUser.isAuthenticated(); // && currentUser.isPermitted(operation);
}
private Boolean hasRole(String role){
Subject currentUser = SecurityUtils.getSubject();
return currentUser.hasRole(role);
}
}
I have a C# test client that passes authentication information in the SOAP header before invoking webservice like so:
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var client = new HelloWorldClient();
var response = "";
using (new OperationContextScope(client.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes(UserName.Text + ":" + Password.Text));
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
try
{
response = client.sayHi("hi " + UserName.Text);
}
catch (TimeoutException tex)
{
response = tex.Message;
}
catch (CommunicationException cex)
{
response = cex.Message;
}
}
TextBox.Text = response;
}
I have used this same strategy for other web services that require Basic authentication
before invoking method calls with success but this service does not seem to be recognizing my credentials. For each method call invoked, regardless of username/password combination, I get the UnAuthorizedException thrown. Can someone shed me some light?
Thanks in advance.
You need a [urls] section in your shiro.ini file. Something like this:
[urls]
/** = authc
Check out the documentation for further details here.
Related
I am trying to learn about authentication in JAX-WS, so I made a small Netbeans8.0.2/Glassfish4.1 web application with a JAX-WS webservice, and I am trying to make it not public, but available to authorized users only.
The web.xml file for this webservice contains:
<security-constraint>
<web-resource-collection>
<web-resource-name>Fib Web Service</web-resource-name>
<url-pattern>/FibServiceWithAuth/*</url-pattern>
<http-method>*</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>file</realm-name>
</login-config>
However, when I make another simple web app that is using this service,
it works without any authentication required, see here:
http://kempelen.ii.fmph.uniba.sk:8080/FibApp/
I understand that I should connect to the service from the JSF managed bean that handles this JSF page like this:
package fibapp;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.WebServiceRef;
#ManagedBean
#RequestScoped
public class FibBean
{
public FibBean() { }
int n;
String result;
public int getN() { return n; }
public void setN(int newN) { n = newN; }
public String getResult() { return result; }
public void setResult(String newResult) { result = newResult; }
#WebServiceRef(wsdlLocation = "http://kempelen.ii.fmph.uniba.sk:8080/FibServiceWithAuth/FibWithAuth?wsdl")
private FibWithAuth_Service fibService;
public void compute()
{
FibWithAuth fib = fibService.getFibWithAuthPort();
// ((BindingProvider) fib).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "someuser");
// ((BindingProvider) fib).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "somepass");
result = fib.fib(n).toString();
}
}
but even when those user/pass lines are commented out, the bean still gets the result from the webservice.
What is missing, please?
In looking at your WSDL (as specified in your managed bean's #WebServiceRef), the endpoint of the service is
<soap:address
location="http://kempelen.ii.fmph.uniba.sk:8080/FibServiceWithAuth/FibWithAuth"/>
which means your web service resource is /FibWithAuth.
However, your web.xml <security-constraint> url is
<url-pattern>/FibServiceWithAuth/*</url-pattern>
I think you want to change this to
<url-pattern>/FibWithAuth/*</url-pattern>
If you truly want to add the security constraint to the entire FibServiceWithAuth web application, then your <security-constraint> url pattern would be /*.
Lastly, I think you'll also want to change
<http-method>*</http-method>
to
<http-method>POST</http-method>
so that your managed bean can access the WSDL via GET request (per your #WebServiceRef annotation) without authentication.
I want to load a web context not by xml, but configure programatically.
If I use the method setwar(), it worked.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app id = "DERBYNET_ID">
<servlet>
<servlet-name>H2Console</servlet-name>
<servlet-class>org.h2.server.web.WebServlet</servlet-class>
<init-param>
<param-name>webAllowOthers</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>H2Console</servlet-name>
<url-pattern>/console/*</url-pattern>
</servlet-mapping>
</web-app>
But, I want to remove webapp. becasuse it has a xml only. So, I want to set the servlet programmatically like below.But, it's not working. I can't see the error log on the console when I run application. But, If I connect the web page, it gives 503 status code. Why this source code doesn't work??
ServletHandler servletHandler = new ServletHandler();
ServletHolder holder = new ServletHolder("H2Console", (Class<? extends Servlet>) WebServlet.class);
holder.setInitOrder(1);
holder.setInitParameter("webAllowOthers", "true");
ServletHolder[] holders = { holder };
ServletMapping servletMapping = new ServletMapping();
servletMapping.setServletName("H2Console");
servletMapping.setPathSpec("/console/*");
ServletMapping[] servletMappings = {servletMapping};
servletHandler.setServlets(holders);
servletHandler.setServletMappings(servletMappings);
derbyWebapp.setServletHandler(servletHandler);
My web.xml is
<servlet>
<servlet-name>SwipeXBackendServices</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.mycompany.backend.webservices</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SwipeXBackendServices</servlet-name>
<url-pattern>/webservices/*</url-pattern>
</servlet-mapping>
Why does the mapping have to be /webservices/* and not com.mycompany.backend.webservices/*.
This is my webservice code
package com.swipex.backend.webservices;
#Path("/Activation")
public class Activation {
private static final Logger log = Logger.getLogger(Activation.class);
private DeviceDetails deviceDetailsTable = DeviceDetails.getInstance();
#POST
#Path("Request")
#Consumes({ MediaType.APPLICATION_JSON })
public Response post(JAXBElement<CDeviceDetails> device) {
Edit : Junit Client Code
#Test
public void testPost() throws Exception {
/*
* Activate
*/
// Initialize Web Services
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
//
// Activation Service
//
URI url = UriBuilder.fromUri(
"http://" + SERVER + "/SwipeXBackEnd/Activation/Request")
.build();
WebResource service = client.resource(url);
System.out.println(url);
// Get the data ready
CDeviceDetails newDevice = new CDeviceDetails(null, "mac id", "model",
"manufacturer", "androidVersion", Calendar.getInstance()
.getTime(), "buildID", "fingerPrint", "board",
"bootLoader", "hardware", "id", "product", "serial", Calendar
.getInstance().getTime(), Calendar.getInstance()
.getTime(), Globals.ACTIVE);
String deviceUniqueIdentity = service.type(MediaType.APPLICATION_JSON)
.post(String.class, newDevice);
assertNotNull(deviceUniqueIdentity);
System.out.println("Activation Passed " + deviceUniqueIdentity);
Url pattern doesn't have any relationship with your java package structure. In your case, you've mapped the SwipeXBackendServices servlet to listen on the '/webservices' url (ex : http://localhost:8080/webservices/). i.e., when a request comes for '/webservices', it'll be routed to your 'SwipeXBackendServices' servlet class.
And also your jax-rs (rest) class 'Activation' is mapped to the url '/activation' (ex : http://localhost:8080/activation).
Mapping of url & class :
/webservices ===> SwipeXBackendServices
/activation ===> Activation
So, don't confuse the package name with the url name.
As Sriram says the servlet mappings have no relation with the packages.
The com.sun.jersey.config.property.packages parameter for the SwipeXBackendServices servlet just tells Jersey which package to scan for discovering REST resources.
The servlet-mapping for the SwipeXBackendServices servlet tells the servlet container on which URLs the servlet will answer. In this case, it will answer to all URLs prefixed with /webservices. Meaning that all REST resources will be prefixed with /webservices.
Then, the URLs for any REST resource will be the concatenation of
The WAR context root
The Jersey servlet mapping
The REST resource path (being itself the concatenation of the class's path and the method's path)
Hence, assuming the context root for your WAR is /myWebApp, the URL for a POST request to your Activation resource will be: http://localhost:8080/myWebApp/webservices/Activation/Request
I am a beginner in JBoss.
I am making a database application that uses JPA. For exposing the same I have to expose a SOAP based web service. I followed the basic JBoss tutorial
http://docs.jboss.org/tools/4.1.0.Final/en/ws_soap_reference/html/topdown.html#bottomupws
to create a web service.
To consume the web service I created a client using the following tutorial.
http://docs.jboss.org/tools/4.1.0.Final/en/ws_soap_reference/html/client.html
But when I am executing this client I am getting a 404 error. The exact same issue is reported on the JBoss community
https://community.jboss.org/thread/164471?tstart=0
My Webservice bean looks something like this:
#WebService(name = "Management", targetNamespace = "http://www.example.org/Management")
public class Management {
#PersistenceContext
private EntityManager em;
#EJB(name = "ejb/ar/ClientEjb", mappedName = "ejb/ar/ClientEjb")
ClientRegistration clientReg;
#WebMethod(action = "http://www.example.org/Management/getAccount")
#WebResult(name = "getAccountResponse", partName = "getAccountResponse")
public String getAccount(
#WebParam(name = "getAccountRequest", partName = "getAccountRequest") String param) {
return "account-56789012354349";
}
#WebMethod(action = "http://www.example.org/Management/addClient")
public List<Client> addClient
(#WebParam(name = "clientId", partName = "clientId") long clientId ){
//For time being let's forget this method.
return null;
}
}
The web.xml has:
<servlet>
<display-name>Management</display-name>
<servlet-name>Management</servlet-name>
<servlet-class>org.jboss.tools.examples.service.Management</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Management</servlet-name>
<url-pattern>/Management</url-pattern>
</servlet-mapping>
The Management.wsdl has:
<wsdl:service name="ManagementService">
<wsdl:port name="ManagementPort" binding="tns:ManagementServiceSoapBinding">
<soap:address location="http://localhost:9090/Management"/>
</wsdl:port>
</wsdl:service>
The ClientSample.java is:
public class ClientSample {
public static void main(String[] args) {
System.out.println("***********************");
System.out.println("Create Web Service Client...");
ManagementService service1 = new ManagementService();
System.out.println("Create Web Service...");
Management port1 = service1.getManagementPort();
System.out.println("Call Web Service Operation...");
System.out.println("Server said: " + port1.getAccount(null));
System.out.println("Server said: " + port1.addClient(0));
//Please input the parameters instead of 'null' for the upper method!
System.out.println("Create Web Service...");
Management port2 = service1.getManagementPort();
System.out.println("Call Web Service Operation...");
System.out.println("Server said: " + port2.addClient(Long.parseLong(args[1])));
System.out.println("Server said: " + port2.getAccount(null));
//Please input the parameters instead of 'null' for the upper method!
System.out.println("***********************");
System.out.println("Call Over!");
}
}
Exception is like below:
***********************
Create Web Service Client...
Create Web Service...
Call Web Service Operation...
Exception in thread "main" com.sun.xml.internal.ws.client.ClientTransportException: The server sent HTTP status code 404: Not Found
at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.checkStatusCode(HttpTransportPipe.java:196)
at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:168)
at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:83)
at com.sun.xml.internal.ws.transport.DeferredTransportPipe.processRequest(DeferredTransportPipe.java:105)
at com.sun.xml.internal.ws.api.pipe.Fiber.__doRun(Fiber.java:587)
at com.sun.xml.internal.ws.api.pipe.Fiber._doRun(Fiber.java:546)
at com.sun.xml.internal.ws.api.pipe.Fiber.doRun(Fiber.java:531)
at com.sun.xml.internal.ws.api.pipe.Fiber.runSync(Fiber.java:428)
at com.sun.xml.internal.ws.client.Stub.process(Stub.java:211)
at com.sun.xml.internal.ws.client.sei.SEIStub.doProcess(SEIStub.java:138)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:98)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:78)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:110)
at com.sun.proxy.$Proxy29.getAccount(Unknown Source)
at org.jboss.tools.examples.service.jaxws.clientsample.ClientSample.main(ClientSample.java:20)
I feel this may be because the service is not getting published, but I can't find I way to publish it.
I am using jboss-as-7.1.0.Final Runtime Server.
Note: Just to see if my Client code is correct I published the bean using
Endpoint.publish("http://localhost:9090/Management", new org.jboss.tools.examples.service.Management());
in the client code itself. The web service could be hit, but this is not in the application context of my application.
I have a Spring MVC REST service running locally through Tomcat on port 8080. When I hit the service directly through the browser or fiddler, it works as expected. I also have a local website built with AngularJS. If I place this website in the Tomcat directory and navigate to the website's link served by Tomcat, everything still works fine.
The problem is when I don't place the website in the Tomcat directory. Say I place the website project on my desktop and navigate to a page that is supposed to retrieve data from the local REST service... I can see in fiddler that the request is going through, hitting the service, and the service returns the data... but the website doesn't display it!
Why would it work fine only when both are served by Tomcat?
Link to hit service directly:
http://localhost:8080/CPProject/users (returns all the users, in JSON format)
Website (this works):
http://localhost:8080/CPWebsite/app/#/users
Website (doesn't work):
http://localhost:63342/CPWebsite/app/index.html#/users (63342 is the port used by Webstorm 7.0)
or
file:///C:/Users/someuser/CPWebsite/app/index.html#/users
My Angular controllers look like this:
cpControllers.controller('UserListCtrl',
function($scope, $http) {
$http.get('http://localhost:8080/CP/users')
.success(function(data) {
$scope.users = data;
}
)
}
);
I've also created a CORS filter in my Sprint MVC service:
#Component
public class SimpleCORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
You'll need to add correct configured CORS filter to your app that allows preflight requests. Here's an example configuration:
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowGenericHttpRequests</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowSubdomains</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, HEAD, POST, PUT, DELETE, OPTIONS</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.maxAge</param-name>
<param-value>3600</param-value>
</init-param>
<init-param>
<param-name>cors.tagRequests</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
Thanks for the answers above. It turns out my CORS filter was fine, but I had forgotten to add it to my application initializer in order for the filter to actually work:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
WebApplicationContext rootContext = createRootContext(servletContext);
configureSpringMvc(servletContext, rootContext);
FilterRegistration.Dynamic corsFilter = servletContext.addFilter("corsFilter", SimpleCORSFilter.class);
corsFilter.addMappingForUrlPatterns(null, false, "/*");
}
See here for more info: http://jpgmr.wordpress.com/2013/12/12/cross-origin-resource-sharing-cors-requests-with-spring-mvc/