Spring Security - UserDetailsService implementation - Login Fails

28,168

Here's how I solved the problem:

in the CustomDetailsService I was returning a UserDetails Object by using an Adapter as you can see.

Looking at some tutorial I realized that i should return an org.springframework.security.core.userdetails.User Object.

Here's my new CustomDetailsService implementation:

@Transactional(readOnly=true)
@Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Inject AccountDao accountDao;

@Override
public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException {
    Account account= accountDao.findByUsername(username);

    if(account==null) {throw new UsernameNotFoundException("No such user: " + username);
    } else if (account.getRoles().isEmpty()) {
        throw new UsernameNotFoundException("User " + username + " has no authorities");
                }

    boolean accountNonExpired = true;
    boolean credentialsNonExpired = true;
    boolean accountNonLocked = true;

    return new User(
            account.getUsername(),
            account.getPassword().toLowerCase(),
            account.isEnabled(),
            accountNonExpired,
            credentialsNonExpired,
            accountNonLocked,
            getAuthorities(account.getRoles()));
    }

public List<String> getRolesAsList(Set<Role> roles) {
    List <String> rolesAsList = new ArrayList<String>();
    for(Role role : roles){
        rolesAsList.add(role.getName());
    }
    return rolesAsList;
}

public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    for (String role : roles) {
        authorities.add(new SimpleGrantedAuthority(role));
    }
    return authorities;
}

public Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {
    List<GrantedAuthority> authList = getGrantedAuthorities(getRolesAsList(roles));
    return authList;
}

}
Share:
28,168
Daniele Grigioni
Author by

Daniele Grigioni

Updated on July 09, 2022

Comments

  • Daniele Grigioni
    Daniele Grigioni almost 2 years

    I'm quite new to spring and i'having this issue with spring security. Actually it only works without my custom UserDetailsService implementation.

    Account and Role Objects

    @Entity
    @Table(name="ACCOUNT", uniqueConstraints = {@UniqueConstraint (columnNames = "USERNAME"),      @UniqueConstraint (columnNames = "EMAIL")})
    public class Account implements Serializable {
    private static final long serialVersionUID = 2872791921224905344L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="ID")
    private Integer id;
    
    @Column(name="USERNAME")
    @NotNull
    private String username;
    
    @Column(name="PASSWORD")
    @NotNull
    private String password;
    
    @Column(name="EMAIL")
    @NotNull
    @Email
    private String email;
    
    @Column(name="ENABLED")
    private boolean enabled;
    
    @ManyToMany(cascade= CascadeType.ALL)
    @JoinTable(name="ACCOUNT_ROLE", joinColumns = {@JoinColumn (name="ID_ACCOUNT")}, inverseJoinColumns ={ @JoinColumn (name="ID_ROLE")})
    private Set<Role> roles = new HashSet<Role>(0);
    

    Role

    @Entity
    @Table(name="ROLE", uniqueConstraints={@UniqueConstraint (columnNames="NAME")})
    public class Role implements Serializable{
    
    private static final long serialVersionUID = -9162292216387180496L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    
    @Column(name = "NAME")
    @NotNull
    private String name;
    
    @ManyToMany(mappedBy = "roles")
    private Set<Account> accounts = new HashSet<Account>(0);
    

    The adapter for the UserDetails

    @SuppressWarnings({ "serial", "deprecation" })
    public class UserDetailsAdapter implements UserDetails {
    
    private Account account;
    
    public UserDetailsAdapter(Account account) {this.account = account;}
    
    public Account getAccount() {return account;}
    
    public Integer getId(){return account.getId();}
    
    public String getEmail () {return account.getEmail();}
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        for (Role r :account.getRoles()) {
            authorities.add(new GrantedAuthorityImpl(r.getName()));
        }
        return authorities;
    }
    

    The custom UserDetailsService

    @Service("customUserDetailsService")
    public class CustomUserDetailsService implements UserDetailsService {
    @Inject AccountDao accountDao;
    @Inject RoleDao roleDao;
    
    
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        Account account= accountDao.findByUsername(username);
    
    
    
        if(account==null) {throw new UsernameNotFoundException("No such user: " + username);
        } else if (account.getRoles().isEmpty()) {
            throw new UsernameNotFoundException("User " + username + " has no authorities");
                    }
        UserDetailsAdapter user = new UserDetailsAdapter(account);
        return user;
        }
    

    The web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
    <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    
    <filter-mapping>
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml
                        /WEB-INF/spring/appServlet/security-  context.xml</param-value>
    </context-param>
    
    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- Creates the Filters to handle hibernate lazyload exception -->
    
    <filter>
        <filter-name>OpenSessionInViewFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>OpenSessionInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    
    <!-- Processes application requests -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    </web-app>
    

    the root-context

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc  http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
    <!-- Root Context: defines shared resources visible to all other web components -->
    <context:property-placeholder properties-ref="deployProperties"/> 
    <!-- Remember to correctly locate the right file for properties configuration(example DB connection parameters) -->
    <bean id="deployProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"
        p:location="/WEB-INF/spring/appServlet/spring.properties" />
    
        <context:annotation-config/>
        <context:component-scan base-package="org.treci">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        </context:component-scan>
    
        <import resource="/appServlet/data-context.xml"/>
    

    The security context

    <?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:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
    <http pattern="/resources" security="none" />
    
    
    <http auto-config="true" use-expressions="true">
        <intercept-url pattern="/" access="permitAll"/>
        <intercept-url pattern="/login" access="permitAll"/>
        <intercept-url pattern="/login-success" access="hasRole('ROLE_ADMIN')"/>
    
        <form-login login-page="/login" 
        default-target-url="/login-success"
        authentication-failure-url="/login-failed"/>
        <logout logout-success-url="/logout"/>
    </http>
    
    <beans:bean id="customUserDetailsService" class="org.treci.app.service.CustomUserDetailsService"></beans:bean>
    
    
    <authentication-manager>
        <authentication-provider user-service-ref="customUserDetailsService">
        </authentication-provider>
    </authentication-manager>
    
    </beans:beans>
    

    I hope some of you can help, save me :)

  • Ginja Ninja
    Ginja Ninja almost 11 years
    If you're using LDAP to authenticate, a database for roles authorization, and don't want to pass a password to UserDetailsService, you can pass a string (ie "LDAP") instead. It won't accept a null value.
  • Stephane
    Stephane over 9 years
    So you are not using your UserDetailsAdapter class then ?