Spring security plays a flower! Two ways of DIY login

Time:2022-5-26

@[toc]
In general, when using spring security, we use the login scheme provided by spring security. We can configure the login interface, login parameters and login callback. This usage can be regarded as a best practice!

But!

There will always be some strange requirements. For example, if you want to customize the login, write your own login logic like Shiro. What should you do if you want to achieve this? Today, brother song will share with you.

Brother song thought about it. We have two ideas to customize the login logic in spring security, but the underlying implementation of these two ideas is actually similar. Let’s have a look.

1. Turn corruption into magic

Earlier, SongGe shared a spring security video with you:

  • A wonderful flower I haven’t seen before

This video is mainly to share with you that we can actually use HttpServletRequest to complete the system login, which is actually the specification of Java EE. Although this login method is popular, it is very fun!

Then SongGe also shared a video with you:

  • Spring security login data acquisition final lecture

This video is actually about spring security’s implementation of HttpServletRequest login logic, or in other words, spring security rewrites the login related APIs provided in HttpServletRequest according to its own implementation.

With these two reserves of knowledge, the first DIY spring security login scheme is ready to come out.

1.1 practice

Let’s take a look at the specific operation.

First, let’s create a spring boot project and introduce two dependencies: Web and security, as follows:

Spring security plays a flower! Two ways of DIY login

For convenience, we are at application Configure the following default user name and password in properties:

spring.security.user.name=javaboy
spring.security.user.password=123

Next, we provide a securityconfig to release the login interface:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }
}

The login interface is/loginLater, our customized login logic will be written in this. Let’s take a look:

@RestController
public class LoginController {
    @PostMapping("/login")
    public String login(String username, String password, HttpServletRequest req) {
        try {
            req.login(username, password);
            return "success";
        } catch (ServletException e) {
            e.printStackTrace();
        }
        return "failed";
    }
}

Directly call the httpservletrequest#login method and pass in the user name and password to complete the login operation.

Finally, we provide another test interface, as follows:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello security!";
    }
}

just this!

To start the project, we first visit/helloInterface, the access will fail. Next, let’s access/loginThe interface performs login operations as follows:

Spring security plays a flower! Two ways of DIY login

After logging in successfully, go to visit/helloInterface, and you can access it successfully.

Isn’t it easy? After successful login, subsequent authorization and other operations remain the same as the original writing.

1.2 principle analysis

The principle of the above login method was introduced by brother song at the beginning. If you are not familiar with it, you can see these two videos:

  • A wonderful flower I haven’t seen before
  • Spring security login data acquisition final lecture

Here I also want to say a few words.

The HttpServletRequest instance obtained in logincontroller#login method is actually an object of an internal class servlet3securitycontextholderawarererequestwrapper in httpservlet3requestfactory. In this class, the login and authenticate methods of HttpServletRequest are rewritten. Let’s take a look at the login method first, as follows:

@Override
public void login(String username, String password) throws ServletException {
    if (isAuthenticated()) {
        throw new ServletException("Cannot perform login for '" + username + "' already authenticated as '"
                + getRemoteUser() + "'");
    }
    AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;
    if (authManager == null) {
        HttpServlet3RequestFactory.this.logger.debug(
                "authenticationManager is null, so allowing original HttpServletRequest to handle login");
        super.login(username, password);
        return;
    }
    Authentication authentication = getAuthentication(authManager, username, password);
    SecurityContextHolder.getContext().setAuthentication(authentication);
}

You can see:

  1. If the user has been authenticated, an exception is thrown.
  2. An AuthenticationManager object was obtained.
  3. Call the getauthentication method to complete the login. In this method, the usernamepasswordauthenticationtoken object will be built according to the user name and password, and then call the authentication\authenticate method to complete the login. The specific code is as follows:
private Authentication getAuthentication(AuthenticationManager authManager, String username, String password)
        throws ServletException {
    try {
        return authManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }
    catch (AuthenticationException ex) {
        SecurityContextHolder.clearContext();
        throw new ServletException(ex.getMessage(), ex);
    }
}

This method returns an authenticated authentication object.

  1. Finally, store the authenticated authentication object into the securitycontextholder. I won’t dwell on the specific logic here. I have already talked about it many times in the video before the official account [a little rain in Jiangnan].

This is the execution logic of the login method.

The servlet3securitycontextholderawarerequestwrapper class also rewrites the httpservletrequest#authenticate method, which is also the authentication method:

@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
    AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;
    if (entryPoint == null) {
        HttpServlet3RequestFactory.this.logger.debug(
                "authenticationEntryPoint is null, so allowing original HttpServletRequest to handle authenticate");
        return super.authenticate(response);
    }
    if (isAuthenticated()) {
        return true;
    }
    entryPoint.commence(this, response,
            new AuthenticationCredentialsNotFoundException("User is not Authenticated"));
    return false;
}

It can be seen that this method is used to judge whether the user has completed the authentication operation. Returning true means that the user has completed the authentication, and returning false means that the user has not completed the authentication.

2. Power of source code

After reading the above principle analysis, you should also understand the second scheme, that is, instead of using the httpservletrequest#login method, we directly call the AuthenticationManager for login verification.

Let’s have a look.

First, we modify the configuration class as follows:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login","/login2")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
        provider.setUserDetailsService(manager);
        return new ProviderManager(provider);
    }
}
  1. First, in login release, add/login2Interface, which is the second login interface I will customize.
  2. Provide an instance of AuthenticationManager. SongGe has shared the playing method of AuthenticationManager many times in the previous spring security series, so I won’t repeat it here (I haven’t seen the background Reply of the little partner official accountss)。 When creating an AuthenticationManager instance, you also need to provide a daoauthenticationprovider. As we all know, the verification of user passwords is completed in this class, and a userdetailsservice instance is configured for daoauthenticationprovider, which provides the user data source.

Next, provide a login interface:

@RestController
public class LoginController {
    @Autowired
    AuthenticationManager authenticationManager;
    @PostMapping("/login2")
    public String login2(String username, String password, HttpServletRequest req) {
        try {
            Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
            SecurityContextHolder.getContext().setAuthentication(token);
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "failed";
    }
}

In the login interface, pass in the user name, password and other parameters, and then package the user name, password and other parameters into a usernamepasswordauthenticationtoken object. Finally, call the authenticationmanager\authenticate method for authentication. After successful authentication, an authenticated authentication object will be returned, and then manually store the authentication object in the securitycontextholder.

After configuration, restart the project and conduct login test.

The second scheme is similar to the first scheme. The second scheme is actually to pull out the bottom layer of the first scheme and realize it again,That’s it

3. Summary

Well, today I’ll introduce two spring security DIY login schemes. These schemes may not be commonly used in work, but they are still very helpful for everyone to understand the principle of spring security. Interested partners can knock and try

In addition, if you find it difficult to read this article, you may wish to reply at the backstage of the official accountss, take a look at other articles in the spring security series, which will help you understand this article. Of course, you can also take a look at SongGe’s new book:

The book “spring security in simple terms” has been officially published by Tsinghua University Press. Interested partners poke here – > – > > spring security in simple terms. A Book learns spring security.