Changing password using Spring security
Asked Answered
U

2

2

I use,

  • Spring Framework 4.0.0 RELEASE (GA)
  • Spring Security 3.2.0 RELEASE (GA)
  • Struts 2.3.16

in which I use,

org.springframework.security.authentication.dao.DaoAuthenticationProvider

for authentication. My spring-security.xml file looks like the following.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="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-4.0.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <http pattern="/Login.jsp*" security="none"></http>

    <http auto-config='true' use-expressions="true" disable-url-rewriting="true" authentication-manager-ref="authenticationManager">
        <session-management session-fixation-protection="newSession">
            <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
        </session-management>

        <csrf/>

        <headers>
            <xss-protection />
            <frame-options />
            <!--<cache-control />-->
            <!--<hsts />-->
            <content-type-options /> <!--content sniffing-->
        </headers>

        <intercept-url pattern="/admin_side/**" access="hasRole('ROLE_ADMIN')" requires-channel="any"/>

        <form-login login-page="/admin_login/Login.action" authentication-success-handler-ref="loginSuccessHandler" authentication-failure-handler-ref="authenticationFailureHandler"/>
        <logout logout-success-url="/admin_login/Login.action" invalidate-session="true" delete-cookies="JSESSIONID"/>
    </http>

    <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

    <beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="userDetailsService" ref="userDetailsService"/>
        <beans:property name="passwordEncoder" ref="encoder" />
    </beans:bean>

    <beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
        <beans:property name="providers">
            <beans:list>
                <beans:ref bean="daoAuthenticationProvider" />
            </beans:list>
        </beans:property>
    </beans:bean>

    <authentication-manager>
        <authentication-provider user-service-ref="userDetailsService"/>            
    </authentication-manager>

    <beans:bean id="loginSuccessHandler" class="loginsuccesshandler.LoginSuccessHandler"/>
    <beans:bean id="authenticationFailureHandler" class="loginsuccesshandler.AuthenticationFailureHandler" />

    <global-method-security secured-annotations="enabled" proxy-target-class="false" authentication-manager-ref="authenticationManager">
        <protect-pointcut expression="execution(* admin.dao.*.*(..))" access="ROLE_ADMIN"/>
    </global-method-security>
</beans:beans>

The implementation of UserDetailsService is as follows.

@Service(value="userDetailsService")
public final class UserDetailsImpl implements UserDetailsService {

    @Autowired
    private final transient UserService userService = null;
    @Autowired
    private final transient AssemblerService assemblerService = null;

    @Override
    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        UserTable userTable = userService.findUserByName(userName);

        if (userTable == null) {
            throw new UsernameNotFoundException("User name not found.");
        } else if (!userTable.getEnabled()) {
            throw new DisabledException("The user is disabled.");
        } else if (!userTable.getVarified()) {
            throw new LockedException("The user is locked.");
        }

        //Password expiration and other things may also be implemented as and when required.
        return assemblerService.buildUserFromUserEntity(userTable);
    }
}

And the following is just a helper service that converts a user entity which is to be used by a Spring User object.

@Service(value="assembler")
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public final class AssemblerDAO implements AssemblerService {

    @Override
    public User buildUserFromUserEntity(UserTable userTable) {
        String username = userTable.getEmailId();
        String password = userTable.getPassword();
        boolean active = userTable.getEnabled();
        boolean enabled = active;
        boolean accountNonExpired = active;
        boolean credentialsNonExpired = active;
        boolean accountNonLocked = userTable.getVarified();
        Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        for (UserRoles role : userTable.getUserRolesSet()) {
            authorities.add(new SimpleGrantedAuthority(role.getAuthority()));
        }

        return new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }
}

There is no need to refer to these classes.


My question is that while using,

org.springframework.security.provisioning.JdbcUserDetailsManager

UserDetailsManager can be injected into a controller and its

public void changePassword(String oldPassword, String newPassword) throws AuthenticationException {
    //...
}

method can be used to change the password. I never tried this but it may roughly be implemented like the following.

<bean id="jdbcUserService" class="org.springframework.security.provisioning.JdbcUserDetailsManager">
    <property name="dataSource" ref="datasource" />
    <property name="authenticationManager" ref="authenticationManager" />
</bean>

and in a controller, it should be injected as follows.

@Autowired
@Qualifier("jdbcUserService")
public UserDetailsManager userDetailsManager;

Is there any facility provided by Spring security in the approach I'm using or just a simple method of my own in a DAO to change the password of the current logged-in user is sufficient? Kindly suggest, if I'm doing something wrong anywhere!

This contents may be too big to answer this question but I'm asking this because it is something quite experimental.

Unipolar answered 4/1, 2014 at 10:44 Comment(0)
P
1

A method to change the password is a good solution, as there is no special functionality for this in spring security.

The reason why no special functionality exists for this in spring security, is that it's not needed if using a session.

The user's current session identified by the JSESSIONID cookie is still residing in the user's browser and will still be a valid session after the password change.

When the old password was checked the last time the user logged in, a cookie was generated and kept in a map of valid cookies in memory.

The temporary authentication token (the cookie) is still valid and has a max. lifetime, and changing the password on the database will not impact the current session validity.

Phan answered 4/1, 2014 at 11:1 Comment(0)
V
1

I agree with the answer @jhadesdev;

Note that by calling Spring's JdbcUserDetailsManager.changePassword(), Spring updates the context-holder, plus updates the DB with the new password.

Spring does not handle the cookie, since it does not force your app to be a web application. so I guess if that is the case, a higher level layer in your app should update the session.

PS - out of curiosity - how did you implement the registration flow, and forgot password flow, etc? Spring does not handle this either. I have written a project that takes care of these flows...

Vacant answered 4/1, 2014 at 12:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.