Saturday, July 18, 2009

Web App + Security + Struts2

When securing a web application there are few options for authentication and authorization of user/roles. You could write your custom logic, use container managed security, or use 3rd part libraries, or augment container managed security with custom logic.

Custom Security: Custom security is implemented by writing programming logic. For a pure Struts2 app (Where every url of the web application is served by Struts actions), typically user is authenticated using a custom HTML form backed by a custom LoginAction. LoginAction will validated username/password and then store the user information in session variable. This user/role information stored in session can be used control the logic for what URL/actions can be accessed by user. You can write a Struts2 interceptor and have every single request for struts2 resource go through this interceptor. When user tries to access a resource in the application, this interceptor will check if user is not logged by looking at certain data in user session. If data is not found user can redirected to login action. If user is logged in then you can check the role to make sure user has access to the resource, once the action is executed, you can use the user role information in view code (JSPs or other ) to hide/show certain section of view.

Disadvantage of above approach is is that only struts actions are protected. Another approach will be to write a Servlet filter instead of a struts Interceptor. Other disadvantage is you will end up with lot of logic for protecting different url for various roles. You may write code to read a custom configuration file.

Following is sample code

struts.xml
---------------

<package extends="struts-default">

<interceptors>
<interceptor name="login" class="mypkg.AuthenticationInterceptor"/>
<interceptor-stack name="intStack">
<interceptor-ref name="login"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>

<default-interceptor-ref name="intStack"/>

Login form
---------------
<s:form action="login">
<table> <tr> <td >Username:</td> </tr>
<tr><td><s:textfield name="username"/></td> </tr>
<tr><td>Password:</td> </tr>
<tr><td><s:password label="Password" name="password"/></td></tr>
<tr><td><s:submit value="Login" /></td></tr>
</table>
</s:form>


LoginAction
----------------

public class LoginAction {

private User user;
private String username;
private String password;

public String execute() throws Exception {
Map session = ActionContext.getContext().getSession();

if (username == null) {
return "login";
}
user = yourDAO.getUser(username, password);
if (user != null) {
session.put("user", user);
return SUCCESS;
}
else {
return "login";
}
}
public void setUsername(String username){
this.username = username;
}
public String getUsername() {
return username;
}
public void setPassword(String password){
this.password = password;
}
public String getPassword() {
return password;
}
}



Interceptor code
----------------------

public class AuthenticationInterceptor implements Interceptor {

public String intercept(ActionInvocation invocation) throws Exception {

Map session = ActionContext.getContext().getSession();

//Check by looking at user's http session for certain data which you
// If not logged in redirect user to login.action
if (!session.containsKey("user") && !ActionContext.getContext().getName().equals("login")) {
return "login";
}

// If user is logged in then vcheck if user has access to requested url

String result = invocation.invoke();
return result;

}

}


Container managed security + Custom security: Servlet specification provides declarative security, access to web resources can be defined in web.xml. Once user is authenticated application servlet API could be used to access authenticated user / role information. Struts2 action can also be restricted to certain roles declarative style. One drawback is Roles information needs to be maintained in web.xml, If your application creates roles dynamically, new roles have to be also specified in web.xml (Needs rebuilding war file/application restart).


<security-constraint>
<web-resource-collection>
<web-resource-name>SecuredPages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>

<security-role>
<description>Role required to see admin pages.</description>
<role-name>admin</role-name>
</security-role>
<security-role>
<description>Role with read only.</description>
<role-name>reader</role-name>
</security-role>

For autheticating form authentication can be used with a jsp specified in web.xml, the jsp includes a form action j_security_check. username/password are validated against the realm specified in web.xml. Realm represents the source of username/password and associated roles. There are a wide variety of Realms (JDBC, JNDI, xml file, Datasource) which are specific to servlet containers. For a web application deployed in Tomcat Realm is specified in context.xml under META-INF/ direcotory

<login-config>
<auth-method>FORM</auth-method>
<realm-name>JDBCRealm</realm-name>
<form-login-config>
<form-login-page>/login2.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
Login form
----------

<s:form action="j_security_check">
<table> <tr> <td >Username:</td> </tr>
<tr><td><s:textfield name="j_username"/></td> </tr>
<tr><td>Password:</td> </tr>
<tr><td><s:password label="Password" name="j_password"/></td></tr>
<tr><td><s:submit value="Login" /></td></tr>
</table>
</s:form>

META-INF/context.xml, login action will be validated against following Realm
-------------------------------------------------------------------------
<Context>
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://mydbserver/mydb_test2"
connectionName="myuser" connectionPassword="mypassword"
userTable="users" userNameCol="email"
userCredCol="password" digest="MD5"
userRoleTable="user_roles" roleNameCol="name"/>
</Context>


Above setup will provide for authentication and authorization for URL in web applications.

If you application requires further restrictions on struts Actions based on user roles, roles interceptor could be used and security could be declared in struts.xml.

Any further customization could be programmed by writing additional struts interceptors or servlet filters. Following is an example of custom interceptor where a different struts result/view is presented to user based on role.

public class ReaderViewInterceptor implements Interceptor{

public void destroy() {
}

public void init() {
}

public String intercept(ActionInvocation invocation) throws Exception {

invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
Map resultsMap = invocation.getProxy().getConfig().getResults();
if( ServletActionContext.getRequest().isUserInRole("Reader") ) {
invocation.setResultCode("readonly_" + resultCode);
}
}
});

String result = invocation.invoke();
return result;
}

}

Web App + Security + Struts2

When securing a web application there are few options for authentication and authorization of user/roles. You could write your custom logic, use container managed security, or use 3rd part libraries, or augment container managed security with custom logic.

Custom Security: Custom security is implemented by writing programming logic. For a pure Struts2 app (Where every url of the web application is served by Struts actions), typically user is authenticated using a custom HTML form backed by a custom LoginAction. LoginAction will validated username/password and then store the user information in session variable. This user/role information stored in session can be used control the logic for what URL/actions can be accessed by user. You can write a Struts2 interceptor and have every single request for struts2 resource go through this interceptor. When user tries to access a resource in the application, this interceptor will check if user is not logged by looking at certain data in user session. If data is not found user can redirected to login action. If user is logged in then you can check the role to make sure user has access to the resource, once the action is executed, you can use the user role information in view code (JSPs or other ) to hide/show certain section of view.

Disadvantage of above approach is is that only struts actions are protected. Another approach will be to write a Servlet filter instead of a struts Interceptor. Other disadvantage is you will end up with lot of logic for protecting different url for various roles. You may write code to read a custom configuration file.

Following is sample code

struts.xml
---------------

<package extends="struts-default">

<interceptors>
<interceptor name="login" class="mypkg.AuthenticationInterceptor"/>
<interceptor-stack name="intStack">
<interceptor-ref name="login"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>

<default-interceptor-ref name="intStack"/>

Login form
---------------
<s:form action="login">
<table> <tr> <td >Username:</td> </tr>
<tr><td><s:textfield name="username"/></td> </tr>
<tr><td>Password:</td> </tr>
<tr><td><s:password label="Password" name="password"/></td></tr>
<tr><td><s:submit value="Login" /></td></tr>
</table>
</s:form>


LoginAction
----------------

public class LoginAction {

private User user;
private String username;
private String password;

public String execute() throws Exception {
Map session = ActionContext.getContext().getSession();

if (username == null) {
return "login";
}
user = yourDAO.getUser(username, password);
if (user != null) {
session.put("user", user);
return SUCCESS;
}
else {
return "login";
}
}
public void setUsername(String username){
this.username = username;
}
public String getUsername() {
return username;
}
public void setPassword(String password){
this.password = password;
}
public String getPassword() {
return password;
}
}



Interceptor code
----------------------

public class AuthenticationInterceptor implements Interceptor {

public String intercept(ActionInvocation invocation) throws Exception {

Map session = ActionContext.getContext().getSession();

//Check by looking at user's http session for certain data which you
// If not logged in redirect user to login.action
if (!session.containsKey("user") && !ActionContext.getContext().getName().equals("login")) {
return "login";
}

// If user is logged in then vcheck if user has access to requested url

String result = invocation.invoke();
return result;

}

}


Container managed security + Custom security: Servlet specification provides declarative security, access to web resources can be defined in web.xml. Once user is authenticated application servlet API could be used to access authenticated user / role information. Struts2 action can also be restricted to certain roles declarative style. One drawback is Roles information needs to be maintained in web.xml, If your application creates roles dynamically, new roles have to be also specified in web.xml (Needs rebuilding war file/application restart).


<security-constraint>
<web-resource-collection>
<web-resource-name>SecuredPages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>

<security-role>
<description>Role required to see admin pages.</description>
<role-name>admin</role-name>
</security-role>
<security-role>
<description>Role with read only.</description>
<role-name>reader</role-name>
</security-role>

For autheticating form authentication can be used with a jsp specified in web.xml, the jsp includes a form action j_security_check. username/password are validated against the realm specified in web.xml. Realm represents the source of username/password and associated roles. There are a wide variety of Realms (JDBC, JNDI, xml file, Datasource) which are specific to servlet containers. For a web application deployed in Tomcat Realm is specified in context.xml under META-INF/ direcotory

<login-config>
<auth-method>FORM</auth-method>
<realm-name>JDBCRealm</realm-name>
<form-login-config>
<form-login-page>/login2.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
Login form
----------

<s:form action="j_security_check">
<table> <tr> <td >Username:</td> </tr>
<tr><td><s:textfield name="j_username"/></td> </tr>
<tr><td>Password:</td> </tr>
<tr><td><s:password label="Password" name="j_password"/></td></tr>
<tr><td><s:submit value="Login" /></td></tr>
</table>
</s:form>

META-INF/context.xml, login action will be validated against following Realm
-------------------------------------------------------------------------
<Context>
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://mydbserver/mydb_test2"
connectionName="myuser" connectionPassword="mypassword"
userTable="users" userNameCol="email"
userCredCol="password" digest="MD5"
userRoleTable="user_roles" roleNameCol="name"/>
</Context>


Above setup will provide for authentication and authorization for URL in web applications.

If you application requires further restrictions on struts Actions based on user roles, roles interceptor could be used and security could be declared in struts.xml.

Any further customization could be programmed by writing additional struts interceptors or servlet filters. Following is an example of custom interceptor where a different struts result/view is presented to user based on role.

public class ReaderViewInterceptor implements Interceptor{

public void destroy() {
}

public void init() {
}

public String intercept(ActionInvocation invocation) throws Exception {

invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
Map resultsMap = invocation.getProxy().getConfig().getResults();
if( ServletActionContext.getRequest().isUserInRole("Reader") ) {
invocation.setResultCode("readonly_" + resultCode);
}
}
});

String result = invocation.invoke();
return result;
}

}