SpringBoot+Vue SpringSecurity Login and authorization

Tools: IDEa2018, SpringBoot 2.1.4, SpringSecurity 5.1.5

Introduction to the

SpringSecurity is a security framework under Spring. Similar to Shiro, It is generally used for user Authentication and user Authorization. It is often integrated with SpringBoot.

Development steps

For ease of understanding, the next section uses the front and back end separation again and introduces database user and role information

Test the login

1 Importing Dependencies

(pom. XML)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
Copy the code

2 Write test methods

(controller. UserController)

@Controller
public class UserController {

    @GetMapping("/hello")
    @ResponseBody
    public String hello(a) {
        return "hello controller"; }}Copy the code

3 test

Start the project, the browser access: localhost: 8080 / hello, address bar automatically jump to http://localhost:8080/login, enter the default landing page, verify login

Username defaults to user,Password is generated randomly (UUID), check console.

Spring Security intercepts URL access by default and provides an authenticated login page

Enter the password, my current is C1068CDB-18F3-48F4-B838-7698218D14C4. Login successful

The user name and password can be changed directly in the configuration file, for example

(application. The properties)

spring.security.user.name=admin
spring.security.user.password=123
Copy the code

Cut to the source

1> User parameters

Refer to the source code for static inner classes. As you can see, the default user password is actually a UUID.

(SpringSecurity — securityProperties.java)

@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {...// Default user
    private User user = newUser(); .public static class User {
       // Default user name
        private String name = "user";

        // Default user name Default password, randomly generated
        private String password = UUID.randomUUID().toString();

        // The role of the default username
        private List<String> roles = new ArrayList<>();

        // Whether to generate a password
        private boolean passwordGenerated = true; . }}Copy the code

2> Verify the user name and password

  • After the security dependency is imported, the default access path will pass through the filter and access its no-parameter construction to create a new post login request with the path /login.

  • The default login page is displayed

  • Get the username and password from the login form through the HttpServletRequest object

  • Create a token object for the username and password

  • Process login form information

(SpringSecurity – UsernamePasswordAuthenticationFilter. Java)

// @since spring security 3.0
public class UsernamePasswordAuthenticationFilter extends
      AbstractAuthenticationProcessingFilter {

   public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
   public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

   private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
   private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
   private boolean postOnly = true;

    // constructor to create matchers for post and HTTP methods in a case-insensitive manner.
   public UsernamePasswordAuthenticationFilter(a) {
      super(new AntPathRequestMatcher("/login"."POST"));
   }

   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      if(postOnly && ! request.getMethod().equals("POST")) {
         throw new AuthenticationServiceException(
               "Authentication method not supported: " + request.getMethod());
      }
		
      // Get the user name and password from the request path
      String username = obtainUsername(request);
      String password = obtainPassword(request);

       // Null judgment
      if (username == null) {
         username = "";
      }

      if (password == null) {
         password = "";
      }

       // Remove Spaces between the beginning and end of the user name
      username = username.trim();

       // Generate a token for username and password authentication
      UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            username, password);

      // Sets the authentication request information
      setDetails(request, authRequest);

       // Return a fully authenticated object, including credentials
      return this.getAuthenticationManager().authenticate(authRequest); }...protected String obtainUsername(HttpServletRequest request) {
		returnrequest.getParameter(usernameParameter); }}Copy the code

Custom login interface

(For ease of explanation, database information validation is not introduced)

1 Implementation interface

Implement the UserDetailsService interface and rewrite the method.

(service. MyUserDetailsSerice)

/** * Custom login interface (core interface, load user specific data.) * /
@Component
public class MyUserDetailsSerice implements UserDetailsService {
    // Log returns the logger corresponding to the class passed as an argument
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);


    /** * Verify that user * is located based on the user name@paramUsername Indicates the name of the user whose data is required. *@returnCore user information, a fully populated user record *@throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("Login, username: {}", username);
        return new User(username, "123", AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); }}Copy the code

2 Configure login blocking

Inheritance WebSecurityConfigurerAdapter configuration class, rewrite the configuration method

You can view springBoot on the official website or view the comments of the EnableWebSecurity interface

(config. MySecurityConfig)

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
        // Basic configuration
        http.httpBasic()
                .and()
                // Identity authentication
                .authorizeRequests()
                // All requests
                .anyRequest()
                // Identity authentication
                .authenticated();
    }        
Copy the code

The returned User implements the UserDetail interface, as described in the source code

3 test

Start the project, clear the browser cache, access Hello, jump to the default login page, verify the password. For login, the user name and password must be any, and the password must be 123 (configured in MyUserDetailsSerice).

Logon failed, console prints, no target for ID “null” PasswordEncoder

4 Add the cipher encoder component

Inherit the PassawordEncoder interface

/** * The implementation class of the service interface used to encode the password. * /
@Component
public class MyPasswordEncoder implements PasswordEncoder {

    /** * encode the original password. In general, good encoding algorithms apply sha-1 or larger hashes combined with 8 bytes or larger randomly generated salt. *@paramRawPassword password, a readable sequence of character values *@return* /
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /** * Verify that the encoded password obtained from the store matches the original password submitted. Return true if the passwords match; If there is no match, return false. The stored password itself is never decoded. *@paramRawPassword Specifies the preset authentication password. The original password to encode and match *@paramEncodedPassword Specifies the password entered in the form. Compared to encoded ciphers from storage *@return* /
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        returnencodedPassword.equals(rawPassword.toString()); }}Copy the code

4 test

Restart the project, clear the browser cache, and access Hello.

Cut to the source

1 about WebSecurityConfigurerAdapter interface EnableWebSecurity for reference

(SpringSecurity — EnableWebSecurity)

/**
 * Add this annotation to an {@code @Configuration} class to have the Spring Security * ............. * & # 064; Override * protected void configure(HttpSecurity http) throws Exception { * http.authorizeRequests().antMatchers(&quot; /public/**&quot;) .permitAll().anyRequest() * .hasRole(&quot; USER&quot;) .and() * // More configuration... *.formlogin () // make sure the base formLogin * // set the license *.permitall () for all urls associated with the formLogin; *} * *................... *@since3.2 * /.@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

	// Disable the debug mode by default
	boolean debug(a) default false;
}

Copy the code

Information about the default User encapsulated in 2 Security (SpringSecurity — user.java)

// 
public class User implements UserDetails.CredentialsContainer{...private String password;
	private final String username;
    // Set of user permissions
	private final Set<GrantedAuthority> authorities;
    // The account has not expired
	private final boolean accountNonExpired;
    // The account is not locked
	private final boolean accountNonLocked;
    // The credentials have not expired
	private final boolean credentialsNonExpired;
    // The user is available
	private final booleanenabled; . }Copy the code

Password encryption

1 Inject the cipher encoder object

Inheritance WebSecurityConfigurerAdapter configuration class

Inject a BCryptPasswordEncoder object directly into MySecurity. It implements the PasswordEncoder interface and overwrites the encode and matches methods

(config.MySecurityConfig.java)

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    /** * implement cipher encoder using BCrypt strong hash function. The client can optionally provide "strength" (that is, the number of logging rounds in BCrypt) and SecureRandom instances. * The greater the strength parameter, the more work (exponentially) you need to do to hash the password. The default value is 10. *@return* /
    @Bean
    public PasswordEncoder passwordEncoder(a) {
        return newBCryptPasswordEncoder(); }... }Copy the code

2 improve the service layer

Perfect MyUserDetailsSerice

(service. MyUserDetailsSerice. Java)

@Component
public class MyUserDetailsSerice implements UserDetailsService {...@Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123");
        logger.info("Login, username: {}, password: {}", username,password);
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); }}Copy the code

3 test

Comment out the @Component annotation of the MyPasswordEncoder so that it loses its container component identity

In debug mode, start the project and access Hello.

Debug displays the conversion of the original password 123 to $2aYGYb9i0ZjnTHPlOk/NQb/efrPNOaJq8hJYtdXf8VcdQUi8T8S3Iim

Console Logs

Cut to the source

As you can see, we’re automatically injecting the BCryptPasswordEncoder object and calling the encode method

(SpringSecurity — BCryptPasswordEncoder)

/ / the constructor
public BCryptPasswordEncoder(a) {
	this(-1);
}
public BCryptPasswordEncoder(int strength) {... }public BCryptPasswordEncoder(int strength, SecureRandom random) {... }...public String encode(CharSequence rawPassword) {
    / / salt value
   String salt;
    // Check whether the constructor has parameters
   if (strength > 0) {
      if(random ! =null) {
          // Salt generated by random and Strength
         salt = BCrypt.gensalt(strength, random);
      }
      else {
           // Salt generated by strengthsalt = BCrypt.gensalt(strength); }}// No arguments
   else {
       / / call gensalt (GENSALT_DEFAULT_LOG2_ROUNDS); Randomly generated salt
       // GENSALT_DEFAULT_LOG2_ROUNDS = 10
      salt = BCrypt.gensalt();
   }
    // Use OpenBSD bcrypt to hash the password with the original password and salt value respectively
   return BCrypt.hashpw(rawPassword.toString(), salt);
}
Copy the code
  • Here the BCryptPasswordEncoder uses no arguments, uses the default salt value, loops 10 times, and generates the hashed password.

  • Spring Security generates a random salt when encrypting the password, and the final encrypted password = password + random salt.

  • Notice the AuthorityUtils method here, and the parameter contains role information. In actual services, ROLE_** is used to specify the role field of a user and grant corresponding permissions after login

/** * Create an array of GrantedAuthority objects (for example, "ROLE_A, ROLE_B, ROLE_C") from a comma-separated string@paramAuthorityString A comma-separated string *@returnThrough the tag string create permissions/AuthorityUtils.com maSeparatedStringToAuthorityList (" admin ")Copy the code

Custom login request

Do not use the default login interface provided by springsecurity

1 Customize the front-end login page

(the template. The login. HTML)


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The login</title>
</head>
<body>
<h2>Welcome to login</h2>
<form action="/auth/login" method="post">
    <input name="username" type="text" placeholder="Please enter user name.."><br/>
    <input name="password" type="password" placeholder="Please enter your password..."><br/>
    <input type="submit" value="Login">
</form>
</body>
</html>
Copy the code

2 Customize the home page

(template.index.html)


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The MAIN page</title>
</head>
<body>
<h1>Welcome to the home page</h1>
</body>
</html>
Copy the code

3 Add a forward path to the controller class

@Controller
public class UserController {

    // Login test.// Login page, go to the /templates/login. HTML page
    @GetMapping("/login")
    public String login(a) {
        return "login";
    }

    Go to the /templates/index.html page
    @GetMapping("/index")
    public String index(a) {
        return "index"; }}Copy the code

4 Modify the interception configuration

Modify the configure method in MySecurityConfig

(config.MySecurityConfig.java)

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {...@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                // Form authentication
                .formLogin()
                / / login page
                .loginPage("/login")
                // Login form submission address
                .loginProcessingUrl("/auth/login")
                .and()
                // Authentication request
                .authorizeRequests()
                // The URL path matches
                .antMatchers("/login").permitAll()
                // Any request
                .anyRequest()
                // Identity authentication.authenticated(); }}Copy the code

LoginProcessingUrl (“/ Auth /login”) defines the form submission address, but there is no request path in UserController, SpringSecutity intercepts all requests by default, Redirect URL 302 to the /login default login page and use the default user name and password to login.

Custom login request status

Method 1: Inherit the interface implementation

1 User-defined login success class

(handler.MyAuthenticationSuccessHandler.java)

/** * inherits the interface for handling successful user authentication policies */
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);

    // Provides the ability to read and write JSON, interact with basic POJO classes, interact with generic JSON tree models, and perform transformations.
    @Autowired
    private ObjectMapper objectMapper;

    // Called when the user has successfully authenticated.
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("Login successful");
        response.setContentType("application/json; charset=utf-8");
        // writeValueAsString: Serializes a Java object into a stringresponse.getWriter().write(objectMapper.writeValueAsString(authentication)); }}Copy the code

2 Customize login failure classes

(handler.MyAuthenticationFailureHandler.java)

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        logger.info("Login failed");
        // HTTP status, 200, success
        response.setStatus(HttpStatus.OK.value());
        response.setContentType("application/json; charset=utf-8"); response.getWriter().write(objectMapper.writeValueAsString(exception)); }}Copy the code

Method 2: Modify the configuration method of MySecurityConfig

1 Add methods for handling successful login and failed login

(config.MySecurityConfig.java)

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()        
        .formLogin()
        .loginPage("/login")
        .loginProcessingUrl("/auth/login")
        // Log in to the successful handler
        .successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                response.setContentType("application/json; charset=utf-8");
                PrintWriter writer = response.getWriter();
                ObjectMapper om = newObjectMapper(); String successMsg = om.writeValueAsString(om.writeValueAsString(authentication)); writer.write(successMsg); writer.flush(); writer.close(); }})// Failed to log in processor
        .failureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                resp.setContentType("application/json; charset=utf-8");
                PrintWriter writer = resp.getWriter();
                writer.write(new ObjectMapper().writeValueAsString(e));
                writer.flush();
                writer.close();
            }
        })
        .and()
        .authorizeRequests()
        .antMatchers("/login").permitAll()
        .anyRequest()        
        .authenticated();

}
Copy the code

Get the current user information

(controller. UserController. Java)

@Controller
public class UserController {...// Current user information
    @GetMapping("/info")
    @ResponseBody
    public Object getCurrentUser(Authentication authentication) {
        returnauthentication; }}Copy the code

test

Start project, access /info, login successful, check F12