Intro
Jersey: 2.9
This part of Jersey documentation describes how to provide authorization for REST services. There are two ways to do that:
standard Servlet way, using configuration in web.xml
much better solution using JSR 250 annotations
The First approach works fine, but I cannot make the second work.
Case 1 (using web.xml):
This example works. It is for informational purpose. If you want just jump do the second one, which does not work.
Resource is very simple:
#Path("/HelloWorld")
public class HelloWorldResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String sayHelloWorld(){
return "Hello World!!!";
}
}
Security configuration is placed in web.xml file, which looks like that:
<web-app ...>
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<!-- SECURITY -->
<security-constraint>
<web-resource-collection>
<url-pattern>/rest/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>boss</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DefaultRealm</realm-name>
</login-config>
</web-app>
This example works fine. When I try to access http://{myhost}:8080/{war_name}/rest/HelloWorld I have to provide username and password. This means that Realm and configuration in database is just fine. So there is no need to show it here.
Case 2 (JSR 250 annotations):
This example does not work. The resource is almost the same as in the first example, just some annotations are added:
#Path("/HelloWorld")
#PermitAll
public class HelloWorldResource {
#RolesAllowed("boss")
#GET
#Produces(MediaType.TEXT_PLAIN)
public String sayHelloWorld(){
return "Hello World!!!";
}
}
As you see, two annotations are added. It is the same security policy like in the first example, but defined using annotations insted of web.xml.
Now web.xml looks like that:
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DefaultRealm</realm-name>
</login-config>
</web-app>
In addition new class is added (like described in documentation):
#ApplicationPath("rest")
public class MyApplication extends ResourceConfig {
public MyApplication() {
super(HelloWorldResource.class);
register(RolesAllowedDynamicFeature.class);
}
}
This class is important. As you see required RolesAllowedDynamicFeature is registered.
So two main steps:
- adding annotations to resource
- registering RolesAllowedDynamicFeature
are done.
Problem:
Second example does not work. Popup to provide username and password never shows up. Every time response is 403 Forbidden. This is not a problem with Realm and database configuration since this configuration works fine with the first example.
So the question is: what is wrong with my second implementation?
Tnaks in advance.
Instead of the ResourceConfig sub-class, try add this to you web.xml block
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.RolesAllowedResourcFilterFactory</param-value>
</init-param>
Related
I'm implementing role-based authorization in my application following the structure of the java ee 8 jwt security sample: https://github.com/javaee-samples/javaee8-samples/tree/master/security/jwt.
I have a back-end application with session-based security and JSF. Authentication and Authorization is managed by a WebFilter:
#WebFilter(urlPatterns = "/faces/*")
public class AuthorizationFilter implements Filter {
}
I have a REST api with JWT token-based security where I want to implement HttpAuthenticationMechanism for managing authentication and authorization.
I want these two different security mechanisms to live next to each other for personal interest and to prove that I can implement both ways. However, the HttpAuthenticationMechanism gets called everytime, also when browsing my JSF application. This leads to triggering of both mechanisms that are conflicting.
Is it possible to apply the HttpAuthenticationMechanism to only a certain url path? Like the urlPattern that's used in WebFilter? If so, how does one?
I want to use the HttpAuthenticationMechanism only to be triggered in my rest application:
#ApplicationPath("rest")
public class RestApplication extends Application {
public RestApplication() {
}
}
There is a method "isProtected" in HttpMessageContext which will set to true/false depending on the security-constraints in web.xml
public AuthenticationStatus validateRequest(HttpServletRequest request,
HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthenticationException {
if (!httpMessageContext.isProtected()) {
return httpMessageContext.doNothing();
}
...
So the first thing to do is check isProtected and only continue if the resource is supposed to be protected.
Now you can use security-constraint in web.xml as usual:
...
<security-constraint>
<web-resource-collection>
<web-resource-name>Restricted</web-resource-name>
<description>Declarative security tests</description>
<url-pattern>/protected/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>somegroup</role-name>
</auth-constraint>
<user-data-constraint>
<description>no description</description>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-role>
<description>Somegroup</description>
<role-name>somegroup</role-name>
</security-role>
...
Now authentication will only be done for URLs below the /protected/* pattern, anything else will be available unprotected
Wondering why this is not addressed in most of the tutorials/examples out there...
Credits goes to this link which seems to be the only one describing this (althoug there is a a missing "NOT" in the Listing No 16)
https://www.informatik-aktuell.de/entwicklung/programmiersprachen/authentication-mit-java-ee-8.html
My web.xml looks like this:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.ajayramesh.jrecycled.servlets.Login</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
I have a class called Login.java with an auto-generated annotation that says:
#WebServlet("/login")
right above my HttpServlet class extension declaration. When this annotation is present, I get the following error when I try to start my server:
The servlets named [LoginServlet] and [com.ajayramesh.jrecycled.servlets.Login] are both mapped to the url-pattern [/login] which is not permitted
I only have one definition in my web.xml and only a single web.xml. When I remove this annotation, the server works fine. To my understanding, annotations are not supposed to have an effect on the runtime of the program, and are only meant to optimize compilation? On a side note, what exactly does that annotation do?
You can't use the same mapping for both annotation and web.xml, you can use either one of it. App server treat it as duplicate url mapping.
Basically, declaring servlet and servlet-mapping elements in web.xml is equal to annotating a servlet class with #WebServlet.
I have spring mvc application
if in web.xml i write so:
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I go to http://localhost:8080/Mvc/controllerPath/sayHello
I see my page
if I write
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/controllerPath/*</url-pattern>
</servlet-mapping>
I go to http://localhost:8080/Mvc/controllerPath/sayHello - I see 404
I think you understood what I want.
Can you hel me?
UPDATE
controller:
#Controller
#RequestMapping("/controllerPath")
public class MyController {
#RequestMapping("/sayHello")
public String sayHello(Model model){
model.addAttribute("name", "Vasya");
return "hello";
}
}
if I write
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/Mvc/controllerPath*</url-pattern>
</servlet-mapping>
i see 404
If you remove:
#RequestMapping("/controllerPath")
to
#RequestMapping("/")
the new servlet mapping will work.
The reason for this is that whatever you have in the servlet mapping url is stripped before spring tries to match it to a controller.
For example, in your first url mapping with just / (and assuming your web appllication is deployed to /mvc), your path of /mvc/controllerPath/sayHello spring strips the url mapping away from the url and expects to find a controller that maps to /controllerPath/sayHello
When you change the url-pattern to /controllerPath, since will strip that as well and look for a controller that answers to just /sayHello, which your controller won't since it's expecting /controllerPath/sayHello
I'd like to intercept the OPTIONS request with my controller using Spring MVC, but it is catched by the DispatcherServlet. How can I manage that?
I added some more detail to the Bozho answer for beginners.
Sometimes it is useful to let the Spring Controller manage the OPTIONS request (for example to set the correct "Access-Control-Allow-*" header to serve an AJAX call).
However, if you try the common practice
#Controller
public class MyController {
#RequestMapping(method = RequestMethod.OPTIONS, value="/**")
public void manageOptions(HttpServletResponse response)
{
//do things
}
}
It won't work since the DispatcherServlet will intercept the client's OPTIONS request.
The workaround is very simple:
You have to... configure the DispatcherServlet from your web.xml file as follows:
...
<servlet>
<servlet-name>yourServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
...
Adding the "dispatchOptionsRequest" parameter and setting it to true.
Now the DispatcherServlet will delegate the OPTIONS handling to your controller and the manageOption() method will execute.
Hope this helps.
PS. to be honest, I see that the DispatcherServlet append the list of allowed method to the response. In my case this wasn't important and I let the thing go. Maybe further examinations are needed.
#RequestMapping(value="/youroptions", method=RequestMethod.OPTIONS)
public View getOptions() {
}
You should configure the dispatcherServlet by setting its dispatchOptionsRequest to true
As a quick supplement to the above 2 answers, here's how to enable dispatchOptionsRequest in a servlet 3 (no web.xml) environment as it took me a while to work out how to apply the answers above in a non-xml setup.
In a spring 3.2 / servlet 3 environment, you will have some variety of DispatcherServlet initializer class which is the java equivalent of web.xml; in my case it's the AbstractAnnotationConfigDispatcherServletInitializer. Adding the following code will enable dispatchOptionsRequest:
#Override
protected void customizeRegistration(Dynamic registration) {
registration.setInitParameter("dispatchOptionsRequest", "true");
}
I took the following approach:
Using Maven (or manually) pull in this dependancy:
<dependency>
<groupId>com.thetransactioncompany</groupId>
<artifactId>cors-filter</artifactId>
<version>1.3.2</version>
</dependency>
This has an implementation to capture all the inbound OPTIONS requests. Into the web.xml file add the following config:
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Content-Type,Accept,Origin</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The problem I've seen with the /** approach is a more specific Controller implementation will override this.
For Spring without web.xml file, and based on Paul Adamson answer, I just set the parameter dispatchOptionsRequest to true into the dispatcher, to process the Options method calls.
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(applicationContext));
dispatcher.setInitParameter("dispatchOptionsRequest", "true");
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/*");
Could you help to check why doFilter not getting called
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<filter>
<filter-name>roseFilter</filter-name>
<filter-class>net.paoding.rose.RoseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>roseFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
</web-app>
class signature:
import org.springframework.web.filter.GenericFilterBean;
public class RoseFilter extends GenericFilterBean {
404 is returned while call http://localhost:8080/hello/world, I set the breakpoints at
doFilter, it seems doFilter not called?(I tried tomcat 6.0.18, 6.0.29, jdk1.6)
The filter won't be invoked when:
The filter class is missing in the classpath and/or is not loadable or instantiable. You should however have noticed it in the server's startup logs. Solution is to be found based on the interpretation of the exceptions/errors found in the server logs.
There's another filter running before in the chain which isn't calling FilterChain#doFilter(), but rather RequestDispatcher#forward() or include() which caused the subsequent filters in the chain being completely skipped (when they do not listen on FORWARD or INCLUDE dispatchers; they by default only listens on REQUEST dispatcher). Solution is either to fix the wrong filter, or to add <dispatcher>FORWARD</dispatcher> etc accordingly, or to rearrange the filter declarations in web.xml so that your new filter comes before the another filter (you in turn only need to ensure that your new filter is using the FilterChain#doFilter() properly :) ).
The request URL is plain wrong. You used http://localhost:8080/hello/world. With a filter listening on /*, this means that the webapp context should be ROOT or at least /hello. Verify your webapp context. I'd just retry with an URL which points to a valid JSP/Servlet inside the same webapp which generates a non-404 response. Does the filter then get called as well?
What's the web request look like? Can you try changing your url-pattern to *.jsp instead of / * ? If you are using something other than pure JSP then change it to whatever the request ending extension is (like for struts it is usually *.do).