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
Related
This question already has answers here:
How to redirect to Login page when Session is expired in Java web application?
(9 answers)
Closed 1 year ago.
I'm creating a website with HTML/CSS/Javascript/JSP and Tomcat v10 Java Servlets and have built a login system that creates a HttpSession once the user signs in. Even though this dynamically removes the login button, there is nothing stopping that user from copying and pasting the url to the login page.
Is there an effective way to immediately redirect to a different html file if a session does not exist? I saw some other posts using PHP but I've never used that technology and was hoping there is a diff way.
As far as I know, url to the JSP is treated like a request to the server
You can use a Filter for it.
Filter is for pre and post processing a request, you can use it to check if inbound request have session or not
Something like this:
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
HttpSession session = httpRequest.getSession();
AdUser user = (AdUser)session.getAttribute("user");
if (user == null) {
httpRequest.setAttribute("errorMessage", "You must login first");
httpRequest.getRequestDispatcher("Authenticate.jsp").forward(request, response);
} else {
chain.doFilter(request, response);
}
Instead of creating homegrown filter you should utilize Java EE security API
Create security constraint that will only allow authenticated users to access given parts of the application. In that case server will make sure that unauthenticated users cannot access given pages.
You could use opensource OpenLiberty server which fully implements Java EE API, if Tomcat is not implementing that spec fully.
Something like (not complete code just a sample to give you an idea):
<!-- SECURITY CONSTRAINT -->
<security-constraint>
<web-resource-collection>
<web-resource-name>protected pages</web-resource-name>
<url-pattern>/html/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>AUTH_USER</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>file</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
....
I'm running a JAX-RS webservice and I want to protect all of it using secure connections. So I added this to my web.xml:
<security-constraint>
<web-resource-collection>
<web-resource-name>Prohibit unsecured HTTP</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
This works all fine and dandy (with redirect from HTTP to HTTPS set up in my EAP standalone.xml, but I'll probably turn that off).
However, when I now want to set up BasicAuth for some resources I add another, more narrow constraint:
<security-constraint>
<web-resource-collection>
<web-resource-name>Project editing</web-resource-name>
<url-pattern>/projects</url-pattern>
<url-pattern>/projects/*</url-pattern>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>PATCH</http-method>
<http-method>DELETE</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>AUTH_USER</role-name>
</auth-constraint>
</security-constraint>
then suddenly requests to these URLs -and the GET requests not even mentioned here- are happily served via HTTP.
If I understand the EE6 documentation correctly then in the case of several security-constraints it would combine url-patterns and http-methods. But it doesn't say anything about the transport-guarantee.
How can I solve this? HTTPS everywhere and BasicAuth on some URLs?
I've tried these two alternatives so far:
1) Add the CONFIDENTIAL guarantee also to the second security-constraint. This helped half-way. Now POST, PUT, etc. are HTTPS-only, but GET requests to "projects" still go through on HTTP.
This could be remedied by adding a third security-constraint block that defines CONFIDENTIAL but no authentication for GET requests, but this feels intuitively wrong and incredibly hard to maintain.
2) Screw web.xml and reject HTTP requests using a #WebFilter instead:
#WebFilter(filterName = "NoHttpFilter", urlPatterns = { "/*" })
public class NoHttpFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest requ = (HttpServletRequest) req;
if (!requ.isSecure()) {
HttpServletResponse resp = (HttpServletResponse) res;
resp.reset();
resp.sendError(404, "If you're not speaking HTTPS, I'm not listening.");
} else {
chain.doFilter(req, res);
}
}
public void destroy() {
}
}
This actually works like a charm, but unfortunately it runs after BasicAuth, so calls to protected places will first ask for the password and then refuse entry based on the protocol.
Same issue here with tomcat9.
In the tomcat global web.app I have added the CONFIDENTIAL. It works for the ROOT webapp.
But for webapp #2 it does not.
So it seems one of webapp #2's web.xml settings is overruling it.
Thoughts?
<security-constraint>
<web-resource-collection>
<url-pattern>/rest/*</url-pattern>
<url-pattern>/ui5/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>connectorview</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login</form-login-page>
<form-error-page>/error</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>role1</role-name>
</security-role>
Spec:
Servlet: 3.0
Java: 7
Tomcat: 7.0.54
Intro:
It is possible to check programatically if user has a specific role using method HttpServletRequest.isUserInRole()
For example:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
String username = null;
String password = null;
//get username and password manually from Authorization header
//...
request.login(username, password);
if (request.isUserInRole("boss")) {
//do something
} else {
//do something else
}
request.logout();
}
This works fine, but this solution requires to manually retrieve username and password from Authorization header and then login using these credentials.
Questions:
Is it possible to just do something like that? With no retrieving data from header and manually login()?
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
if (request.isUserInRole("boss")) {
//do something
} else {
//do something else
}
}
Trying to answer myself:
From my understanding this code requires proper configuration in web.xml. This example works with this configuration in web.xml file, for example:
<web-app ...>
...
<security-constraint>
<web-resource-collection>
<url-pattern>/HelloWorld</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>
But this means that programatically checking roles is not required since configuration in web.xml it is all we need to restrict access.
Summary:
is it possible to programatically checking roles without specifing restrictions (auth-constraint) in web.xml?
if not, does this mean, that using isCallerInRole() performing only checking for additional roles, becouse main required role is specified in web.xml?
Thanks.
Edit 1:
Since the first answer suggest adding login-config element to my web.xml, I must say I already have it. I added this to code snippet, as I didn't include it when posting question. And example works with this configuration. But when I remove auth-constraint or the whole security-constraint, presence of login-config is not enought.
I added info about container: Tomcat 7.0.54.
Question1:
Is it possible to programatically checking roles without specifing restrictions (auth-constraint) in web.xml?
Answer:
Yes, it is possible. There is no need to specify restrictions in web.xml. There is no need to put scurity-contraint in web.xml.
In addition there is no need to manually retrieve credentials from header Authorization and then manually login().
Solution:
Here is a working example:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
request.authenticate(response); //solution
if (request.isUserInRole("boss")) {
//do something
} else {
//do something else
}
}
web.xml:
<web-app ...>
...
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DefaultRealm</realm-name>
</login-config>
</web-app>
And that works.
As you see method HttpServletRequest.authenticate() is used nad does the trick.
Documentation says:
Triggers the same authentication process as would be triggered if the request is for a resource that is protected by a security constraint.
That is all we need.
I hope it helps someone in the future.
The basic authorization mechanism provided by servlets in web.xml is basic and mostly 'hard-coded'
If you want to implement a more elaborate way of checking user roles/authorizations, you need to secure your servlets then you have a few possibilities:
properly implement JAAS. There quite few tutorials around; here is one for Tomcat
a probably more powerful alternative is to use Shiro
Here is the answer for your issue, if you are using Basic authentication, add this:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>ourRealm</realm-name>
</login-config>
I´m tried to create a FORM authentication but I dont need any role.
The pattern I want to block is "/numbers" so I try:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login</form-login-page>
<form-error-page>/login?erro=true</form-error-page>
</form-login-config>
</login-config>
<security-constraint>
<display-name>Numeros da sorte</display-name>
<web-resource-collection>
<web-resource-name>Numeros da sorte</web-resource-name>
<description/>
<url-pattern>/numbers</url-pattern>
</web-resource-collection>
</security-constraint>
but it doesnt work because it allows everyone to /numbers without ask for authentication, so if I put some role there, it start working.
There is a way to control authorization without role (just with the act of authentication) ?
Yes it is possible, but you need something else in addition to configurations in web.xml.
In web.xml, using auth-constraint element, you specify the role, say, admin, that can access the protected resource. And then, in the application server, you map all authenticated users to admin role. By doing this mapping, we assign admin role to all authenticated users. Because all authenticated users have admin role, these users can access the protected resource.
I would like to implement a simple authentication in an JSF/Primefaces application. I have tried lots of different things, e.g. Dialog - Login Demo makes a simple test on a dialog, but it does not login a user or?
I also have taken a look at Java Security, like:
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/protected/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>REGISTERED_USER</role-name>
</auth-constraint>
</security-constraint>
With this everything under /protected is protected, but as far as i understand i need to define a realm in the server for authentication. I do not want to specify this in the server but just have a simple lookup in the database for it.
Is there a way to authenticate a user without defining something in the application server? Or anonther simple solution to authenticate and protect different pages?
Is there a way to authenticate a user without defining something in the application server?
This is a long and very thorny story. It often comes up as one the main points of criticism against Java EE.
The core of the story is that traditionally Java EE applications are supposed to be deployed with "unresolved dependencies". Those dependencies have to be satisfied at the application server, typically by someone who is not a developer, often by using some kind of GUI or console.
Security configuration is one of those unresolved dependencies.
If the security configuration is done at the application server, this is by definition always not portable, e.g. it has to be done in an application server specific way. It completely rules out using application domain models (e.g. a JPA entity User) for this authentication.
Some servers (e.g. JBoss AS) allow configuring their proprietary security mechanisms from within the application, and additionally allow for 'custom login modules' (the term is different for pretty much every server) to be loaded from the application as well. With some small hacks, this will allow the usage of application domain models and the same data source that the application itself uses for authentication.
Finally, there's a relatively unknown portable way to do authentication from within an application. This is done via the JASPIC SPI, also known as JASPI or JSR 196. Basically, this JASPIC seems to be what you are looking for.
Unfortunately, JASPIC is not perfect and even though it's a technology from Java EE 6 and we're almost at Java EE 7, at the moment support for JASPIC in various application servers is still sketchy. On top of that, even though JASPIC is standardized, application server vendors still somehow require proprietary configuration to actually make it work.
I wrote an article about JASPIC that explains the current problems in more detail: Implementing container authentication in Java EE with JASPIC
I have found a for me suitable and easy solution by simply using Web Filters. I have added a filter to web.xml like
<!-- Authentication Filter -->
<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>org.example.filters.AuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/protected/*</url-pattern>
</filter-mapping>
The filter looks something like this
#WebFilter(filterName="AuthenticationFilter")
public class AuthenticationFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Cookie[] cookies = ((HttpServletRequest)request).getCookies();
// Try to find a valid session cookie
if (cookies != null) {
String sessionId = null;
for (Cookie cookie : cookies) {
if ("sessionId".equals(cookie.getName())) {
sessionId = cookie.getValue();
}
}
// Check if we have a valid session
UserSession session = Backend.getInstance().getSessionGateway().getBySessionId(sessionId);
if (session != null) {
chain.doFilter(request, response);
return;
} else if (sessionId != null) {
// Remove the cookie
Cookie cookie = new Cookie("sessionId", null);
cookie.setMaxAge(-1);
((HttpServletResponse)response).addCookie(cookie);
}
}
// Problem due to relative path!!
// ((HttpServletResponse)response).sendRedirect("../login.xhtml");
RequestDispatcher rd = request.getRequestDispatcher("/login.xhtml");
rd.forward(request, response);
}
}
So i had just to implement a Bean that authenticates and sets the session cookie. I will add the user agent to have additional security but it basically works.
The only problem i have that i can not do a redirect, cause it is not using context path but just redirects to /index.xhtml instead of /my_app_context/index.xhtml