Spring Security+Spring Data Jpa, Security management is not simple, only simpler!

I am busy updating OAuth2 this week, and Spring Security also found time to publish an article.

This article is the seventh in six new articles in the Spring Security series. It is recommended to read the previous articles in this series to get a better understanding of this article:

  1. Dig a big hole and Spring Security will do it!
  2. How to decrypt the password
  3. A step-by-step guide to customizing form logins in Spring Security
  4. Spring Security does front and back separation, so don’t do page jumps! All JSON interactions
  5. Authorization in Spring Security used to be so simple
  6. How does Spring Security store user data into the database?

Although the previous six articles, but our user data are stored in memory, in the sixth article, although the introduction of JdbcUserDetailsManager, but it is still inconvenient to use, so today I will adopt a more flexible definition.

It is up to us to define the authorization database model.

To keep things simple, I’ll introduce the Spring Data Jpa to help with database operations. However, I will not focus on the usage of Spring Data Jpa in this article. If you are not familiar with the operation of Spring Data Jpa, you can reply to SpringBoot in the background of the public account to get the Spring Boot tutorial of Songgo’s hand. You can also check out the Spring Boot + Vue series of video tutorials recorded by Songo

1. Create a project

First we create a new Spring Boot project and add the following dependencies:

Note that in addition to Spring Security dependencies, we also need Data dependencies and Spring Data Jpa dependencies.

After the project is created, we create an empty library in the database, called WithjPA, where we don’t have to do anything, and we’re done.

2. Prepare the model

Next we create two entity classes representing user roles:

User Roles:

@Entity(name = "t_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String nameZh;
    / / omit getter/setter
}
Copy the code

This Entity class is used to describe user role information, including role ID and role name (English and Chinese). @entity indicates that this is an Entity class. After the project is started, a role table will be automatically created in the database according to the attributes of the Entity class.

User entity class:

@Entity(name = "t_user")
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;
    @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
    private List<Role> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    @Override
    public String getPassword(a) {
        return password;
    }

    @Override
    public String getUsername(a) {
        return username;
    }

    @Override
    public boolean isAccountNonExpired(a) {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked(a) {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired(a) {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled(a) {
        return enabled;
    }
    // omit other get/set methods
}
Copy the code

The user entity class mainly needs to implement the UserDetails interface and implement the methods in the interface.

The fields here are basically easy to understand, but I’ll talk about a few special ones:

  1. The attributes accountNonExpired, accountNonLocked, credentialsNonExpired, and Enabled describe the user status. Indicates whether the account is not expired, whether the account is not locked, whether the password is not expired, and whether the account is available.
  2. The roles attribute represents the User’s Role, and users and roles are many-to-many relationships described by an @manytomany annotation.
  3. Orities’ getAuthorities approach returns information about the user’s Role, and in this approach we simply transform our own Role a little.

Configuration of 3.

Once the data model is ready, let’s define a UserDao:

public interface UserDao extends JpaRepository<User.Long> {
    User findUserByUsername(String username);
}
Copy the code

The thing here is very simple, we just need to inherit JpaRepository and provide a method to query the user based on username. If you are not familiar with the operation of Spring Data Jpa, you can reply to springBoot on the background of the official account to get the Spring Boot tutorial, which has the related operation of Jpa, and you can also see the video tutorial recorded by Songgo: Spring Boot + Vue series video tutorials.

Next, define UserService as follows:

@Service
public class UserService implements UserDetailsService {
    @Autowired
    UserDao userDao;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.findUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User does not exist");
        }
        returnuser; }}Copy the code

The UserService defined by ourselves needs to implement the UserDetailsService interface. To realize this interface, we need to implement the method in the interface, that is, loadUserByUsername. The parameter of this method is the user name passed in when the user logs in. Query the user information according to the user name (after finding out, the system will automatically compare passwords).

After the configuration is complete, we will do a little configuration in Spring Security. I will use the HelloController for Spring Security and the test as described in the previous article. , mainly list the areas that need to be modified.

In SecurityConfig, we configure the user as follows:

@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService);
}
Copy the code

Note that we are going to rewrite the configure method again, only this time not based on memory or JdbcUserDetailsManager, but using a custom UserService.

In the application. Properties directory, configure the database and JPA information as follows:

spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///withjpa? useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

spring.jpa.database=mysql
spring.jpa.database-platform=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
Copy the code

These are all general configurations, so we won’t repeat them.

After this combination, our Spring Security is connected to the database. Next, we will test the HelloController. Refer to the previous article.

4. Test

First let’s add two test data. Add the following method to the unit test:

@Autowired
UserDao userDao;
@Test
void contextLoads(a) {
    User u1 = new User();
    u1.setUsername("javaboy");
    u1.setPassword("123");
    u1.setAccountNonExpired(true);
    u1.setAccountNonLocked(true);
    u1.setCredentialsNonExpired(true);
    u1.setEnabled(true);
    List<Role> rs1 = new ArrayList<>();
    Role r1 = new Role();
    r1.setName("ROLE_admin");
    r1.setNameZh("Administrator");
    rs1.add(r1);
    u1.setRoles(rs1);
    userDao.save(u1);
    User u2 = new User();
    u2.setUsername("A little Rain in the South");
    u2.setPassword("123");
    u2.setAccountNonExpired(true);
    u2.setAccountNonLocked(true);
    u2.setCredentialsNonExpired(true);
    u2.setEnabled(true);
    List<Role> rs2 = new ArrayList<>();
    Role r2 = new Role();
    r2.setName("ROLE_user");
    r2.setNameZh("Ordinary user");
    rs2.add(r2);
    u2.setRoles(rs2);
    userDao.save(u2);
}
Copy the code

After running this method, we see three more tables in the database:

This is created automatically from our entity class.

Let’s look at the data in the table.

The users table:

Character sheet:

User and role association table:

Now that we have the data, let’s start the project, and let’s test it.

We first log in as Jiangnan Little Rain:

After successful login, access /hello, /admin/hello, and /user/hello respectively.

  1. /helloBecause you can access it after logging in, the interface is successfully accessed.
  2. /admin/helloAdmin is required, so access failed.
  3. /user/helloUser identity is required, so access succeeded.

You can refer to songko’s video for specific test effects. I won’t take screenshots.

During the test, if the enabled attribute of the user is set to False in the database, the user account is disabled. In this case, the login fails.

You can also test Javaboy users in the same way.

All right, that’s all for today.

Thank you for your reading, if you feel useful, do not hesitate to click on it and encourage Songko, I will finish this series with the speed of lightning