Recipe11.10.Mixing Application-Managed and Container-Managed Security


Recipe 11.10. Mixing Application-Managed and Container-Managed Security

Problem

You want the convenience of container-managed security, yet need a custom mechanism for implementing your security policies.

Solution

Use the SecurityFilter (http://securityfilter.sourceforge.net) custom servlet filter and associated classes.

Discussion

Container-managed security, as shown in Recipe 11.9, has some advantages:

  • When users attempt to access a protected URL, the container automatically prompts them to logon. Once authenticated, they are forwarded to the originally requested URL.

  • The user identity can be determined using the getUserPrincipal( ) or getremoteUser( ) methods of the HttpServletRequest. These methods can determine if a user is logged in.

  • You can determine if a user has a specific role using the isUserInRole(roleName) method of the HttpServletRequest. Struts leverages this feature to provide role-constrained actions via the roles attribute. Struts provides for role-specific page generation using the logic:present role="roleNames" custom JSP tag.

Container-managed security has drawbacks, such as portability. With container-managed security, the implementation is split between your web application and the application server. You usually must configure container-specific resources to specify the repository, known as a security realm, from which the container acquires the user's credentials and roles. Container-managed security will only prompt users to login if they attempt access of a protected URL. Users cannot log in by going to a known page and entering their username and password. This restriction makes it difficult, for example, to include a login form on every page.

The SecurityFilter servlet filter and related classes provide a hybrid of container-managed security and application-managed security that solves most of these problems. SecurityFilter permits implementation of a custom security policy yet allows programmatic access to user identity and role information via the standard HttpServletRequest methods. You configure the SecurityFilter through an XML file. The format of this file is near identical to the security-constraint elements used for container-managed security in the web.xml. Example 11-19 shows a sample securityfilter-config.xml file. This example is similar to the web.xml for container-managed security shown in Recipe 11.9.

Example 11-19. Configuration for SecurityFilter
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE securityfilter-config PUBLIC     "-//SecurityFilter.org//DTD Security Filter Configuration 2.0//EN"     "http://www.securityfilter.org/dtd/securityfilter-config_2_0.dtd"> <securityfilter-config>    <security-constraint>         <web-resource-collection>           <web-resource-name>RegPages</web-resource-name>           <description>Registered user pages</description>           <url-pattern>/reg/*</url-pattern>         </web-resource-collection>         <auth-constraint>             <role-name>jscUser</role-name>         </auth-constraint>    </security-constraint>    <security-constraint>       <web-resource-collection>          <web-resource-name>AdminPages</web-resource-name>          <url-pattern>/admin/*</url-pattern>       </web-resource-collection>       <auth-constraint>          <role-name>jscAdmin</role-name>       </auth-constraint>    </security-constraint>     <!-- Use this login-config to test BASIC authentication -->     <!--     <login-config>         <auth-method>BASIC</auth-method>         <realm-name>StrutsCookbookCh11</realm-name>     </login-config>     -->     <login-config>         <auth-method>FORM</auth-method>         <realm-name>StrutsCookbookCh11</realm-name>         <form-login-config>             <form-login-page>/sf_logon.jsp</form-login-page>             <form-error-page>/sf_logon_error.jsp</form-error-page>             <form-default-page>/Welcome.do</form-default-page>         </form-login-config>     </login-config>     <security-role>         <description>Regular Users</description>         <role-name>jscUser</role-name>     </security-role>     <security-role>         <description>Administrators</description>         <role-name>jscAdmin</role-name>     </security-role>     <realm className="com.oreilly.strutsckbk.ch11.sf.MemorySecurityRealm"/> </securityfilter-config>

Like container-managed security, with SecurityFilter you can specify allowed roles for URLs using the security-constraint element. SecurityFilter supports BASIC and FORM-based authentication. Unlike container-managed security, SecurityFilter allows for a user to perform an "unsolicited" login. That is, the user can log in without having to attempt access to a protected URL. In this scenario, once logged in, the user will be forwarded to the page specified in the form-default-page element. The logon page that you specify for the form-login-page element follows the same convention as container-managed security. The logon form, sf_logon.jsp, shown in Example 11-20, submits the j_username and j_password fields to j_security_check.

Example 11-20. SecurityFilter logon page
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <html> <head> <title>Security Filter : Logon Page</title> </head> <body> <form method="POST" action="j_security_check">     <table border="0" width="100%">         <tr>             <th align="right">                 <bean:message key="prompt.username"/>:             </th>             <td align="left">                 <input type="text" name="j_username" size="16"                  maxlength="18">             </td>         </tr>         <tr>             <th align="right">                 <bean:message key="prompt.password"/>:             </th>             <td align="left">                 <input type="password" name="j_password" size="16"                  maxlength="18">             </td>         </tr>         <tr>             <td align="right">                 <input type="submit" value="Submit">             </td>             <td align="left">                 <input type="reset">             </td>         </tr>     </table> </form> </body> </html>

With container-managed security, you have to separate the security realm configuration and code from the rest of the web application. The configuration is usually part of a container-specific XML file, and the code usually needs to be placed in a separate JAR file or in the server's classpath. With SecurityFilter, however, you include the configuration and code for your realm with your web application. It all gets bundled together in the same WAR file. Your custom realm must implement the SecurityRealmInterface interface (shown in Example 11-21). SecurityFilter is licensed under the SecurityFilter Software License, derived from and compatible with the Apache Software License. (For brevity, the license has been excluded in this example.)

Example 11-21. SecurityFilter realm interface
package org.securityfilter.realm; import java.security.Principal; public interface SecurityRealmInterface {    /**     * Authenticate a user.     *     * @param username a username     * @param password a plain text password, as entered by the user     *     * @return a Principal object representing the user if successful,      * false otherwise     */    public Principal authenticate(String username, String password);    /**     * Test for role membership.     *     * Use Principal.getName( ) to get the username from the principal object.     *     * @param principal Principal object representing a user     * @param rolename name of a role to test for membership     *     * @return true if the user is in the role, false otherwise     */    public boolean isUserInRole(Principal principal, String rolename); }

If you want to work with only usernames and passwords and don't need to create Principals, you can extend the SimpleSecurityRealmBase class. The custom MemorySecurityRealm, shown in Example 11-22, extends this class and implements the booleanAuthenticate( ) and isUserInRole() methods by delegating to a custom security service.

Example 11-22. Extending the SimpleSecurityRealmBase
package com.oreilly.strutsckbk.ch11.sf; import org.securityfilter.realm.SimpleSecurityRealmBase; public class MemorySecurityRealm extends SimpleSecurityRealmBase {    private SecurityService serviceImpl = new SecurityServiceImpl( );    public boolean booleanAuthenticate(String username, String password) {        try {            User user = serviceImpl.authenticate(username, password);            if (user != null) return true;        } catch (SecurityException e) {            e.printStackTrace( );        }        return false;    }    public boolean isUserInRole(String username, String role) {        User user = serviceImpl.findUser(username);        return user == null ? false : user.hasRole(role);    } }

The realm is configured and deployed in the securityfilter-config.xml file:

<realm className="com.oreilly.strutsckbk.ch11.sf.MemorySecurityRealm"/>

Optionally, you can declaratively set properties on a custom realm using the realm-param element:

<realm className="fully.qualified.classname.of.SecurityRealm">     <realm-param name="propertyName" value="propertyValue" /> </realm>

You describe the deployment of the actual SecurityFilter servlet filter in the web.xml file as shown in Example 11-23.

Example 11-23. Declaring SecurityFilter in the deployment descriptor
<?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>     <display-name>Struts Cookbook - Chapter 11 : SecurityFilter</display-name>     <!-- Security Filter -->     <filter>         <filter-name>Security Filter</filter-name>         <filter-class>org.securityfilter.filter.SecurityFilter</filter-class>         <init-param>             <param-name>config</param-name>             <param-value>/WEB-INF/securityfilter-config.xml</param-value>             <description>               Configuration file location (this is the default value)             </description>        </init-param>        <init-param>             <param-name>validate</param-name>             <param-value>true</param-value>             <description>Validate config file if set to true</description>        </init-param>     </filter>     <!-- map all requests to the SecurityFilter -->     <filter-mapping>         <filter-name>Security Filter</filter-name>         <url-pattern>/*</url-pattern>     </filter-mapping>          <!-- Action Servlet Configuration -->     <servlet>         <servlet-name>action</servlet-name>         <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>         <init-param>             <param-name>config</param-name>             <param-value>/WEB-INF/struts-config.xml</param-value>         </init-param>         <load-on-startup>1</load-on-startup>     </servlet>     <!-- Action Servlet Mapping -->     <servlet-mapping>         <servlet-name>action</servlet-name>         <url-pattern>*.do</url-pattern>     </servlet-mapping>     <!-- The Welcome File List -->     <welcome-file-list>         <welcome-file>index.jsp</welcome-file>     </welcome-file-list> </web-app>

It's best to map all requests to the filter. Let the securityfilter-config.xml file set the security constraints for specific URLs.

SecurityFilter provides support for automatic logins using cookies. This gives you a similar capability to the custom solution shown in Recipe 11.7. Furthermore, you can configure all the details about the cookies, such as expiration and encryption, in the securityfilter-config.xml file, as shown in Example 11-24.

Example 11-24. Configuring the "remember me" cookie
<form-login-config>     <!--Logon page must contain a checkbox for j_rememberme -->     <form-login-page>/sf_logon.jsp</form-login-page>     <form-error-page>/sf_logon_error.jsp</form-error-page>     <form-default-page>/Welcome.do</form-default-page>     <!-- remember-me config -->     <remember-me className="org.securityfilter.authenticator.persistent.            DefaultPersistentLoginManager">         <!-- optional settings for default persistent login manager -->         <remember-me-param name="cookieLife" value="15"/>         <remember-me-param name="protection" value="all"/>         <remember-me-param name="useIP" value="true"/>         <remember-me-param name="encryptionAlgorithm" value="DES"/>         <remember-me-param name="encryptionMode" value="ECB"/>         <remember-me-param name="encryptionPadding" value="PKCS5Padding"/>         <!-- encryption keys; customize for each application -->         <!-- NOTE: these kys must be speciied AFTER other          encryption settings -->         <remember-me-param name="validationKey"  value="347382902489402489754895734890347"/>         <remember-me-param name="encryptionKey"  value="347892347028490237487846240673842"/>     </remember-me> </form-login-config>

You enable the "remember me" capability by adding a checkbox to your logon form with the name of j_rememberme. Here's what you would add to the logon page shown in Example 11-20:

<tr>     <th align="right">Remember me:</th>     <td align="left">         <input type="checkbox" name="j_rememberme" value="true">     </td> </tr>

The behavior you get with the SecurityFilter will look and feel like container-managed security. If users attempt to access a protected page, they will be prompted to log in. Of course, if they use the "remember me" feature, they can automatically log in. They can log in without having to attempt access to a protected page. Once authenticated, control is forwarded to the form-default-page.

Using SecurityFilter, you can use the getUserPrincipal( ), getremoteUser( ), and isUserInRole( ) methods of the HttpServletRequest as if you were using full-blown container-managed security. Struts support for rolesthe roles attribute of the action element and the roles attribute of the logic:present tagwill work as intended.

See Also

The SecurityFilter project's home page can be found at http://securityfilter.sourceforge.net. SecurityFilter provides a framework for authentication and authorization using a servlet filter. To understand how filters work for these purposes, take a look at Recipes Section 11.6 and Section 11.8.

Recipe 11.9 discusses the use of container-managed security.



    Jakarta Struts Cookbook
    Jakarta Struts Cookbook
    ISBN: 059600771X
    EAN: 2147483647
    Year: 2005
    Pages: 200

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net