Announcement Announcement Module
Collapse
No announcement yet.
spring-security-javaconfig preauth with Tomcat JAAS Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • spring-security-javaconfig preauth with Tomcat JAAS

    I am trying to do the following:

    If a user is pre-authenticated then they should have access based on their roles
    If a user is not pre-authenticated then the should authenticate through a login form and use spring-security.

    The issue I'm running into is that as soon as I enable the login-config related area of web.xml I can no longer log in to the application. I get authenticated, but I end up getting directed back to login.jsp instead of the the root of the application. I am no longer authenticated at this point and can't manually navigate to a protected page.

    It's almost as though I'm getting authenticated via web.xml and then spring-security tries to authenticate again?

    Everything works fine so long as I comment out the relavent section of web.xml...

    What is the best way to accomplish this? I'd like applications within the same context to have single sign on capabilities, but I'd like to fall back on spring-security if the user is not pre-authenticated.

    Code:
    <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
        xmlns:spring="http://www.springframework.org/tags"
        xmlns:c="http://java.sun.com/jsp/jstl/core"
        xmlns:form="http://www.springframework.org/tags/form" version="2.0">
        <jsp:directive.page language="java" contentType="text/html" />
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
    <head>
    <title>Please Login</title>
    </head>
    <body>
        <c:url value="/login/authme" var="loginUrl"/>
        
        <form name="f" action="${loginUrl}" method="post">
            <fieldset>
                <legend>Please Login</legend>
                <c:if test="${param.error != null}">
                    <div class="alert alert-error">
                        Failed to login.
                    </div>
                </c:if>
                <c:if test="${param.logout != null}">
                    <div class="alert alert-success">
                        You have been logged out.
                    </div>
                </c:if>
                <label for="username">Username</label>
                <input type="text" id="username" name="j_username" value="${username}"/>
                <label for="password">Password</label>
                <input type="password" id="password" name="j_password"/>
                <div class="form-actions">
                    <button type="submit" class="btn">Log in</button>
                </div>
            </fieldset>
        </form>
    </body>
    </html>
    </jsp:root>
    Code:
    	<login-config>
            <auth-method>FORM</auth-method>
            <form-login-config>
                <form-login-page>/login</form-login-page>
                <form-error-page>/login?error</form-error-page>
            </form-login-config>
        </login-config>
    
        <security-role>
            <role-name>OFFICE</role-name>
        </security-role>
        
        <security-role>
            <role-name>DEVELOPMENT</role-name>
        </security-role>
        
        <security-constraint>    
            <web-resource-collection>
            	<web-resource-name>Public</web-resource-name>
                <description>Matches unconstrained pages</description>
                <url-pattern>/login</url-pattern>
                <url-pattern>/login/authme</url-pattern>
                <url-pattern>/logout</url-pattern>
                <url-pattern>/resources/*</url-pattern>
                <url-pattern>/robots.txt</url-pattern>
            </web-resource-collection>
        </security-constraint>
        
        <security-constraint>
            <web-resource-collection>
                <web-resource-name>Secured User Areas</web-resource-name>
                <url-pattern>/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>OFFICE</role-name>
            </auth-constraint>
        </security-constraint>
        
        <security-constraint>
            <web-resource-collection>
                <web-resource-name>Secured Admin Areas</web-resource-name>
                <url-pattern>/admin/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>DEVELOPMENT</role-name>
            </auth-constraint>
        </security-constraint>
    Code:
    package com.renovo.endpointmanager.config.root;
    
    import javax.inject.Inject;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    import org.springframework.ldap.core.support.AbstractContextSource;
    import org.springframework.ldap.core.support.BaseLdapPathContextSource;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
    import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
    import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
    import org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever;
    
    /**
     * Security related configs
     * 
     * TODO JAASAuthenticationProvider
     * 
     *
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled=true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {	
    	
    	@Inject
    	Environment environment;
    	
    	@Bean
    	@Override
    	public AuthenticationManager authenticationManagerBean() throws Exception {
    		
    		return super.authenticationManagerBean();
    	}
    	
    	@Bean
    	public Http403ForbiddenEntryPoint http403ForbiddenEntryPoint() {
    		
    		Http403ForbiddenEntryPoint http403ForbiddenEntryPoint = new Http403ForbiddenEntryPoint();
    		return http403ForbiddenEntryPoint;
    	}
    	
    	@Bean
    	public LoginUrlAuthenticationEntryPoint loginAuthEntryPoint() {
    		
    		LoginUrlAuthenticationEntryPoint loginAuthEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
    	    return loginAuthEntryPoint;
    	}	
    	
    	@Override
    	public void configure(WebSecurity webSecurity) throws Exception {
    
    		webSecurity.ignoring()
    			.antMatchers("/resources/**")
    			.and()
    			.debug(true); // TODO not for production 
    	}	
    	
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {		
    		
    		http.exceptionHandling()
    				.authenticationEntryPoint(http403ForbiddenEntryPoint())
    			.and()
    			.authorizeUrls()
    				.antMatchers("/login").permitAll()
    				.antMatchers("/robots.txt", "/resources/**").permitAll()
    				.antMatchers("/admin/**").hasRole(getAdminRole())
    				.antMatchers("/**").hasRole(getUserRole())				
    			.and()
    			.formLogin()
    				.loginPage("/login")
    				.loginProcessingUrl("/login/authme")
    				.defaultSuccessUrl("/")
    				.usernameParameter("j_username")
    				.passwordParameter("j_password")
    				.permitAll()
    			.and()
    			.logout()
    				.deleteCookies("JSESSIONID")
    				.logoutUrl("/logout")
    				.logoutSuccessUrl("/login?logout")
    				.permitAll()
    			.and()
    			.jee()
    				.mappableRoles(getAdminRole(), getUserRole());
    	}	
    
    	@Bean
    	public BaseLdapPathContextSource ldapContextSource() {
    		
    		BaseLdapPathContextSource cs = new DefaultSpringSecurityContextSource(getProviderUrl());
    		((AbstractContextSource) cs).setUserDn(getAdminDn());
    		((AbstractContextSource) cs).setPassword(getAdminPassword());
    		
    		return cs;
    	}	
    		
    	@Override
    	protected void registerAuthentication(AuthenticationManagerBuilder auth)
    			throws Exception {		
    			
    		auth.inMemoryAuthentication()
    			.withUser("user").password("pass").roles(getUserRole())
    			.and()
    			.withUser("admin").password("pass").roles(getUserRole(), getAdminRole());
    		
    		auth.ldapAuthentication()
    			.contextSource(ldapContextSource())
    			.userSearchBase(getUserSearchBase())
    			.userSearchFilter(getUserSearchFilter())
    			.groupSearchBase(getGroupSearchBase())
    			.groupSearchFilter(getGroupSearchFilter());			
    	}
    	
    	// properties
    	
    	private String getProviderUrl() {
    		return environment.getProperty("security.ldap.providerUrl");
    	}
    	
    	private String getAdminDn() {
    		return environment.getProperty("security.ldap.adminDn");
    	}
    	
    	private String getAdminPassword() {
    		return environment.getProperty("security.ldap.adminPassword");	
    	}
    	
    	private String getUserRole() {
    		return getUserRole(false);
    	}
    	
    	private String getUserRole(boolean rolePrefix) {
    		String prefix = rolePrefix ? "ROLE_" : "";
    		return prefix + environment.getProperty("security.ldap.userRole");
    	}
    	
    	private String getAdminRole() {
    		return getAdminRole(false);
    	}	
    	
    	private String getAdminRole(boolean rolePrefix) {
    		String prefix = rolePrefix ? "ROLE_" : "";
    		return prefix + environment.getProperty("security.ldap.adminRole");
    	}
    	
    	private String getUserSearchBase() {
    		return environment.getProperty("security.ldap.userSearchBase");
    	}
    	
    	private String getUserSearchFilter() {
    		return environment.getProperty("security.ldap.userSearchFilter");
    	}	
    	
    	private String getGroupSearchBase() {
    		return environment.getProperty("security.ldap.groupSearchBase");
    	}
    	
    	private String getGroupSearchFilter() {
    		return environment.getProperty("security.ldap.groupSearchFilter");
    	}	
    
    }
    Last edited by lunias; Aug 5th, 2013, 08:32 AM.

  • #2
    I could be mistaken, but it seems like your requirements are causing a chicken and the egg problem. When you add the <security-constraints> section to your web.xml does the following:

    * When a user requests a page, they are not authenticated so the container intercepts the request and sends the user to the login page
    * The login page is then rendered
    * You submit the login page to /login/authme
    * /login/authme is a secured page for the container which means that the container intercepts the request and sends the user to the login page again. Note that Spring Security will never see the request because the container intercepts it before it gets to Spring Security.

    To correct this, you would need to make the login page submit to /j_security_check as out lined in the specification. Again, Spring Security cannot modify the container managed security as it does not have access to the request.

    I'm not certain you will be able to achieve what you want because typically SSO happens with an anonymous user making a request to a restricted part of the application. The application would then redirect to the identity provider to request some sort of token, the identity provider validates the user and then sends redirects to the application with a token, the application validates the token.

    The problem is that user is not authenticated during this flow and so according to your requirements Spring Security would attempt to authenticate. Another issue is in order to trigger the authentication you need to have the container protect the resources (to trigger the SSO). If it is protected by the container, Spring Security will not be able to see the request.

    There may be ways to get things to work, but it would require a lot more information on your setup.

    Comment


    • #3
      Thanks for the response Rob. I agree that this is a chicken and egg problem where the two methods of authentication are interrupting each others flow. Allow me to better detail my security requirements for this application:

      We have a pre-existing non-spring application which uses container based security (Tomcat's JNDIRealm for LDAP and JAASRealm for a database authentication fallback). I am developing a spring application which integrates with the existing application. For this reason we'd like to have a single sign on so that users can authenticate from either the legacy application or the spring application.

      Implementing strictly container-based security is an option, but is there a better way which will allow me to leverage spring security in the new application to restrict parts of the application based on roles? There is also a REST api which I will need to protect with some form of authentication.

      Comment


      • #4
        Before we try to solve everything I think it is best for me to understand how you plan to accomplish SSO. How will the container managed security support SSO between the two applications?

        Comment


        • #5
          I've never implemented SSO w/ Tomcat container managed security before so please excuse me if I'm incorrect here:

          I have Tomcat configured with a CombinedRealm which utilizes an LDAP authentication scheme and then falls back on JAAS should the LDAP auth fail. The JAAS authentication happens in a LoginModule class which can be shared between the projects. This class simply validates the user / pass combo against a database and allows / denies the request based on the result.

          Tomcat's server.xml snippet
          Code:
          <!-- Combined Realm Configuration -->
          <Realm className="org.apache.catalina.realm.CombinedRealm">
          
            <!-- LDAP -->
            <Realm className="org.apache.catalina.realm.JNDIRealm"
                   connectionURL="ldap://test.com:389"
                   connectionName="admin"
                   connectionPassword="pass"
                   userBase="CN=Users,DC=test,DC=com"
                   userSearch="(sAMAccountName={0})"
                   userRoleName="memberOf"
                   userSubtree="true"
                   roleBase="CN=Users,DC=test,DC=com"
                   roleSearch="(member={0})"
                   roleName="CN"
                   roleSubtree="true"
                   referrals="follow"
                   allRolesMode="strictAuthOnly"
                   />
          
            <!-- JAAS -->
            <Realm className="org.apache.catalina.realm.JAASRealm"
                   appName="myApp1"
                   userClassNames="com.test.auth.jaas.PlainUserPrincipal"
                   roleClassNames="com.test.auth.jaas.PlainRolePrincipal"
                   />
          
          </Realm>
          <!-- End Combined Realm Configuration -->
          Tomcat's jaas.config
          Code:
          myApp1{
                  com.test.auth.jaas.MyLoginModule required
                  debug="true"
                  requiredRole="development";
          };
          I am then hoping that since both apps will be hosted on the same server simultaneously that I will be able to authenticate myself to the server once and gain access to both apps until I log out or my session expires.

          I would like to use spring security w/ annotations in the new project to restrict access based on roles and in the case of the REST endpoints provide a separate method of authentication (HTTP basic or OAuth eventually).

          Comment


          • #6
            I'm not a Tomcat expert, but in my experience this would not work since the session is not shared between wars. I'd suggest trying to make two applications perform SSO without Spring Security first.

            If that doesn't work, you will likely need to use an SSO solution like CAS.

            Comment

            Working...
            X