This article is a sub-chapter of the personal development framework for SpringBoot2.1. Please read the personal development framework for SpringBoot2.1

Back-end project address: personal application development framework for SpringBoot2.1

Front-end project address: yWH-vuE-admin

Reference:

  • Spring Security is a beginner to advanced series of tutorials
  • Detailed configuration of Spring Security

In the last article we have a preliminary understanding of Spring Security, we will mainly implement the database query user to authenticate whether the user has logged in.

Design of database tables

Reference:

  • RBAC Authority Management this article is very detailed, but a little long ago, 12 years I was in senior one…

Before we do user login, we need to design the database table, RBAC (role-based Access Control), that is, users are associated with permissions through roles. Simply put, a user has several roles, and each role has several permissions. In this way, a user – role – permission authorization model is constructed. In this model, the relationship between users and roles, roles and permissions is generally many-to-many.

After the table structure is determined, we set up the entity, DAO, Service and THE XML corresponding to dao of the system user in the project. I will not show the code here. We can take the previous automatic generation of MybatisPlus and review the previous one by ourselves. After adding, you can now verify in the test class that there is no error before proceeding to the next step.

The security core classes

In the last note we know that one of the core classes of security WebSecurityConfigurerAdapter, we can create your own user in the configuration class, which request release, what certification request, in this note we want to achieve from the database query users, So we’ll implement the following core classes.

  • Populated UserDetails interface
  • UserDetailsService interface

Populated UserDetails interface

The UserDetails interface is an extensible user information interface provided by Security. It stores the information of some non-sensitive classes. This interface class is implemented in the Entity package under the Security module, which is actually an entity class.

package com.ywh.security.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

/** * CreateTime: 2019-01-24 16:10 * ClassName: SecurityUserDetails * Package: com.ywh.security.entity * Describe: * User details for Security * *@author YWH
 */
public class SecurityUserDetails implements UserDetails {


    /** * User password */
    private String password;

    /** * User name */
    private String username;

    /** * User status. 1 indicates a valid user and 0 indicates an invalid user */
    private Integer state;

    /** * The user's permission, can put the user's role information set first, role represents the permission */
    private Collection<? extends GrantedAuthority> authorties;


    public SecurityUserDetails(String password, String username, Integer state, Collection<? extends GrantedAuthority> authorties) {
        this.password = password;
        this.username = username;
        this.state = state;
        this.authorties = authorties;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorties;
    }

    @Override
    public String getPassword(a) {
        return password;
    }

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

    /** * indicates whether the user's account has expired. Expired accounts cannot be authenticated. *@returnTrue if the user's account is valid (that is, not expired), false if it is no longer valid (that is, expired) */
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired(a) {
        return true;
    }

    /** * indicates whether the user is locked or unlocked. The locked user cannot be authenticated. *@returnTrue: unlocked; false: locked */
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked(a) {
        return true;
    }

    /** * indicates whether the user's credentials (password) have expired. Expired credentials prevent authentication *@returnTrue if the user's credentials are valid (that is, not expired), false if they are no longer valid (that is, expired) */
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired(a) {
        return true;
    }

    /** * indicates whether the user is enabled or disabled. Disabled users cannot be authenticated. *@returnThe true user is enabled. The false user is disabled. */
    @JsonIgnore
    @Override
    public boolean isEnabled(a) {
        return state == 1; }}Copy the code

UserDetailsService interface

This interface simply overwrites a method, loadUserByUsername, that queries the user’s details by user name and puts it in a subclass of our UserDetails implementation, stored in the SecurityContextHolder of Security. In the last article we used this to get user information.

This interface is implemented in the Service/IMPl of the Security module. The selectByUserName method is a normal DAO interface. The SQL statement is defined in the mapper. XML file, but I won’t post it because it is very simple

package com.ywh.security.service.impl;

/** * CreateTime: 2019-01-25 16:39 * ClassName: SecurityUserDetailsServiceImpl * Package: Com. Ywh. Security. Service. Impl * the Describe: this implementation class * * UserDetailService@PrimaryIndicates that the interface inherited from this class has multiple implementation classes, which are preferred when it is not known which to introduce@PrimaryAnnotated class *@author YWH
 */
@Primary
@Service
public class SecurityUserDetailsServiceImpl implements UserDetailsService {

	// User query interface
    private SysUserDao sysUserDao;

    @Autowired
    public SecurityUserDetailsServiceImpl(SysUserDao sysUserDao) {
        this.sysUserDao = sysUserDao;
    }

	@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserEntity sysUserEntity = sysUserDao.selectByUserName(username);
        if(sysUserEntity ! =null) {// Stream A new feature in Java8, Stream provides a high-level abstraction of Java collection operations and expressions in an intuitive way similar to querying data from a database with SQL statements.
            / / reference http://www.runoob.com/java/java8-streams.html
            List<SimpleGrantedAuthority> collect = sysUserEntity.getRoles().stream().map(SysRoleEntity::getSysRoleName)
                    .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            return newSecurityUserDetails(sysUserEntity.getSysUserPassword(),sysUserEntity.getSysUserName(),sysUserEntity.getSysUserState(),co llect); }throw MyExceptionUtil.mxe(String.format("'%s'. This user does not exist", username)); }}Copy the code

Modify WebSecurityConfigurerAdapter implementation class

Of such in the last, we rewrite, we know that the user configuration is rewriting the configure (AuthenticationManagerBuilder auth) method, we are through the memory manager to create the user, this time we implemented UserDetailsService interface, We can use this to query the user and use it. Specific modifications are as follows:

package com.ywh.security.config;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

	private UserDetailsService userDetailsService;


    @Autowired
    public SecurityConfigurer(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
    
    /** * User information configuration *@paramAuth User information manager *@throwsException Exception information */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Before modifying
// auth
// .inMemoryAuthentication()
// .withUser("root")
// .password("root")
// .roles("user")
// .and()
// .passwordEncoder(CharEncoder.getINSTANCE());

// After modification
        auth
                .userDetailsService(this.userDetailsService)
                .passwordEncoder(passwordEncoder());

    }
    
	/** * Password encryption *@returnReturn the encrypted password */
    @Bean
    public PasswordEncoder passwordEncoder(a) {
        return newBCryptPasswordEncoder(); }}Copy the code

After restarting the project, we can see the effect as follows: the user ywh is in the database, and the password of the user is encrypted by the PasswordEncoder I manually called and then put into the database. The password is 123.

@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void getOneTest(a){
    System.out.println("password:" + passwordEncoder.encode("123"));
}
Copy the code

Custom login

See Spring Security for customizing user login authentication

After this step, we have finished querying the user login from the database, but the login page is the default page given by Security.

To use our custom page, Security continues to authenticate us, creating our custom login.html login page after creating a public directory in the Resource file in the core module


      
<html>
<head>
    <title>Landing page</title>
</head>

<body>
<h2>Login form</h2>
<form action="/core/login" method="post">
    <table>
        <tr>
            <td>User name:</td>
            <td>
                <input type="text" placeholder="Username" name="username" required="required" />
            </td>
        </tr>
        <tr>
            <td>Password:</td>
            <td>
                <input type="password" placeholder="Password" name="password" required="required" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">The login</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>
Copy the code

Notice that the action property in the form has the same value as the loginProcessingUrl(“/login”) in the Security configuration class. I wrote /core/login because I configured context-path, Don’t write the /core prefix if you haven’t configured it

The access index. The HTML


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>Logged in!!</body>
</html>
Copy the code

Modify the configure(HttpSecurity HttpSecurity) method of the Security configuration class

	/** * Configure how to protect our requests through interceptors, what is allowed and what is not, and allow configuration for specific HTTP requests based on security considerations *@param httpSecurity http
     * @throwsThe Exception Exception * /
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // Temporarily ban CSRC otherwise cannot be submitted
                .csrf().disable()
                If the second user logs in, the first user is kicked out and redirected to the login page
                .sessionManagement().maximumSessions(1).expiredUrl("/login.html");
        httpSecurity
                // Start authentication
                .authorizeRequests()
                // Allow static files and login pages
                .antMatchers("/static/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/login.html").permitAll()
                // Other requests require authentication login
                .anyRequest().authenticated();
        httpSecurity
                // Form login
                .formLogin()
                // Set the login page to jump to
                .loginPage("/login.html")
                // .failureUrl("/auth/login? Error ") sets which page to jump to if an error occurs
                // Security is the default login path authentication, if you want to use custom change can be used
                .loginProcessingUrl("/login")
                // If you access the login page directly, you will be redirected to this page after successful login. Otherwise, you will be redirected to the desired page
                .defaultSuccessUrl("/index.html");
    }
Copy the code

You can also enter an interface path in loginPage() to return to the loginPage. Through the above code we have implemented our own defined login page, security will intercept and authenticate for us, just create two pages and add two attributes.

Want to implement login successful custom logic, you can configure the successHandler () method, in order to realize the interface for AuthenticationSuccessHandler, rewrite the method for onAuthenticationSuccess ().

Custom exit

Want to implement custom exit function logic, you need to implement AuthenticationFailureHandler interface, rewrite the onAuthenticationFailure method. Or write anonymous inner classes in methods in the configuration.

Logout can be done by ‘/logout’, and an A tag can be added to the index.html interface.

<a href="/core/logout">exit</a>
Copy the code

Logout: context-path (‘ /core ‘); logout: context-path (‘ /core ‘); logout: context-path (‘ /core ‘)

  • Disable the HttpSession
  • Clean up remember passwords
  • Clean up the SecurityContextHolder
  • Redirect/login? logout

Of course we can also customize the exit by adding the following to configure(HttpSecurity HttpSecurity) method. Here I have implemented only the simplest operation

httpSecurity
                / / logout
                .logout()
                // logout using the default logout function of security. You can also customize the logout path
                .logoutUrl("/logout")
                // Which page to jump to after successful logout
                .logoutSuccessUrl("/login.html")
                .logoutSuccessHandler((request, response, authentication) -> {
                    // Logout success handler
                    System.out.println("logout success");
                    response.sendRedirect("/core/login.html");
                })
                .addLogoutHandler((request, response, authentication) ->{
                    // Logout handler
                    System.out.println("logout------");
                })
                / / clean up the Session
                .invalidateHttpSession(true);
Copy the code

As for security, we have realized the most basic functions. It can also do more things, such as remembering my password, third party login and so on.