Search code examples
springcachingspring-securityjaasspring-security-ldap

Spring JAAS Authentication with database authorization


I am using Spring security 4.0. My login module is configured in Application server so I have to do authentication using JAAS but my user details are stored in database, so once authenticated user object will be created by querying database. Could you please let me know how to achieve this i.e. LDAP authentication and load user details from database. Also how cache the user object using eh-cache, so that the user object can be accessed in the service / dao layer.


Solution

  • This can be achieved using CustomAuthentication Provider. Below are the codes.

    import java.util.Arrays;
    import java.util.List;
    
    import org.springframework.security.authentication.AuthenticationProvider;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.authentication.jaas.JaasGrantedAuthority;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import com.sun.security.auth.UserPrincipal;
    
    public class CustomAutenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider {
        private AuthenticationProvider delegate;
    
        public CustomAutenticationProvider(AuthenticationProvider delegate) {
            this.delegate = delegate;
        }
    
        @Override
        public Authentication authenticate(Authentication authentication) {
            Authentication a = delegate.authenticate(authentication);
    
            if(a.isAuthenticated()){
                a = super.authenticate(a);
            }else{
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
    
            return a;
        }
    
        private List<GrantedAuthority> loadRolesFromDatabaseHere(String name) {
            GrantedAuthority grantedAuthority =new JaasGrantedAuthority(name, new UserPrincipal(name));
            return Arrays.asList(grantedAuthority);
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return delegate.supports(authentication);
        }
    
        /* (non-Javadoc)
         * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider#additionalAuthenticationChecks(org.springframework.security.core.userdetails.UserDetails, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
         */
        @Override
        protected void additionalAuthenticationChecks(UserDetails userDetails,
                UsernamePasswordAuthenticationToken authentication)
                        throws AuthenticationException {
    
    
            if(!authentication.isAuthenticated())
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
    
    
        }
    }
    

    UserDetails required for DAOAuthentication

    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Component;
    
    import com.testjaas.model.User;
    import com.testjaas.model.UserRepositoryUserDetails;
    
    
    @Component
    public class AuthUserDetailsService implements UserDetailsService {
    
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            System.out.println("loadUserByUsername called !!");
            com.testjaas.model.User user = new User();
            user.setName(username);
            user.setUserRole("ROLE_ADMINISTRATOR");
            if(null == user) {
                throw new UsernameNotFoundException("User " + username + " not found.");
            }
    
            return new UserRepositoryUserDetails(user);
        }
    
    
    }
    

    RoleGrantor - This will be a dummy class required for Spring JAAS authentication

    import java.security.Principal;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    import org.springframework.security.authentication.jaas.AuthorityGranter;
    
    public class RoleGranterFromMap implements AuthorityGranter {
    
        private static Map<String, String> USER_ROLES = new HashMap<String, String>();
    
        static {
            USER_ROLES.put("test", "ROLE_ADMINISTRATOR");
            //USER_ROLES.put("test", "TRUE");
        }
    
        public Set<String> grant(Principal principal) {
            return Collections.singleton("DUMMY");
        }
    }
    

    SampleLogin - This should be replaced with your login module

    import java.io.Serializable;
    import java.security.Principal;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.security.auth.Subject;
    import javax.security.auth.callback.Callback;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.callback.NameCallback;
    import javax.security.auth.callback.PasswordCallback;
    import javax.security.auth.login.LoginException;
    import javax.security.auth.spi.LoginModule;
    
    public class SampleLoginModule implements LoginModule {
    
        private Subject subject;
        private String password;
        private String username;
        private static Map<String, String> USER_PASSWORDS = new HashMap<String, String>();
    
        static {
            USER_PASSWORDS.put("test", "test");
        }
    
        public boolean abort() throws LoginException {
            return true;
        }
    
        public boolean commit() throws LoginException {
            return true;
        }
    
        public void initialize(Subject subject, CallbackHandler callbackHandler,
                Map<String, ?> sharedState, Map<String, ?> options) {
            this.subject = subject;
    
            try {
                NameCallback nameCallback = new NameCallback("prompt");
                PasswordCallback passwordCallback = new PasswordCallback("prompt",false);
    
                callbackHandler.handle(new Callback[] { nameCallback,passwordCallback });
    
                this.password = new String(passwordCallback.getPassword());
                this.username = nameCallback.getName();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        public boolean login() throws LoginException {
    
            if (USER_PASSWORDS.get(username) == null
                    || !USER_PASSWORDS.get(username).equals(password)) {
                throw new LoginException("username is not equal to password");
            }
    
            subject.getPrincipals().add(new CustomPrincipal(username));
            return true;
        }
    
        public boolean logout() throws LoginException {
            return true;
        }
    
        private static class CustomPrincipal implements Principal, Serializable {
            private final String username;
    
            public CustomPrincipal(String username) {
                this.username = username;
            }
    
            public String getName() {
                return username;
            }
        }
    
    }
    

    Spring XML configuration

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:security="http://www.springframework.org/schema/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jdbc="http://www.springframework.org/schema/jdbc"
        xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">
        <security:http auto-config="true">
            <security:intercept-url pattern="/*" access="isAuthenticated()"/>
        </security:http>
        <!-- <security:authentication-manager>
            <security:authentication-provider ref="jaasAuthProvider" />
        </security:authentication-manager> -->
    
        <bean id="userDetailsService" class="com.testjaas.service.AuthUserDetailsService"></bean>
        <bean id="testService" class="com.testjaas.service.TestService"/>
        <bean id="applicationContextProvider" class="com.testjaas.util.ApplicationContextProvider"></bean>
    
        <security:authentication-manager>
            <security:authentication-provider ref="customauthProvider"/>
        </security:authentication-manager>
    
        <bean id="customauthProvider" class="com.testjaas.security.CustomAutenticationProvider">
            <constructor-arg name="delegate" ref="jaasAuthProvider" />
            <property name="userDetailsService" ref="userDetailsService" />
        </bean>
    
        <bean id="jaasAuthProvider" class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
            <property name="loginConfig" value="classpath:pss_jaas.config" />
            <property name="authorityGranters">
                <list>
                    <bean class="com.testjaas.security.RoleGranterFromMap" />
                </list>
            </property>
            <property name="loginContextName" value="JASSAuth" />
            <property name="callbackHandlers">
                <list>
                    <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler" />
                    <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler" />
                </list>
            </property>
        </bean>
    </beans>
    

    Sample jaas config

    JASSAuth {
      com.testjaas.security.SampleLoginModule required;
    };