Spring security - how to extend userDetailsService to add an additional field id

I am using the following security_context.xml in a new spring framework that we are trying to set up
For authentication we are using

                <user name="rod" password="koala" authorities="supervisor, teller, user" />
                <user name="dianne" password="emu" authorities="teller, user" />
                <user name="scott" password="wombat" authorities="user" />
                <user name="peter" password="opal" authorities="user" />
we will eventually use LDAP but for now planning to use a properties file and utilize <user-service id="userDetailsService" properties="users.properties"/>

In addition to username, password, grantedAuthority, enable/disable we would like to add userid as a field. How to extend  userDetailsService to add this extra attribute any pointers will be greatly appreciated.
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

mccarlIT Business Systems Analyst / Software DeveloperCommented:
Ok, so to do what you are after will require a number of steps...

1) You will need to extend Spring's UserDetails interface, to add a method for retrieving the "userId" field.

2) You will need to create your own implementation of the UserDetailsService interface. This will take as a property (or constructor arg) the properties file that contains your user info. It will also most likely load that property file on startup and store the details in an in-memory data structure such as a Map object. The UserDetailsService interface only has one method, so in your implementation of that method, you will look up the username provided and return the details in the file. These details will be in an object that you create which implements the interface you created in step 1 above. You could take the InMemoryDaoImpl source code as a possible example of how to do this, although I would make it a bit simpler than what it does.

3) You will need to register your custom UserDetailsService implementation for Spring Security to use, with some XML something like the below...
    <authentication-provider user-service-ref='myUserDetailsService'/>

  <beans:bean id="myUserDetailsService"
    <beans:property name="properties" value="users.properties"/>

Open in new window

4) To use the above, whereever you access the current UserDetails object, you will just have to check that it is an "instanceof" you interface created in Step 1, and then cast it as that interface and then you will be able to access your userId field.

At a later time, when you look to use LDAP, you would look at implementing the UserDetailsContextMapper interface which maps from LDAP info to your custom UserDetails created in Step 1 above, and then you can set that UserDetailsContextMapper on the LdapAuthenticationProvider that you would be using.

Let us know if you need more detailed information in this regard, and I can help further.
softprossgAuthor Commented:
thanks mccarl, I am working on it.
softprossgAuthor Commented:
hi mccarl

I have customized userDetailsService, my security context :

<beans:bean id="webDetailsService" class="com.lmco.fs21.security.WebUserDetailsService">
<beans:bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>
        <authentication-provider  user-service-ref="webDetailsService">
        <password-encoder ref="encoder" />          

My impl also works

public class WebUserDetailsService implements UserDetailsService{
      private static final Logger LOGGER = Logger.getLogger(WebUserDetailsService.class);
      public UserDetails loadUserByUsername(String username)
                  throws UsernameNotFoundException {
            final ArrayList<GrantedAuthority> ROLES = new ArrayList<GrantedAuthority>();
            ROLES.add(new GrantedAuthorityImpl("supervisor"));
        //return new WebUserDetails("rod", "koala", true, true, true, true, ROLES, "01");
            return new WebUserDetails("rod", "13e778ebbddf10b56326871a83d2ee03a1b675e03fca2b63e99577671f536d83bec68954dfb95a5d", true, true, true, true, ROLES, "01");

but I am not being able to load the properties file in my code as you suggested

<beans:bean id="webDetailsService" class="com.lmco.fs21.security.WebUserDetailsService">
                   <beans:property name="properties" value="users.properties" />
not sure how to reference the property file in my code.

Please can you guide me.
JavaScript Best Practices

Save hours in development time and avoid common mistakes by learning the best practices to use for JavaScript.

mccarlIT Business Systems Analyst / Software DeveloperCommented:
Ok, so in your WebUserDetailsService class, have it implement InitializingBean aswell, this will give you an afterPropertiesSet method that gets called by Spring after it has instantiated your 'webDetailsService' bean. Also, add a private member to hold the value 'properties' property (as a String) and a setter method so that Spring can inject the "user.properties" string from the XML configuration.

And then in your afterPropertiesSet method, you load the properties file with code something like below...

Properties props = new Properties();

Open in new window

And then you can iterate through the entries in the 'props' object (it implementes Map, so that should be easy) and parse whatever format you want to use for the file. Of course, if you aren't worried about having that file be formatted as a true .properties file, nothing is tied to that. So you could, for example, load a file that is in CSV format and parse just as you might do in any other program. Its up to you.

Anyway, the result is that from the above parsing, you should create/populate a Map<String, WebUserDetails> object, with the username as the key and the created WebUserDetails objects as values, and then your loadUserByUsername method would be as simple as...

      public UserDetails loadUserByUsername(String username)
                  throws UsernameNotFoundException {
            WebUserDetails userDetails = userDetailsMap.get(username);
            if (userDetails == null)
                  throw new UsernameNotFoundException();
            return userDetails;

Open in new window

Hope that is enough to guide to the solution you are after!
softprossgAuthor Commented:
Dear Mccarl

Thanks for you excellent suggestions, I am past userDetails hurdle, now teying to set up a customized login page with

<form-login login-page="/login" default-target-url="/success"
                  always-use-default-target="true" login-processing-url="/j_spring_security_check" authentication-failure-url="/login?error=1" />

I could set up the controller and it works fine for the first time I start the server. From second instance after a logout, cannot login back with the same user. Apparently the logout is not clearing the server cache. It is not browser cache, can you suggest me once again what needs to be done.

Thanks for all your help.
mccarlIT Business Systems Analyst / Software DeveloperCommented:
In the <http/> element of your configuration, have you turned off the 'auto-config' attribute? ie. set it to false? If so, you will need to add back in a child element called <logout/>. And you are logging out by accessing the /j_spring_security_logout URL?

If the above isn't the case, then you will need to get a bit more information because it isn't clear what is happening otherwise. By 'cannot login back with the same user', what exactly do you mean? Does it fail with an error message? Or is it just as though that user is no longer an authorized user? Can you post your full config?

Also, try it WITHOUT your custom UserDetailsService again, just the standard static users in the configuration as per your original post, just to make sure that there isn't an issue there.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
softprossgAuthor Commented:
thanks Mccarl I re did the application from scratch, now logout is working with custom user details.
mccarlIT Business Systems Analyst / Software DeveloperCommented:
Glad you were able to resolve your issues!
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Editors IDEs

From novice to tech pro — start learning today.