Hello I'm facing some problems with the authentification mechanism provided by Spring Boot Security
The main issue is that I have a login form provided by Spring Boot Security and want to check the inputs with an underlying database of users with email, hashed_password, and user_salt
I injected my UserDetailService in the WebSecurityConfigAdapter
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(as); // #Autowired private AuthService as;
}
The AuthService is the following:
#Service
public class AuthService implements UserDetailsService {
#Autowired
MyUserClassRepository ur;
#Override
public UserDetails loadUserByUsername(String username) {
MyUserClass user = ur.findUserByEmail(username);
if(user == null){
throw new UsernameNotFoundException(username);
}
System.out.println("User found but we must check the password");
return new MyUserPrincipal(user);
}
#SessionScope
public class MyUserPrincipal implements UserDetails {
private MyUserClass user;
MyUserClass getUser(){
return user; // I need to retrieve the user later for my Webapplication
}
MyUserPrincipal(MyUserClass user){
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
System.out.println("Call Granted");
return null;
}
#Override
public String getPassword() {
return user.passwordHash;
}
#Override
public String getUsername() {
return user.email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
}
I need a hook or a method where I can get the input of the user so I can encode and salt the password.
A default PassWordEncoder as a component doesn't have access to the inputs or at least I don't know how to get the credentials in the input field:
//#Component
public final class myPwEncoder implements PasswordEncoder {
public String encode(CharSequence rawPassword) {
System.out.println("Encode My Stuff: " + rawPassword);
//MyUserClass user = ((AuthService.MyUserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser(); NullPointerException because the User will be after the verification initialized...
return rawPassword.toString();
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
System.out.println("Macthes call: "+rawPassword + " " + encodedPassword);
return rawPassword.toString().equals(encodedPassword);
}
}
Thank you for your help!
(I want to use sha256-hash with random generated Salt stored in the user DB table. I know BCrypt is probably the best choice but I have to need to use sha256)
Related
In summary, user is being authenticated, but I do appear to actually have logged into the users account.
I'm currently working on implementing LDAP authentication on a project. It appears that the authentication portion of things are working in the sense that my application does accept the correct credentials. The issue I'm having is that I cant seem to access 'principal' in my jsp views. (I was able to access all of this before making the switch to LDAP). When running a trace my CustomUserDetails service is querying and pulling the correct account information. Any assistance is appreciated
This will display the proper username:
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="name"/></h2>
</sec:authorize>
This does not (it did work before LDAP)
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="principal.firstName"/></h2>
</sec:authorize>
Relevant Code
SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private CustomUserDetailsService userDetailsService;
#Bean
public CustomSaltSource customSaltSource(){ return new CustomSaltSource();}
#Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new AuthenticationSuccessHandler();
}
#Autowired
void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().contextSource()
.url("ldap://bar.foo.com")
.port(####)
.and()
.userDnPatterns("cn={0},cn=users,dc=ms,dc=ds,dc=foo,dc=com")
.ldapAuthoritiesPopulator(new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService));
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/skins/**", "/css/**", "/**/laggingComponents", "/assets/**").permitAll().and()
.formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/", true).successHandler(myAuthenticationSuccessHandler())
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).deleteCookies("JSESSIONID").permitAll()
.and().authorizeRequests().antMatchers("/api/**").anonymous()
.and().authorizeRequests().anyRequest().authenticated().and().rememberMe().key("KEY").userDetailsService(userDetailsService);
}
#Override
public void configure(WebSecurity web) throws Exception {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new PermissionEvaluator());
web.expressionHandler(handler);
web.ignoring().antMatchers( "/skins/**", "/css/**", "/api/**", "/assets/**", "/health"); //"/**/test/**"
}
}
CustomUserDetaulsService.java
import org.hibernate.Session;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Set;
#Service
public class CustomUserDetailsService implements UserDetailsService{
#Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
Session session = DBFactory.factory.openSession();
User user = (User) session.createQuery("from User where userName =:userName")
.setParameter("userName", username).uniqueResult();
if(user == null){
throw new UsernameNotFoundException("User Not Found");
}
//Needed to initialize permissions
Set<Role> roles = user.getRoles();
int i = roles.size();
for(Role role: roles){
int j = role.getPermissions().size();
}
CustomUserDetails userDetails = new CustomUserDetails(user);
session.close();
return userDetails;
}
}
If I'm not wrong,
You switched to Ldap Authorization, set url and DN patterns but still provide userDetailsService which search user in database.
You need to set UserDetailsContextMapper by implementing the interface and creating your custom one. This will map data from ldap directory context to your custom UserDetails and return it through mapUserFromContext method.
Here is an example CustomUserDetailsContextMapper:
public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
private LdapUser ldapUser = null;
private String commonName;
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
} catch (NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser(ldapUserDetails);
ldapUser.setCommonName(commonName);
return ldapUser;
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
My custom LdapUser:
public class LdapUser implements UserDetails
{
private String commonName;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
#Override
public String getDn() {
return ldapUserDetails.getDn();
}
#Override
public void eraseCredentials() {
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
#Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
#Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
}
Then set CustomUserDetailsContextMapper in auth configuration. This is how you will be able to get your user from authentication.getPrincipal().
I hope I correctly understand your problem and answered.
I had to test LDAP auth + different roles for devs,admins,users
Many thanks to #Yernar Arystanov
The code is not so clean but works...
public class LdapUser implements UserDetails
{
private String commonName;
private List<String> groups;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
#Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
#Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
public String getCommonName() {
return commonName;
}
public void setCommonName(String commonName) {
this.commonName = commonName;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
}
Suggestion by #Ivan Baranuk was correct (extends LdapUserDetailsMapper):
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper {
private LdapUser ldapUser = null;
private String commonName;
private List<String> groups = new LinkedList<String>();
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails;
ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
Arrays.stream(ctx.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
groups.add(m.toString());
});
} catch (NamingException | javax.naming.NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser((LdapUserDetails) ldapUserDetails);
ldapUser.setCommonName(commonName);
ldapUser.setGroups(groups);
return ldapUser;
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
then finally in SecurityConfig:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
public void configure(AuthenticationManagerBuilder auth) throws Exception {
...
auth.ldapAuthentication()
.userSearchBase("enter your search base")
.userSearchFilter("(&(objectClass=user)(sAMAccountName={0}))")
.userDetailsContextMapper(ldapUserDetailsMapper())
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator())
.contextSource()
.url(yourProperties.getLdapUrl())
.managerDn(yourProperties.getManagerDn())
.managerPassword(yourProperties.getManagerPassword());
}
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
return new LdapAuthoritiesPopulator() {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData,
String username) {
LinkedList<SimpleGrantedAuthority> res = new LinkedList();
Arrays.stream(userData.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
if(m.toString().equals("your_dev_group"))
res.add(new SimpleGrantedAuthority("DEV_USER"));
if(m.toString().equals("your_admin_group"))
res.add(new SimpleGrantedAuthority("ADMIN_USER"));
});
if(res.isEmpty())
return Arrays.asList(new SimpleGrantedAuthority("USER"));
else
return res;
}
};
}
#Bean
#Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Bean
protected LdapUserDetailsMapper ldapUserDetailsMapper() {return new CustomUserDetailsContextMapper();}
}
At the end - some simple get method(authorized users only):
#RequestMapping("/logger")
public String testLogger(Authentication authentication) {
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_DEV_USER"))) {
LdapUser userDetails = (LdapUser) authentication.getPrincipal();
log.info("WELCOME :" + userDetails.getCommonName());
userDetails.getGroups().iterator().forEachRemaining((g) - > log.info("group: " + g.toString()));
}
return ""
}
i am a newbie to spring
was not able to implement login to the application, actually could not figure out where its wrong.
followed a youtube video to do all this.
Help would be very much appreciated.
When i try to log in the application wont allow to log in.
console logs shows querys are being executed but cant log into the system.
also the password are saved in plain ASCII.
WebSecurityConfig class
#Configuration
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccessDeniedHandler accessDeniedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
// roles admin allow to access /actuator/**
// roles user allow to access /Application/**
// custom 403 access denied handler
#Override
protected void configure(HttpSecurity http) throws Exception {
// some antMatchers permit all
}
}
customUserDetailsService class
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
private final UserRepo userRepo;
#Autowired
public CustomUserDetailsService(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Override
// userId is reffered as username
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user=userRepo.findByUsername(userName);
if(null == user){
System.out.println("\n\n\n No user present with username: "+userName);
throw new UsernameNotFoundException("No user present with username: "+userName);
}else{
CustomUserDetails c =new CustomUserDetails(user);
//System.out.println(c.getAuthorities());
return c;
}
}
}
CustomUserDetails class
public class CustomUserDetails extends User implements UserDetails{
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<UserRole> roles = super.getUserRole();
List<String> userRoles= new ArrayList<String>();
for(UserRole r : roles) {
userRoles.add(r.getRole().toString());
}
String strRoles=StringUtils.collectionToCommaDelimitedString(userRoles);
return AuthorityUtils.commaSeparatedStringToAuthorityList(strRoles);
}
#Override
public boolean isAccountNonExpired() {
return super.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return super.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return super.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return super.isEnabled();
}
#Override
public String getUsername() {
return super.getUsername();
}
#Override
public String getPassword() {
return super.getPassword();
}
}
I try to implement log-file which stores all logins.
So far I put some code to my LoginHandler but I always get the ERROR:
org.springframework.security.core.userdetails.User cannot be cast to at.qe.sepm.asn_app.models.UserData
The method in my LoginHandler:
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
UserData user = (UserData)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
AuditLog log = new AuditLog(user.getUsername() + " [" + user.getUserRole() + "]" ,"LOGGED IN", new Date());
auditLogRepository.save(log);
handle(httpServletRequest, httpServletResponse, authentication);
clearAuthenticationAttributes(httpServletRequest);
}
Is it possible to change the return value type from SecurityContextHolder to my UserData object?
Additional Code:
public class MyUserDetails implements UserDetails {
private UserData user;
public UserData getUser(){
return user;
}
#Override
public String getUsername(){
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword(){
return user.getPassword();
}
}
MyUserDetails myUserDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserData user = myUserDetails.getUser();
The compiler says that UserDetails and MyUserDetails are incompatible types.
My WebSecurityConfig:
#Configuration
#EnableWebSecurity()
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable(); // needed for H2 console
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(false)
.logoutSuccessUrl("/login.xhtml");
http.authorizeRequests()
//Permit access to the H2 console
.antMatchers("/h2-console/**").permitAll()
//Permit access for all to error pages
.antMatchers("/error/**")
.permitAll()
// Only access with admin role
.antMatchers("/admin/**")
.hasAnyAuthority("ADMIN")
//Permit access only for some roles
.antMatchers("/secured/**")
.hasAnyAuthority("ADMIN", "EMPLOYEE", "PARENT")
//If user doesn't have permission, forward him to login page
.and()
.formLogin()
.loginPage("/login.xhtml")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/secured/welcome.xhtml").successHandler(successHandler());
// :TODO: user failureUrl(/login.xhtml?error) and make sure that a corresponding message is displayed
http.exceptionHandling().accessDeniedPage("/error/denied.xhtml");
http.sessionManagement().invalidSessionUrl("/error/invalid_session.xhtml");
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new LoginHandler();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//Configure roles and passwords via datasource
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, true from user_data where username=?")
.authoritiesByUsernameQuery("select username, user_role from user_data where username=?")
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
I also tried to implement the Springs User, UserDetails and UserDetailsService but I failed so far. I have no idea how to adjust these to my project because I use inheritance. My models are UserData which inherits to Parent and Employee. So I also have UserBaseRepository and UserDataRepository. These all confuses me a lot.
For now I stuck in implementing the methods from the Spring provided User-classes.
The org.springframework.security.core.UserDetails should always be implemented by your own UserData or another class that wraps your UserData instance
For example:
public class UserData{
private username;
private password;
/// other user parameters
.
.
etc
}
public class MyUserDetails implements UserDetails {
private UserData user;
public UserData getUser(){
return user;
}
#Override
public String getUsername(){
return user.getUsername();
}
#Override
public String getPassword(){
return user.getPassword();
}
}
And then you cast it like this
MyUserDetails myUserDetails = (MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserData user = myUserDetails.getUser();
I store information about users in seperated tables owners,employees,users i am trying to use java configuration in spring security.
I have created three diferent authentication providers for each user type, but only the Users Provider is being triggered. I have read the spring security docs and the only way to do this seems to be is create class with multiple embedded classes extended from WebSecurityConfigurerAdapter but i don't want to do it this way because it requires a lot of duplicating code, is there any other way
I tryed to use the simple userDetailService inside which i send request to all tables in databese but still there is not results, only one query is buing executed and nothing, the only responce i get is:
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
.s.a.DefaultAuthenticationEventPublisher : No event was found for the
exception
org.springframework.security.authentication.InternalAuthenticationServiceException
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for
failed:
org.springframework.security.authentication.InternalAuthenticationServiceException:
No entity found for query; nested exception is
javax.persistence.NoResultException: No entity found for query
But i never throw any exception!! And the most strange is that i can see in the debugger how the execution rapidly stops right after em.createQuery(..).getSingleResult().. and that's it, nothing more! There is no return statement no exception nothing, wtf!!
This is part of my current configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(createAuthenticationProvider(employeeDetailService()))
.authenticationProvider(createAuthenticationProvider(ownerDetailsService()))
.authenticationProvider(createAuthenticationProvider(userDetailsService()));
}
#Bean
public OwnerDetailsService ownerDetailsService() {
return new OwnerDetailsService();
}
#Bean
public EmployeeDetailServiceImpl employeeDetailService() {
return new EmployeeDetailServiceImpl();
}
#Bean
public UserDetailsServiceImpl userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(6);
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new MySimpleUrlAuthenticationSuccessHendler();
}
private AuthenticationProvider createAuthenticationProvider(UserDetailsService service) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(service);
provider.setPasswordEncoder(passwordEncoder());
provider.setHideUserNotFoundExceptions(true);
return provider;
}
User detail services:
#Service
public abstract class CustomUserDetailService implements UserDetailsService{
#Autowired
IDBBean dao;
protected CustomUserDetails getUser(GetUserByNameFunction function, String name) {
return createUser(function.get(name));
}
protected CustomUserDetails createUser(Authenticational user) {
return new CustomUserDetails(user, getAuthorities(user.getAuthority()));
}
protected List<GrantedAuthority> getAuthorities(String authority) {
return Collections.singletonList(new SimpleGrantedAuthority(authority));
}
}
Implementations
public class EmployeeDetailServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getEmployeeByEmail, email);
}
}
public class OwnerDetailsService extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getOwnerByEmail, email);
}
}
public class UserDetailsServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return super.getUser(dao::getUserByEmail, userName);
}
}
Custom user details:
private Long id;
private String userEmail;
public CustomUserDetails(Authenticational user,
Collection<? extends GrantedAuthority> authorities) {
super(
user.getName(),
user.getPassword().toLowerCase(),
user.isEnabled(),
true,
true,
true,
authorities);
upadateValues(user);
}
private void upadateValues(Authenticational user) {
this.id = user.getId();
this.userEmail = user.getEmail();
}
Just to clarify something from the other answer:
Your authentication providers are stored in a list inside ProviderManager that iterates your authentication request through them. If your authentication provider throws AuthenticationException (BadCredentialsException extends AuthenticationException), then the ProviderManager will try another provider. If you set the hideUserNotFoundExceptions property, then it will also wrap and ignore UsernameNotFoundException and try another provider in this case too.
If I were you I would start by placing a debugging point inside ProviderManager's authenticate method. From there you can find out why the other authentication providers are not being called for their authenticate method.
Also I would think about having only one authentication provider with one UserDetailsService. It seems to me that you are doing a lot of complex not really needed operations like passing function to your abstract implementation when all you could do would be to have one UserDetailsService that would ask all your DAOs for a user. Which is basically what you're trying to accomplish but minus 2 authentication providers, minus 1 abstract class and minus 2 UserDetailsService implementations.
Spring Security will not try other authentication providers if a provider throws an AccountStatusException or if a UserDetailsService throws a UserNameNotFoundException or any other AuthenticationException
If you want other providers to be tried, then the loadUserByUserName methods of your UserDetailsServiceImpl and OwnerDetailsService should not throw the UserNameNotFound exception.
You should decide if you either want to return a dummy anonymous UserDetails object that will be used exclusively for fallback or some other mechanism to not throw the exception when a user is not available in your UserDetailsService implementation
this is an example I'v done to provide multi-auth for my application which have two diffirent user : admin and client .
ps: the admin and the client are two diffirent model.
public class CustomUserDetails implements UserDetails{
private Admin admin;
private Client client;
public CustomUserDetails(Admin admin) {
this.admin = admin;
}
public CustomUserDetails(Client client) {
this.client = client;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
if((admin != null)&&(client==null)) {
return admin.getPassword();
}
else {
return client.getPassword();
}
}
#Override
public String getUsername() {
if((admin != null)&&(client==null)) {
return admin.getEmail();
}
else {
return client.getMail();
}
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
the CustomUserDetailsService class :
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
AdminRepository adminRepo;
#Autowired
ClientRepository clientRepo;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Admin admin = adminRepo.findByEmail(email);
Client client = clientRepo.findByEmail(email);
if((admin == null)&&(client==null)) {
throw new UsernameNotFoundException("User not found");
}
else if((admin != null)&&(client==null)) {
return new CustomUserDetails(admin);
}
else {
return new CustomUserDetails(client);
}
}
}
I configured my application to allow only single session per account.
It works fine when i use JdbcDaoImpl provider.
It also works fine when i use DaoAuthenticationProvider with custom User object extending spring User.
But when i try to setup spring security with custom User object implementing UsersDetails interface i got the message above when I tried to log in using different account. I cannot figure out why.
Here is my security configuration :
<session-management invalid-session-url="/">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
<authentication-manager>
<authentication-provider ref="daoAuthenticationProvider"/>
</authentication-manager>
<beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsServiceImplementation"></beans:property>
</beans:bean>
<beans:bean id="userDetailsServiceImplementation" class="com.company.service.implementation.UserServiceImpl" />
And my custom User object :
public class UserVo extends CommonVo implements UserDetails{
private String username;
private String password;
private String firstName;
private String lastName;
private String enabled;
private List<GrantedAuthority> userAuthorities;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEnabled() {
return enabled;
}
public void setEnabled(String enabled) {
this.enabled = enabled;
}
public List<GrantedAuthority> getUserAuthorities() {
return userAuthorities;
}
public void setUserAuthorities(List<GrantedAuthority> userAuthorities) {
this.userAuthorities = userAuthorities;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return userAuthorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return "Y".equals(enabled) ? true : false;
}
}
And this is my UserDetailsService implementation :
public class UserServiceImpl implements UserService, UserDetailsService{
#Autowired
private UserDAO userDao;
#Autowired
private UserVo userVo;
#Override
public int insert(UserVo userVo) {
return userDao.insert(userVo);
}
#SuppressWarnings("unchecked")
#Override
public List<UserVo> list(UserVo userVo) {
return (List<UserVo>) userDao.select(userVo);
}
public List<String> listUserRoles(UserVo userVo) {
return (List<String>) userDao.listUserRoles(userVo);
}
#SuppressWarnings("rawtypes")
#Override
public Map select(UserVo userVo) {
return userDao.select(userVo);
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
userVo.setUsername(username);
#SuppressWarnings({ "rawtypes", "unchecked" })
Map result = new HashMap(userDao.select(userVo));
List<String> userRoles = userDao.listUserRoles(userVo);
String sRoles = StringUtils.join(userRoles, ",");
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(String role : userRoles){
authorities.add(new SimpleGrantedAuthority(role));
}
userVo.setUsername(result.get("username").toString());
userVo.setPassword(result.get("password").toString());
userVo.setEnabled(result.get("enabled").toString());
userVo.setUserAuthorities(authorities);
//return new LoginVo(result.get("username").toString(), result.get("password").toString(), AuthorityUtils.commaSeparatedStringToAuthorityList(sRoles));
return userVo;
}
}
This works as expected if i return object that extends spring User class, in this case LoginVo.
For concurrency control Spring Security uses a SessionRegistry the default implementation uses a HashMap to store things. For a HashMap to work correctly you need to have a correctly implemented hashCode and equals method. If you don't (or always return a default value) it won't work correctly.
To solve simply implement a correct hashCode and equals method in your custom object.
If anyone faces this issue in spring boot ,this is what you have to add in your security config file,Apart From M.Deinum Answer
//security configuration class for implementing spring security on urls
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
//for handling user success handler
#Autowired
private CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
#Override
//this configuration is for handling user requests
protected void configure(HttpSecurity http) {
try {
http
.authorizeRequests()
.antMatchers("/orders").permitAll()
.antMatchers("/createrole").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasAuthority("admin")
.antMatchers("/agent/**").hasAuthority("agent")
.antMatchers("/distributor/**").hasAuthority("distributor")
.antMatchers("/home/**").hasAuthority("user").anyRequest()
.authenticated().and().csrf().disable().formLogin().successHandler(customizeAuthenticationSuccessHandler)
.loginPage("/login").failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout.done").deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutSuccessUrl("/login").and().exceptionHandling().accessDeniedPage("/403");
http.sessionManagement( ).maximumSessions(1). maxSessionsPreventsLogin(true);
http.sessionManagement( ).sessionFixation( ).migrateSession( )
.sessionAuthenticationStrategy( registerSessionAuthStr( ) );
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("Exception here");
}
}
//this method allows static resources to be neglected by spring security
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**","/assets/**","/fonts/**","/dis/**","/vendor1/**","/mobile/**");
}
#Bean
public SessionRegistry sessionRegistry( ) {
SessionRegistry sessionRegistry = new SessionRegistryImpl( );
return sessionRegistry;
}
#Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
}
///Very important ,you wont login again after logout if you dont include this
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
//BCryptPasswordEncoder encoder = passwordEncoder();
try {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
} catch (Exception e) {
System.out.println("Login Failed");
}
}
}
Ok, i've solved my problem by following #m-deinum answer in the comment section of my original question.
What i did is generate hashCode() and equals() by right clicking eclipse -> Source -> Generate hashCode() and equals() in both UserVo and CommonVo.
It turns out that i have to override both method above if i have my own implementation of UsersDetail interface
I'm developing a normal spring application [Configured without xml] without any boot Configurations...
i overrided my hascode and equal methods in the customer.
I not able to login after 5 or 10 attempts getting msg.[Maximum sessions of 1 for this principal exceeded]
I don't have this method, how to implement in a normal application.
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
in Console-
Session is destroyed --
Session is newly created ---
Session is destroyed ---
Session is newly created --
Session is destroyed --
Session is newly created--
Session is newly created ----
Info Anonymous User.....................