13. User Login – Preparation

When developing the registration function, configure the following code in the SecurityConfig class:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
}
Copy the code

The above code is used to turn off cross-domain attacks. If you do not have the above code, an error will occur when executing asynchronous requests!

Once the above code is added, Spring Security’s login interception will not take effect until more detailed configuration is added! For the sake of developing the login function, remove the above code (delete it, or add it as a comment) for now.

Also, in SecurityConfig there is:

@Bean
public PasswordEncoder passwordEncoder(a) {
    return new BCryptPasswordEncoder();
}
Copy the code

All this code does is create a password encryptor object and hand it over to the Spring container for management, so that when you need to perform password encryption, you can automatically assemble the password encryptor!

At present, in order to ensure the correct login, it is necessary to remove the above password encryption, because, after the development of the registration function, the user’s password after successful registration has been stored in the database in the form of ciphertext, and add {bcrypt} prefix used to declare the encryption algorithm. Spring Security will automatically use the PasswordEncoder installed in the above code to encrypt once more, and then perform another encryption because of the {bcrypt} prefix, which will cause login authentication to fail!

Use the ${bcrypt} prefix for ciphertext, or let the Spring container manage the BcryptPasswordEncoder.

Once you remove the code above, there will be no PasswordEncoder object in the Spring container, but if you need to use it in UserServiceImpl, you should set it to a self-created mode, that is:

// @autowired // Need to remove autowiring annotations
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
Copy the code

14. User Login – Simulated login based on memory authentication

Remove the Spring Security username and password configured in application.properties.

And then, In SecurityConfig class (inherited from WebSecurityConfigurerAdapter configuration class) rewritten in protected void the configure (AuthenticationManagerBuilder auth) method, In this method, configure the user name, password, and permission of the account:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("java")
            .password("{bcrypt}$2a$10$tsM03ULkiifEpSCWtQ5Mq.yrLZIPKVr5vHwU1FGjtT9B1vPlswa.C")
            .authorities("/test");
}
Copy the code

The original text of the ciphertext is 1234.

Note: When configuring the above code, you must call authorities() to configure the authorization scope. If not, it will fail to start. Since the authorities required for each request have not yet been configured, the scope can be used as an arbitrary string for the time being.

15. User Login -UserDetailsService interface

Spring Security defines the UserDetailsService interface, in which there are abstract methods:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
Copy the code

The function of this method is: Given the user name, you need to return the UserDetails (an object of type UserDetails). After Spring Security obtains the UserDetails, it will automatically complete the authentication of the user’s identity, including the user permission information after successful authentication, which is handled by the framework. As a developer, Just solve the problem of “get user details by user name”!

In cn. Tedu. Straw. Portal. The security package to create UserDetailsServiceImpl class, realize the above interface, analog implementation for user data:

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Assume the correct user name is security
        // Assume the user name is the correct value: security
        if ("security".equals(username)) {
            // Build the UserDetails object using the User class provided by Spring-Security
            UserDetails userDetails = User.builder()
                    .username("security")
                    .password("{bcrypt}$2a$10$tsM03ULkiifEpSCWtQ5Mq.yrLZIPKVr5vHwU1FGjtT9B1vPlswa.C")
                    .authorities("test")
                    .build();
            return userDetails;
        }
        return null; }}Copy the code

Note: The above classes must be in the package scanned by the Component, and add the @Component annotation. Then the Spring framework automatically creates and manages the objects of the above classes, and can then directly assemble the objects of this class.

Then, go back to the SecurityConfig class and apply objects of the above class:

@Autowired
UserDetailsServiceImpl userDetailsService;

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

Note: The preceding global attribute is declared as the UserDetailsServiceImpl type. It cannot be specified as the interface type, because there are more than one objects of the interface type.

16. User Login – Query database to verify login

Add an abstract method to the IUserService interface:

UserDetails login(String username);
Copy the code

The above method is not strictly a “login” method, but a “get user details” method. You do not even know whether the login was successful or not, so there is no password in the parameter list. Later, Spring Security will get the object returned by the above method and verify whether the password is correct.

Then, override the above abstract method in the UserServiceImpl implementation class:

@Override
public UserDetails login(String username) {
    // Query user information according to parameter username
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("username", username);
    User user = userMapper.selectOne(queryWrapper);
    // Check whether the query result is null, that is, whether the user exists
    // Note: Subsequent validation and the final interface are displayed by Spring-Security. Do not throw exceptions here
    if (user == null) {
        return null;
    }
    // Organize the User Details object
    / / TODO unfinished
    UserDetails userDetails = org.springframework.security.core.userdetails.User
            .builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities("test")
            .build();
    return userDetails;
}
Copy the code

Under the SRC/test/Java, after the completion of the cn. Tedu. Straw. Portal. Service. UserServiceTests write and execute unit testing:

@Test
void login(a) {
    String username = "13988139111";
    UserDetails userDetails = userService.login(username);
    log.debug("login, user details={}", userDetails);
}
Copy the code

If the test passes, you can apply the obtained UserDetails object above to the return value of UserDetailsServiceImpl:

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        returnuserService.login(username); }}Copy the code

17. User Login – About access control (equivalent to interceptors)

Override protected void configure(HttpSecurity HTTP) in SecurityConfig:

@Override
protected void configure(HttpSecurity http) throws Exception {
    // Prepare a whitelist, which is a path that can be accessed without login
    String[] antMatchers = {
        "/index.html"
    };
    // Authorization Settings are relatively fixed configurations
    // CSRF ().disable() > disables cross-domain attacks
    // authorizeRequests() > Authorizes requests
    // antMatchers() > Configure access whitelist
    // permitAll() > Authorize paths in the whitelist
    // anyRequest() > other requests
    // authenticated() > Only authenticated access is allowed.
    // and.formlogin () > Unauthenticated logins will be authenticated and authorized through the login form
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers(antMatchers).permitAll()
            .anyRequest().authenticated()
            .and().formLogin();
}
Copy the code

About the above code:

  • The configuration of chained methods that invoke parameter object HTTP is relatively fixed and can be understood or applied directly;

  • AntMatchers () called above is equivalent to whitelisting when using SpringMVC interceptor. In the argument to the method, all paths that need to be directly released (accessible without logging in) should be added, for example:

    • String[] antMatchers = {
      	"/index.html"."/bower_components/**"."/css/**"."/img/**"."/js/**"
      };
      Copy the code

So far, the main page, index.html, is accessible without a login, while the rest of the pages are currently accessible without a login!

18. User Login – Change the custom login page

First, add the Thymeleaf dependency to the project:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Copy the code

The templates folder is created in the SRC /main/resoueces directory of the SpringBoot project. This is the templates folder used by the SpringBoot project by default. No configuration is required. By default, HTML template files are looked up in this folder when forwarding. Once the folder is created, drag and drop the login. HTML file from the static folder into the templates folder.

Next, customize the controller to design the request path for the login page, and forward the request path directly to the **/templates/login.html** file, since Thymeleaf has configured the prefix to /templates/ and the suffix to.html during integration. So the view name returned on the controller is login:

@Controller
public class SystemController {

    @GetMapping("/login.html")
    public String login(a) {
        return "login";
    }

    // This applies to @restController
    // public ModelAndView login() {
    // return new ModelAndView("login");
    // }

}
Copy the code

You then need to add the request path you designed above to the configured whitelist.

Completed, restart the project, the browser through http://localhost:8080/login.html can see custom login page.

At present, through http://localhost:8080/login.html can access to a custom login page, and, through http://localhost:8080/login also can access to the Spring Security built-in login page, that is to say, The two login pages co-exist! Spring Security should always be configured to automatically use our custom login page! You need to add the following to the SecurityConfig configuration:

package cn.tedu.straw.portal.security;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
/ /}

// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
/ /}

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // The login page URL
        String loginPageUrl = "/login.html";
        // The URL to process the login request
        String loginProcessingUrl = "/login";
        // URL after login failure
        String loginFailureUrl = "/login.html? error";
        // URL after successful login
        String loginSuccessUrl = "/index.html";
        // Exit the login URL
        String logoutUrl = "/logout";
        // The URL after the successful login
        String logoutSuccessUrl = "/login.html? logout";
        // Prepare a whitelist, which is a path that can be accessed without login
        String[] antMatchers = {
                loginPageUrl,
                "/index.html"."/bower_components/**"."/css/**"."/img/**"."/js/**"
        };
        // Authorization Settings are relatively fixed configurations
        // CSRF ().disable() > disables cross-domain attacks
        // authorizeRequests() > Authorizes requests
        // antMatchers() > Configure access whitelist
        // permitAll() > Authorize paths in the whitelist
        // anyRequest() > other requests
        // authenticated() > Only authenticated access is allowed.
        // and.formlogin () > Unauthenticated logins will be authenticated and authorized through the login formhttp.csrf().disable() .authorizeRequests() .antMatchers(antMatchers).permitAll() .anyRequest().authenticated() .and().formLogin() .loginPage(loginPageUrl) .loginProcessingUrl(loginProcessingUrl) .failureUrl(loginFailureUrl) .defaultSuccessUrl(loginSuccessUrl) .and().logout() .logoutUrl(logoutUrl) .logoutSuccessUrl(logoutSuccessUrl); }}Copy the code

19. On access control

To prepare the URL for the test:

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private IUserService userService;

    // http://localhost:8080/test/user/1
    @GetMapping("/user/{id}")
    public User getUserById(@PathVariable("id") Integer id) {
        returnuserService.getById(id); }}Copy the code

When designing the request path, you can box a name in the request path with {} to represent a variable, and then, when the client submits the request, the {} placeholder can be any data that will be matched!

When a {} placeholder is used in the request path, add the @pathVariable annotation to the parameter list of the method that handles the request before the parameter declaration to get the value of the placeholder!

Put the core parameters in the URL, which is a RESTful API.

Completed, can be access via http://localhost:8080/test/user/1.

If you want to restrict the access to the above URLS, for example, some users can access them, but others cannot, you can design a” permission string “, such as “A” or “hello”, etc. You are advised to use the URL style to define access permissions, for example, “test:user:info” or “/user/user/info”.

Note: The design of the permission string has nothing to do with the design of the URL!

You can configure the @preauthorize annotation before methods that process requests to state that “certain permissions must be granted to access this request path,” for example:

@GetMapping("/user/{id}")
@PreAuthorize("hasAuthority('test:user:info')")
public User getUserById(@PathVariable("id") Integer id) {
    return userService.getById(id);
}
Copy the code

About the above annotation configuration:

  • Note the name@PreAuthorizeRepresents “verify permissions before processing requests”;
  • In the annotation propertieshasAuthorityYou need to have certain permissions.
  • In the annotation propertiestest:user:infoIs a custom permission string, just an identifier.

Then, still need to add @ EnableGlobalMethodSecurity before SecurityConfig declaration of a class (prePostEnabled = true) annotations, to allow the execution access check! Such as:

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

    // Ignore the code in the class
    
}
Copy the code

If visit http://localhost:8080/test/user/1 again, with no rights, will lead to AccessDeniedException, and, due to the current project is used in the unified exception handling mechanism, all unknown exceptions will be treated, You can see the JSON data representing the error message.

You can try to add permissions directly so that users can access the above URLS. For example, in the business layer implementation class, when processing “Get user details”, encapsulate the matching permission string for the user details (consistent with the permission string required by the controller) :

// An array of permission strings
String[] authorities = {
    "test:user:info"
};
// Organize the User Details object
/ / TODO unfinished
UserDetails userDetails = org.springframework.security.core.userdetails.User
        .builder()
        .username(user.getUsername())
        .password(user.getPassword())
        .authorities(authorities)
        .build();
Copy the code

Check the permissions of the user based on the user ID (List ).