Quantcast
Viewing latest article 6
Browse Latest Browse All 10

Security Configuration in web.xml and glassfish-web.xml

We will start with the web.xml. The first interesting part is is the configuration of Jersey. As you can see everything that matches the url pattern /services/* is handled by the Jersey Servlet – so those requests are considered to be our REST services (of course, if they don’t exist you will get an HTTP 404 error):

web.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?xml version="1.0" encoding="UTF-8"?>
         xmlns="http://java.sun.com/xml/ns/javaee"
         id="WebApp_ID" version="3.0">
  <display-name>Authentication</display-name>
  <welcome-file-list>
    <welcome-file>welcome.jsp</welcome-file>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
 
  <!-- Jersey REST -->
  <servlet>
    <servlet-name>ServletAdaptor</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    
    <!-- this would allow us to get rid of @XmlRootElement -->
    <!-- but since we use jersey-json we don't need this -->
    <!--
    <init-param>     
      <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
      <param-value>true</param-value>
    </init-param>
     -->
    
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletAdaptor</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
 
  <listener>
    <listener-class>com.nabisoft.web.listener.EventListener</listener-class>
  </listener>
 
  <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>userMgmtJdbcRealm</realm-name>
    <form-login-config>
      <form-login-page>/WEB-INF/login.jsp</form-login-page>
      <form-error-page>/WEB-INF/login.jsp?auth-error=1</form-error-page>
    </form-login-config>
  </login-config>
 
  <!--
  you could also define a page that is displayed if
  glassfish determins that an authenticated user is not
  authorized to access a resource
  <error-page>
    <error-code>403</error-code>
    <location>/not-authorized.html</location>
  </error-page>
  -->
  
  <security-constraint>
 
    <!-- everything below /secure/* and /services/secure/* requires authentication -->
    <web-resource-collection>
      <web-resource-name>Secured Content</web-resource-name>
      <url-pattern>/secure/*</url-pattern>
      <url-pattern>/services/secure/*</url-pattern>
    </web-resource-collection>
     
    <!-- only users with at least one of these roles are allowed to access the secured content -->
    <auth-constraint>
      <role-name>ADMINISTRATOR</role-name>
      <role-name>USER</role-name>
    </auth-constraint>
 
    <!-- we always want https! -->
    <user-data-constraint>
      <description>highest supported transport security level</description>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
 
  </security-constraint>
 
  <!-- declare the roles relevant for our webapp -->
  <security-role>
    <role-name>ADMINISTRATOR</role-name>
  </security-role>
  <security-role>
    <role-name>USER</role-name>
  </security-role>
  <security-role>
    <role-name>DEFAULT</role-name>
  </security-role>
  
  <session-config>
    <!-- on productive systems you might have another value for the timeout -->
    <session-timeout>5</session-timeout>
    <!--
       we don't want to use the default name JSESSIONID because this
       tells everyone (especially hackers) that our application is based on java
    -->
    <cookie-config>
      <name>SESSIONID</name>
    </cookie-config>
  </session-config>
 
</web-app>

Right after the Jersey/REST configuration I have configured a listener (see EventListener.java below). I thought it might be interesting to see what happens in the background, i.e. the listener tells you whenever a session has been destroyed. The source code is listed below. Finally we come to our login configuration. We will use form based login with our own login page. Please consider that our login config has specified userMgmtJdbcRealm as the realm name to be used (keep this in mind for later). Next we want to tell Glassfish that everything under /secure/* is secured content which requires users to be authenticated (login) and afterwards authorized if they want to access that content. We want only users that have at least one of the roles ADMINISTRATOR or USER to be allowed accessing our secured content. And last but not least we want to secure the communication via HTTPS by using CONFIDENTIAL for transport guarantee. This tells Glassfish to automatically redirect HTTP requests to HTTPS when trying to access restricted content. But as you might remember we want both the restricted content as well as our login/registration page to be accessible via HTTPS only. We don’t want to accept HTTP for those pages! To achieve this we need to find a way to programically redirect all HTTP requests going to our login/registration page to their HTTPS equivalents. This can be easily achived by implementing a simple filter (see HttpToHttpsFilter.java below) that checks the protocol and always redirects to HTTPS in case we have an HTTP request. I have choosen to configure the filter via the @WebFilter(“/”) annotation instead of adding the corresponding configuration to the web.xml file. For demonstration I have only choosen “/” as the URL pattern that shall match our filter. I have choosen this URL pattern only because our user registration page is found there. The design I have choosen for implementing the filter is not good for re-using the filter. A better implementation would be to configure the filter via web.xml and to pass some init-params which would tell the filter which URL patterns should be redirected to HTTPS and which port to use for HTTPS. But we are not going to make it too complicated here Image may be NSFW.
Clik here to view.
:-)
You could also implement the redirection directly in the corresponding /index.jsp file instead of using a filter. Actually, this would be better because it’s simply a waste to have a filter for exactly one URL pattern. I just wanted to show you how to use a filter with annotations which allows you to switch from HTTP to HTTPS. Let me try to come to a conclusion: Glassfish will automatically ask for login in case an unauthenticated user tries to access secured content by showing our custom login page (see /WEB-INF/login.jsp above). Glassfish will also make sure that only HTTPS requests are allowed for our secured content. If our login form is displayed automatically by Glassfish then Glassfish also makes sure to use HTTPS here as well. Our HttpToHttpsFilter will only redirect from HTTP to HTTPS if the requested resource is our registration page that can be found at /index.jsp (see above).

HttpToHttpsFilter.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.nabisoft.web.filter;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//instead of defining the filter in our web.xml we will use an annotation
@WebFilter("/welcome.jsp")
public class HttpToHttpsFilter implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
        // if you have any init-params in web.xml then you could retrieve them
        // here by calling config.getInitParameter("my-init-param-name").
        // example: you could define a comma separated list of paths that should be
        // checked for http to https redirection
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        System.out.println("HttpToHttpsFilter: URL requested = "+request.getRequestURL().toString());
        
        if ( !request.isSecure() ) {
            String url = request.getRequestURL().toString().replaceFirst("http", "https");
            url = url.replaceFirst(":8080/", ":8181/");  //quick and dirty!!!
            
            //don't forget to add the parameters
            if (request.getQueryString() != null)
                url += "?" + request.getQueryString();
            System.out.println("HttpToHttpsFilter redirect to: "+url);
            response.sendRedirect(url);
        } else {
            chain.doFilter(req, res); // we already have a https connection ==> so just continue request
        }
    }
    @Override
    public void destroy() {
        // release resources if you have any
    }
}
EventListener.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.nabisoft.web.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class EventListener implements ServletContextListener,
        HttpSessionAttributeListener, HttpSessionListener {
    /**
     * The servlet context with which we are associated.
     */
    private ServletContext context = null;
    /**
     * Record the fact that a servlet context attribute was added.
     *
     * @param event
     *            The session attribute event
     */
    public void attributeAdded(HttpSessionBindingEvent event) {
        log("attributeAdded('" + event.getSession().getId() + "', '"
                + event.getName() + "', '" + event.getValue() + "')");
    }
    /**
     * Record the fact that a servlet context attribute was removed.
     *
     * @param event
     *            The session attribute event
     */
    public void attributeRemoved(HttpSessionBindingEvent event) {
        log("attributeRemoved('" + event.getSession().getId() + "', '"
                + event.getName() + "', '" + event.getValue() + "')");
    }
    /**
     * Record the fact that a servlet context attribute was replaced.
     *
     * @param event
     *            The session attribute event
     */
    public void attributeReplaced(HttpSessionBindingEvent event) {
        log("attributeReplaced('" + event.getSession().getId() + "', '"
                + event.getName() + "', '" + event.getValue() + "')");
    }
    /**
     * Record the fact that this web application has been destroyed.
     *
     * @param event
     *            The servlet context event
     */
    public void contextDestroyed(ServletContextEvent event) {
        log("contextDestroyed()");
        this.context = null;
    }
    /**
     * Record the fact that this web application has been initialized.
     *
     * @param event
     *            The servlet context event
     */
    public void contextInitialized(ServletContextEvent event) {
        this.context = event.getServletContext();
        log("contextInitialized()");
    }
    /**
     * Record the fact that a session has been created.
     *
     * @param event
     *            The session event
     */
    public void sessionCreated(HttpSessionEvent event) {
        log("sessionCreated('" + event.getSession().getId() + "')");
    }
    /**
     * Record the fact that a session has been destroyed.
     *
     * @param event
     *            The session event
     */
    public void sessionDestroyed(HttpSessionEvent event) {
        log("sessionDestroyed('" + event.getSession().getId() + "')");
    }
    /**
     * Log a message to the servlet context application log.
     *
     * @param message
     *            Message to be logged
     */
    private void log(String message) {
        if (context != null)
            context.log("EventListener: " + message);
        else
            System.out.println("EventListener: " + message);
    }
    /**
     * Log a message and associated exception to the servlet context application
     * log.
     *
     * @param message
     *            Message to be logged
     * @param throwable
     *            Exception to be logged
     */
    private void log(String message, Throwable throwable) {
        if (context != null)
            context.log("EventListener: " + message, throwable);
        else {
            System.out.println("EventListener: " + message);
            throwable.printStackTrace(System.out);
        }
    }
}

In our web.xml we have declared some roles as well as userMgmtJdbcRealm as the realm name to be used for our login configuration (see above). As you will see soon below userMgmtJdbcRealm is a jdbcRealm. Glassfish will use it for authentication and authorization. For authorization Glassfish will use our jdbcRealm to find out which Groups are assigned to the authenticated user. Now in the glassfish-web.xml the Groups retrieved from the database have to be mapped to Roles that our application knows. As a naming convention I have used the same name for both Groups and Roles. Please also compare this to our Group Enum which we have defined in Step 1 (see above). Before we go to Step 6 please have a look at our glassfish-web.xml and make sure not to get confused about Roles and Groups and how they work together:

glassfish-web.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
  <context-root>/AuthenticationDemo</context-root>
  <!-- role mapping -->
  <security-role-mapping>
    <role-name>USER</role-name>
    <group-name>USER</group-name>
  </security-role-mapping>
  
  <security-role-mapping>
    <role-name>DEFAULT</role-name>
    <group-name>DEFAULT</group-name>
  </security-role-mapping>
  
  <security-role-mapping>
    <role-name>ADMINISTRATOR</role-name>
    <group-name>ADMINISTRATOR</group-name>
  </security-role-mapping>
  <!-- default -->
  <class-loader delegate="true"/>
  <jsp-config>
    <property name="keepgenerated" value="true">
      <description>Keep a copy of the generated servlet class' java code.</description>
    </property>
  </jsp-config>
  
</glassfish-web-app>

 

9. Creating a jdbcRealm

Now we want to create a jdbcRealm which is used by Glassfish for authentication and authorization. You could do this by using the Admin Concole of Glassfish or by using an asadmin command. I prefere the asadmin command, please hee http://docs.oracle.com/cd/E18930_01/html/821-2433/create-auth-realm-1.html for more details:

asadmin command for creating a jdbcRealm:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#one liner for copy and paste
asadmin create-auth-realm --classname com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm --property jaas-context=jdbcRealm:datasource-jndi="jdbc/auth":user-table=users:user-name-column=email:password-column=password:group-table=users_groups:group-name-column=groupname:digest-algorithm=SHA-512 userMgmtJdbcRealm
#better to read (do not copy and paste this => use previous line for copy and paste!!):
asadmin create-auth-realm
  --classname com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
  --property jaas-context=jdbcRealm
             :datasource-jndi="jdbc/auth"
             :user-table=users
             :user-name-column=email
             :password-column=password
             :group-table=users_groups
             :group-name-column=groupname
             :digest-algorithm=SHA-512
  userMgmtJdbcRealm

The asadmin command will create a jdbcRealm. The new jdbcRealm is called userMgmtJdbcRealm and will use our jdbc/auth jdbc resource which we have created in Step 2 (see above). We have also to tell where our user table can be found and which columns represent the user name and user password. The user table is called USERS, the user name column is email and the password column is password. Besides that we also have o specify the name of our group table (in our case USERS_GROUPS) as well as the column that specifies the groupname (in our case groupname). We also have specified to use SHA-512 as digest algorithm. We don’t have to specify HEX for encoding because HEX is the default encoding in case digest-algorithm is specified. Please compare all that to our User Management Data Model which we have defined in Step 1 (see above).

For more details about this check spring web hosting website.


Viewing latest article 6
Browse Latest Browse All 10

Trending Articles