Thursday, 23 June 2011

Spring Security3.0.x Authorisation Only using J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource


We have a requirement in which we use weblogic container for authentication and we are planning to use spring security for authorisation only.

We are using weblogic11g app server and spring security 3.5 for achieving the same.

Below code can help to map j2ee user roles to Spring GrantedAuthorities.


I even posted my question in below website to clear off my doubts.

http://forum.springsource.org/showthread.php?110397-How-to-achieve-Spring-Security3.5-for-authorisation-only-and-not-for-authentication

-------------------------------------------------------------------------------------------------------------------------------------------------
Main things required in your web.xml
-------------------------------------------------------------------------------------------------------------------------------------------------

<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>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-security.xml
/WEB-INF/servlets.xml
/WEB-INF/views.xml

</param-value>
</context-param>

<servlet>
<servlet-name>springSecurityPreAuth</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/servlets.xml
/WEB-INF/views.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springSecurityPreAuth</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>


<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<security-constraint>
<web-resource-collection>
<web-resource-name>Secure TestWeb Access</web-resource-name>
<url-pattern>*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>testWebApp</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
<security-role>
<role-name>testWebApp</role-name>
</security-role>

-------------------------------------------------------------------------------------------------------------------------------------------------
weblogic.xml
-------------------------------------------------------------------------------------------------------------------------------------------------

<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.2/weblogic-web-app.xsd">
<wls:weblogic-version>10.3.4</wls:weblogic-version>
<wls:context-root>commonloginweblogic</wls:context-root>

<wls:security-role-assignment>
<wls:role-name>testWebApp</wls:role-name>
<wls:principal-name>testWebApp</wls:principal-name>
</wls:security-role-assignment>

</wls:weblogic-web-app>

-------------------------------------------------------------------------------------------------------------------------------------------------

Your applicationContext-security.xml code goes like this
-------------------------------------------------------------------------------------------------------------------------------------------------

<?xml version="1.0" encoding="UTF-8"?>

<!--
- Sample namespace-based configuration
-
-->

<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

<global-method-security pre-post-annotations="enabled">
</global-method-security>

<http auto-config="true" use-expressions="true">
</http>


<b:bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map path-type="ant">
<filter-chain pattern="/**" filters="j2eePreAuthFilter"/>
</filter-chain-map>
</b:bean>

<authentication-manager alias="authenticationManager">
<authentication-provider ref='preAuthenticatedAuthenticationProvider'/>
</authentication-manager>

<b:bean id="preAuthenticatedAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<b:property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
</b:bean>

<b:bean id="preAuthenticatedUserDetailsService"
class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>


<b:bean id="j2eePreAuthFilter" class="security.SsoUserJ2eePreAuthenticatedProcessingFilter">
<b:property name="authenticationManager" ref="authenticationManager"/>
<b:property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
<b:property name="continueFilterChainOnUnsuccessfulAuthentication" value="false"/>
</b:bean>

<b:bean id="authenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
<b:property name="mappableRolesRetriever" ref="j2eeMappableRolesRetriever"/>
<b:property name="userRoles2GrantedAuthoritiesMapper" ref="j2eeUserRoles2GrantedAuthoritiesMapper"/>
</b:bean>

<b:bean id="j2eeMappableRolesRetriever" class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever">
</b:bean>

<b:bean id="j2eeUserRoles2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
<b:property name="attributePrefix" value="test"/>
</b:bean>


</b:beans>


-------------------------------------------------------------------------------------------------------------------------------------------------
Our class extending J2eePreAuthenticatedProcessingFilter code goes like this
-------------------------------------------------------------------------------------------------------------------------------------------------

package security;
import java.security.Principal;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;

import domain.User;

import view.SessionState;


/**
* This filter implementation hooks into the Spring Security authentication
* mechanism and ensures that the additional SSO secrity details are loaded and
* made available in the session.
*/
public class SsoUserJ2eePreAuthenticatedProcessingFilter extends J2eePreAuthenticatedProcessingFilter {

/**
* Log.
*/
private static final Logger LOG = Logger.getLogger(SsoUserJ2eePreAuthenticatedProcessingFilter.class);

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
LOG.debug("successfulAuthentication()");

// There may be unauthenticated threads all requesting for the same session
// at the same time so synchronisation must be achieved.
//
// To minimise contention, this implementation synchronises on the session
// object itself.
//
// Since this is only a session startup issue, there is no further
// contention, bottle-neck or performance impact.

HttpServletRequest req = (HttpServletRequest) request; //principal is set ed()) {LOG.debug("user=" + user);}

if(user != null) {
LOG.debug("Got user data");

//final Authentication authenticatcation()" + authentication);

HttpSession session = request.getSession();
synchronized(session) {
SessionState sessionState = (SessionState)session.getAttribute(SessionState.NAME);

if(sessionState == null) {
LOG.debug("Create new application session state for the newly authenticated user");

// Create new session state for this user
sessionState = new SessionState();

request.getSession().setAttribute(SessionState.NAME, sessionState);

// Load the additional user data for this user
Principal principal = request.getUserPrincipal();
String username = principal.getName();
if(LOG.isDebugEnabled()) {LOG.debug("username=" + username);}

User user=new User();
user.setUsername(username);
if(LOG.isDebugEnabled()) {LOG.debug("user=" + user);}

if(user != null) {
LOG.debug("Got user data");

//final Authentication authentication2 = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
for (GrantedAuthority authority : authentication.getAuthorities()) {
LOG.debug("roles retrieved From Spring=" + authority.getAuthority());
}
}
//
sessionState.setUser(user);
}
else {
// TODO throw exception?
LOG.warn("User authenticated but no user data available for '" + username + "'");
}
}
}

super.successfulAuthentication(request, response, authResult);
}
}

-------------------------------------------------------------------------------------------------------------------------------------------------

In our jsp's finally we can authorise the user based on role


<sec:authorize access="hasRole('testWebApp')">
You are a Junior Developer
</sec:authorize>
-------------------------------------------------------------------------------------------------------------------------------------------------

We can also access the role information in our java code.

SecurityContextHolderAwareRequestWrapper requestWrapper=new SecurityContextHolderAwareRequestWrapper(request,"test");
boolean testRole=requestWrapper.isUserInRole("Webapp");
LOGGER.debug("HomeController.handleRequest testRole()." + testRole);

-------------------------------------------------------------------------------------------------------------------------------------------------

If you want to restrict the user acccess to various links in the web application then we need to add the fsi filter as well

<b:bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map path-type="ant">
<filter-chain pattern="/**" filters="j2eePreAuthFilter,fsi"/>
</filter-chain-map>
</b:bean>

<b:bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<b:property name="allowIfAllAbstainDecisions" value="false"/>
<b:property name="decisionVoters">
<b:list>
<b:ref bean="roleVoter"/>
<b:ref bean="webExpressionVoter"/>
</b:list>
</b:property>
</b:bean>

<b:bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<b:bean id="webExpressionVoter" class="org.springframework.security.web.access.expression.WebExpressionVoter"/>

<b:bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<b:property name="authenticationManager" ref="authenticationManager"/>
<b:property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<b:property name="securityMetadataSource">
<filter-invocation-definition-source use-expressions="true">
<intercept-url pattern="/secure/extreme/**" access="hasRole('testRoleExtreme')"/>
<intercept-url pattern="/secure/**" access="hasRole('testRoleSecure')"/>
<intercept-url pattern="/**" access="hasRole('testRole')"/>
</filter-invocation-definition-source>
</b:property>
</b:bean>
-------------------------------------------------------------------------------------------------------------------------------------------------
Now if you want to implement a proper error page whenever any security related interceptors like FilterSecurityInterceptor throws exceptions then we need to add below tags in our security.xml

If you don't add then you may face below stack trace

Error 500--Internal Server Error
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:71)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:203)

Below code should work and will resolve the issue

<b:bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map path-type="ant">
<filter-chain pattern="/**" filters="exceptionTranslationFilter,j2eePreAuthFilter,fsi"/>
</filter-chain-map>
</b:bean>

<b:bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<b:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<b:property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</b:bean>

<b:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<b:property name="loginFormUrl" value="/login.jsp"/>
</b:bean>

<b:bean id="accessDeniedHandler"
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<b:property name="errorPage" value="/accessDenied/showAccessDenied.html"/>
</b:bean>

Remember in our jsp page add below code compulsory if you are relying on I.E and also want to redirect to user-defined error page rather than the typical I.E forbidden page.

<% response.setStatus(200); %>

Refer to this article for more details
http://www.coderanch.com/t/365499/Servlets/java/web-xml-error-page-not

For more information please refer to below url

http://static.springsource.org/spring-security/site/docs/3.0.x/reference/core-web-filters.html

-------------------------------------------------------------------------------------------------------------------------------------------------

Hope this article helps.

3 comments:

cepcion said...

Hello, I'm a developer in about the same situation as you (WebLogic + Spring security, preauthentication). I'm very interested in knowing how you were able to get this configuration working.

My email is paul.concepcion@safeway.com . I'd love to ask you a few questions sometime.

Thanks and I look forward to hearing from you.

Ecommerce developer said...

Great posting.In your blog,help me to my professional .It give nice idea create a program.I got more knowledge.Thanks for creating wonderful site.Quality content.

Anonymous said...

Great posting!!! This is a hard topic to find. Thank you for sharing.