SpringSecurity form login using UserDetailsService

2019-08-15 04:38发布

问题:

I have got stuck at creating usefull form login in SpringMVC using Spring Security. I'm quite newbie in this and also Hibernate. I would like to create simple form login which could provide access to my web application.

I have created my project using SpringSource Tool Suite and selecting Spring Template Project. It uses Maven and I have also generated by Hibernate classes with annotations and hibernate.cfg.xml. In my database (HSQLDB) I have three tables: users, roles and users_roles. The third one contains user_id and role_id, so it stores information about users' roles. I have generated class by Hibernate successful.

I have started writing my class implementing UserDetailsService. But I don't know how to do this properly. In spring-security.xml I have defined bean like this:

<bean id="userDetailsService" class="hutter.pl.services.HutterUserDetailsService" />

I would like to use hashing by sha-256 with saltSource.

<bean class="org.springframework.security.authentication.dao.ReflectionSaltSource" id="saltSource">
    <property name="userPropertyToUse" value="username"/>
</bean>

<security:authentication-manager>   
    <security:authentication-provider user-service-ref="userDetailsService">
        <security:password-encoder hash="sha-256">
            <security:salt-source ref="saltSource" />
        </security:password-encoder> 
    </security:authentication-provider>
</security:authentication-manager>

Should I have used this solution: https://stackoverflow.com/a/1654488/845220 ? Hibernate have generted classes like: RolesHome, Roles, Users, UsersHome, UsersRoles, UsersRolesHome. But i really don't know how to use these Hibernates classes to authorize users.

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {   
        UsersHome usersHome = new UsersHome();
       //Users user = ...       
       //...            
       return null;     
    }   
}

Could you give me some hints?

EDIT: I have tried to add method public Users findByLogin(String login) to the UsersHome class.

   public Users findByLogin(String login) {
    log.debug("getting Users instance with login: " + login);
    try {
        Users instance = entityManager.find(Users.class, login);
        log.debug("get successful");
        return instance;
    } catch (RuntimeException re) {
        log.error("get failed", re);
        throw re;
    }
}

And body of my UserDetailsService looks like:

UsersHome usersHome = new UsersHome();
Users user = usersHome.findByLogin(username);

But I have got excpetion:

 ERROR: my.package.dao.UsersHome - get failed
 java.lang.NullPointerException
at my.package.dao.UsersHome.findByLogin(UsersHome.java:72)
at my.package.services.HutterUserDetailsService.loadUserByUsername(MyUserDetailsService.java:19)

回答1:

I think you don't need to implement an UserService yourself. You can use a jdbc-user-service with a datasource:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
  <property name="username" value="root" />
  <property name="password" value="password" />
</bean>

<authentication-manager>
  <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query="select username,password, enabled from users where username=?"
      authorities-by-username-query="select u.username, ur.authority from users u, user_roles ur  where u.user_id = ur.user_id and u.username =?" 
    />
  </authentication-provider>
</authentication-manager>

With the properties users-by-username-query and users-by-username-query you can define the queries spring security should use to receive the users and authorities from the datasource.

Implementing your own UserService is necessary if

  • you want to return customized UserDetails objects (which you can access later via the SecurityContextHolder)
  • the receival of the User objects and authorities is too complex and/or cannot be defined with easy queries at the jdbc-user-service

A possible implementation of UserDetailsService could look like this:

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

  @PersistenceContext
  private EntityManager entityManager;

  @Transactional(readOnly = true)
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {   

    // this works only if username is the primary key of user
    // if thats not the case you have to create a query object to receive the user by username
    User user = entityManager.find(User.class, username); 

    List<GrantedAuthority> roles = .... // get roles for user, depends on your table structure

    if (user == null) {
      // user not found
      throw new UsernameNotFoundException();
    }
    return new MyUserDetails(user, roles);
  }

  private static class MyUserDetails implements UserDetails {
    private User user;
    private List<Role> roles;

    public MyUserDetails(Usere user, List<GrantedAuthority> roles) {
      this.user = user;
      this.roles = roles;
    }

    public Collection<GrantedAuthority> getAuthorities() {
      return roles;
    }

    public String getPassword() {
      return user.getPassword();
    }

    public String getUsername() {
      return user.getUsername();
    }

    // return true for the missing boolean methods..
  }
}

(syntax is unchecked)

For first testing it can help to disable the password encoder and store the password unencrypted in the database. This avoids the problem that your authentication does not work because of wrongly configured PasswordEncoders. Once you have your userservice running you can then add a PasswordEncoder again and store the hashed password in the database.

Hope it helps :-)