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;
}
}
Author by
Daniele Grigioni
Updated on July 09, 2022Comments
-
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 almost 11 yearsIf 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 over 9 yearsSo you are not using your UserDetailsAdapter class then ?