How to convert the spring security xml configuration hibernate into java config using Spring-Security 3 and Hibernate 4

StackOverflow https://stackoverflow.com/questions/22176693

Question

I just learned about spring security and want to make a connection to a database using java hibernate configuration but I find very few examples or tutorial. I find a lot more by using xml configuration. And i'm using Spring 4.0.2, Spring-Security 3.2.0 and Hibernate 4.3.2 here

my question is: how the following xml converted into java configuration?

<authentication-manager>
    <authentication-provider user-service-ref="customUserDetailsService">
        <password-encoder hash="plaintext">
    </password-encoder></authentication-provider>
</authentication-manager>

where the CustomUserDetailsService.java

package com.whatever.svtest.service.impl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.Service;
import org.springframework.transaction.annotation.Transactional;

import com.whatever.svtest.dao.UserDao;

@Service
@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        com.whatever.svtest.model.User domainUser = userDao.getByUsername(username);

        if (domainUser == null) {
            throw new UsernameNotFoundException("user not found");
        }

        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("USER"));        

        return new User(username, domainUser.getPassword(), true, true, true, true, authorities);
    }

}

on SecurityConfig.java I use the default login form created by spring. I'm trying to figure out by myself how to convert the xml config to java config.

package com.whatever.svtest.init;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

import com.whatever.svtest.service.impl.UserServiceImpl;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(new UserServiceImpl()).passwordEncoder(NoOpPasswordEncoder.getInstance());

    }

}

and i put the SecurityConfiguration.java on the Initializer.java like this

package com.whatever.svtest.init;

import javax.servlet.Filter;

import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // return null;
        return new Class[] { SecurityConfiguration.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebAppConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new DelegatingFilterProxy("springSecurityFilterChain") };
    }

}

WebAppConfig.java

package com.whatever.svtest.init;

import javax.annotation.Resource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@Import({ DatabaseConfig.class })
@ComponentScan(basePackages = { "com.whatever.svtest.controller" })
@PropertySource({ "classpath:persistence-mysql.properties" })
public class WebAppConfig extends WebMvcConfigurerAdapter {

    @Resource   
    private Environment env;

    @Override   
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
    }

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");
        source.setUseCodeAsDefaultMessage(true);
        return source;
    }

    @Bean
    public ViewResolver setupViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

}

When i run my webapps i got this. (i put the image here http://i.stack.imgur.com/Mssrc.jpg)

and i also read (somewhere) about creating an custom implementation of AuthenticationProvider.java but i dont know where to put this code..

package com.whatever.svtest.init;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

import com.whatever.svtest.dao.UserDao;
import com.whatever.svtest.model.User;

public class MyAuthProvider implements AuthenticationProvider {

    @Autowired
    private UserDao userDao;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        User user = userDao.getByUsername(name);
        authentication.setAuthenticated(user != null && password.equals(user.getPassword()));
        return authentication;
    }

    @Override
    public boolean supports(Class<?> authentication) {

        return (MyAuthProvider.class.isAssignableFrom(authentication));
    }

}
Was it helpful?

Solution 2

[SOLVED]

after two days struggling with my own code finnaly i found the solution..!

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = SecurityConfiguration.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

}

I dont have to create the new bean. I just need to pass the UserService object into userDetailsService method, put the autowired and of course use the @ComponentScan to the current class. UserService class already have a UserDao and i implement the UserDetailsService in it.

@Service("userService")
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService, UserDetailsService {

    @Autowired
    private UserDao userDao;

    // other method

    @Override
    public User getByUsername(String username) {
        return userDao.getByUsername(username);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = getByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("user not found");
        } else {
            List<GrantedAuthority> listAuthorities = new ArrayList<GrantedAuthority>();
            listAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            return new org.springframework.security.core.userdetails.User(username, user.getPassword(), true, true, true, true, listAuthorities);
        }

    }
}

thanks to Rob Winch for giving a clue.

OTHER TIPS

Inconsistent Configuration?

The configuration you posted doesn't quite make sense to me. Specifically the following:

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(new UserServiceImpl()).passwordEncoder(NoOpPasswordEncoder.getInstance());
}

A solution

It seems that you have not defined UserServiceImpl, but you have defined CustomUserDetailsService (which is likely the argument that should be passed in. However, in order for a bean to be autowired, you need to create it as a bean. So you should change your configuration as such:

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(uds());
}

@Bean
public CustomUserDetailsService uds() {
    return new CustomUserDetailsService();
}

By returning CustomUserDetailsService as a @Bean you ensure Spring autowire it properly.

A few additional notes:

  • You do not need a custom AuthenticationProvider. This is since you are authenticating with a username / password a UserDetailsService is fine. If you wanted to authenticate w/ something other than a username / password you would create a custom AuthenticationProvider
  • There is no need to specify the no-op Password Encoder since this is the default.

Improving CustomUserDetailsService

One thing to point out with your current implementation is that while you can @Autowire fields directly, it makes it much easier to make mistakes, so you should probably change your CustomUserDetailsService to have a constructor that allows injecting the UserDao. This also makes unit testing easier (so you don't need to use reflection to set the UserDao). So you would update CustomUserDetailsService to be:

@Service
@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {

    private UserDao userDao;

    @Autowired
    public CustomUserDetailsService(UserDao userDao) {
        this.userDao = userDao;
    }

Then your configuration can be the following:

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(uds());
}

@Autowired
private UserDao userDao;

@Bean
public CustomUserDetailsService uds() {
    return new CustomUserDetailsService(userDao);
}

Update based on new error

You also need to ensure your UserDao is picked up as a Bean. For example:

@Bean
public UserDao userDao() {
    return new UserDao(...);
}

NOTE: Make Sure you initialize the UserDao properly (i.e. ensure all its dependencies are initialized. If you use Autowired on UserDao ensure those dependencies are @Bean also.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top