Implementation of automatic login based on Hash encryption scheme in Spring Security Series tutorials

Keywords: Java JavaEE

preface

In the previous two chapters, brother one by one   We have realized the function of adding graphic verification code verification in Spring Security. In fact, the functions of Spring Security are not only these, but also many other effects, such as automatic login, logout and login.

Some small partners will ask, why should we realize automatic login? This requirement is actually very common, because for users, they may often need to log in and log out. Think about it. If users have to enter their user name and password every time they log in, is it annoying and the user experience is not very good?

Therefore, in order to improve the user experience of the project, we can add the automatic login function to the project. Of course, we should also provide users with the function of exiting and logging in. Then follow   One brother   Learn how to implement these functions!

If you need more tutorials, just scan the code on wechat

 

👆👆👆

Don't forget to scan the code to get the information [HD Java learning roadmap]

And [full set of learning videos and supporting materials]

1, Introduction to automatic login

1. Why log in automatically

When we visit the website or app, we are generally required to register an account, including user name and password information, and the password will be limited in length and value range. Many times, the accounts we register on different websites may have different passwords, which makes us have to remember the user information on these different websites.

Then the next time we log in, because we have too many passwords, we may not remember these account passwords. Therefore, after several failed login attempts, many people will choose to retrieve their password, thus falling into the cycle of how to set their password again.

In order to minimize the frequency of user re login and improve the user experience, we can provide an experiential function of automatic login, which will not only bring convenience to users, but also bring risks to users.

2. Implementation scheme of automatic login

After understanding the background and function of automatic login, how can we realize automatic login?

First of all, we know that automatic login is a mechanism to save the user's login information in the cookie of the user's browser. When the user visits next time, it will automatically verify and establish the login status.

Therefore, based on the above principles, Spring Security provides us with two better schemes to realize automatic login:

Hash based encryption algorithm mechanism: encrypt the necessary login information of users and generate tokens to realize automatic login, which is realized by TokenBasedRememberMeServices class.

Based on database and other persistent data storage mechanisms: generate persistent tokens to realize automatic login, which is realized by persistent token based remembermeservices.

The two implementation classes I mentioned above are actually subclasses of abstractremebermeservices, as shown in the following figure:

After understanding these core APIs, we can use these two APIs to realize automatic login.

2, Automatic login based on Hash encryption scheme

Let me take you first to use the first implementation scheme, that is, hash encryption scheme to realize automatic login.

First, we will develop on the basis of the previous cases. The specific project creation process is omitted. Please refer to the previous chapters.

1. Configure the key of the encryption token

First, we create an application.yml file, add the database configuration and a key string used to encrypt the token. The value of the string can be customized.

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db-security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
    username: root
    password: syc
  security:
    remember-me:
      key: yyg   

2. Configure SecurityConfig class

As in the previous case, I still want to create a SecurityConfig class. In the configure(HttpSecurity http) method, associate our database through JdbcTokenRepositoryImpl, and turn on the "remember me" function through the rememberMe() method. In addition, I also need to configure the rememberKey in the configuration file as the key for hash encryption.

@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.remember-me.key}")
    private String rememberKey;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Associating data sources with JdbcTokenRepositoryImpl
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);

        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .hasRole("USER")
                .antMatchers("/app/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
             //Turn on the "remember me" function
                .rememberMe()
                .userDetailsService(userDetailsService)
                //Configure key s for hash encryption
                .key(rememberKey)
                .and()
                .csrf()
                .disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //Do not encrypt login password
        return NoOpPasswordEncoder.getInstance();
    }
    
}

3. Add test interface

In order to facilitate the subsequent testing, I randomly write a web interface for testing.

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("hello")
    public String hello() {

        return "hello, user";
    }

}

4. Start project testing

Then we start the project for testing. Of course, don't forget to write the project entry class. I won't paste the relevant code here.

When we visit the / user/hello interface, we will redirect to the / login interface first. At this time, we will find that there is an "remember me" function on the default login page.

At this point, if we open   The developer debugs the tool, checks "remember me", and then initiates a request. At this time, we will see the remember me cookie on the console, indicating that Spring Security has automatically generated the remember me cookie, and the remember me parameter in the form is also in the "on" state.

In other words, we use a few lines of code to realize the automatic login based on Hash encryption scheme.

3, Implementation principle of hash encryption scheme

You may be curious, how does the hash encryption scheme achieve automatic login? Don't worry. Next, brother Yi will analyze the implementation principle of hash encryption for you.

1. Analysis of cookie encryption principle

As I told you earlier, automatic login is actually a mechanism to save the user's login information in the cookie of the user's browser. When the user visits next time, it will automatically verify and establish the login status.

Therefore, after automatic login, the cookie information representing the user will be generated. However, for security, the cookie will not be stored in plaintext. It needs to be encrypted and decoded. So next, I'll analyze the encryption and decoding process of this cookie.

first   Yige   Explain to you the so-called hash encryption algorithm. Its essence is to combine the fields such as username, expirationTime and password, plus the user-defined key field, separate each field with ":", and finally use the md5 algorithm for hash operation, so as to get an encrypted string. Spring Security stores the encrypted string in the cookie as the user's login identification information.

then   Yige   Take a look at the makeTokenSignature() method in the token based member meservices source class. You will see the specific encryption implementation process of the hash encryption algorithm. The source code is shown in the following figure:

2. Analysis of cookie decoding principle

MD5 is used for encryption above. After the user logs in next time, he must compare the information to judge whether the user information is consistent. Spring Security decodes the information in the cookie first, and then compares it with the previously recorded login information to judge whether the user has logged in.

Spring Security uses Base64 to decode cookies in the decode Cookie() method of abstract member meservices class, as shown in the following figure:

For the above two source methods, we can simply extract the following two lines of code:

//Hash encryption for each field
hashInfo=md5Hex(username +":"+expirationTime +":"password+":"+key)

//Decoding with base64
rememberCookie=base64(username+":"+expirationrime+":"+hashInfo)

Among them, expirationTime refers to the validity period of this automatic login, and key is a hash value specified by yourself to prevent the token from being modified. After analyzing the source code by using the above two methods, Yige briefly summarizes the generation and verification principle of cookie s:

  • First, use the above source code to generate cookie s and save them in the browser;

  • After the browser is closed and reopened, when the user accesses the / user/hello interface again, it will carry the remember me cookie to the server;

  • After receiving the cookie, the server uses Base64 to decode, calculate the user name and expiration time, and then query the user password according to the user name;

  • Finally, calculate the hash value through the MD5 hash function, and compare the calculated hash value with the hash value passed by the browser to confirm whether the token is valid.

3. Source code analysis of automatic login

After analyzing the encryption and decoding of cookie information above, I will introduce the implementation process of automatic login from two aspects in combination with the source code. One is the generation process of remember me token, and the other is the parsing process of the token.

3.1 source code analysis of token generation

If we want to know how to generate the remember me automatic login token in the source code, we must first know how Spring Security enters the code where the token is located. The execution of this code is related to the authentication and authorization of Spring Security mentioned in the previous chapter. Please go to the previous section.

AbstractAuthenticationProcessingFilter#doFilter -> 
AbstractAuthenticationProcessingFilter#successfulAuthentication -> 
AbstractRememberMeServices#loginSuccess -> 
TokenBasedRememberMeServices#onLoginSuccess

The core processing method of token generation is defined in: token based member me services # onlogin success.

@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
  Authentication successfulAuthentication) {
    //Get user name from authentication object
  String username = retrieveUserName(successfulAuthentication);
    //Get password from authentication object
  String password = retrievePassword(successfulAuthentication);
    
    ......
    
  if (!StringUtils.hasLength(password)) {
        //Query the corresponding user according to the user name
    UserDetails user = getUserDetailsService().loadUserByUsername(username);
        //Get the password on the user
    password = user.getPassword();
  }
    
    //Get the login expiration time. The default is 2 weeks
  int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
  long expiryTime = System.currentTimeMillis();
  expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
    
    //Generate remember me signature information
  String signatureValue = makeTokenSignature(expiryTime, username, password);
    
    //Save cookie
  setCookie(new String[] { username, Long.toString(expiryTime), signatureValue },
    tokenLifetime, request, response);
}

protected String makeTokenSignature(long tokenExpiryTime, String username,
  String password) {
  String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
  MessageDigest digest;
  digest = MessageDigest.getInstance("MD5");
  return new String(Hex.encode(digest.digest(data.getBytes())));
}

The implementation logic of the above source code is well understood:

  • Firstly, the user name / password is extracted from the Authentication object successfully logged in;

  • Since the password may be erased after successful login, if you don't get the password at first, reload the user from UserDetailsService and obtain the password again;

  • Next, obtain the validity period of the token, which is two weeks by default;

  • Next, call the makeTokenSignature() method to calculate the hash value. In fact, it calculates a hash value according to username, token validity, password and key. If we do not set this key ourselves, it is set in the membermeconfigurer #getkey method by default, and its value is a UUID string. However, if the server restarts, the default key will change, which will invalidate all the remember me automatic login tokens previously distributed, so we can specify this key.

  • Finally, put the user name, token validity and the calculated hash value into the Cookie and return it with the response.

3.2 source code analysis of token parsing

For the function of RememberMe, Spring Security provides   Remember Me Authentication Filter   This filter class handles related functions. Let's take a look at the doFilter() method of the member me authentication filter:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
   throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest) req;
  HttpServletResponse response = (HttpServletResponse) res;

  if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //Business logic for handling automatic login
   Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
     response);

   if (rememberMeAuth != null) {
    // Attempt authenticaton via AuthenticationManager
    try {
     rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

     // Store to SecurityContextHolder
     SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

     onSuccessfulAuthentication(request, response, rememberMeAuth);

     if (logger.isDebugEnabled()) {
      logger.debug("SecurityContextHolder populated with remember-me token: '"
        + SecurityContextHolder.getContext().getAuthentication()
        + "'");
     }

     // Fire event
     if (this.eventPublisher != null) {
      eventPublisher
        .publishEvent(new InteractiveAuthenticationSuccessEvent(
          SecurityContextHolder.getContext()
            .getAuthentication(), this.getClass()));
     }

     if (successHandler != null) {
      successHandler.onAuthenticationSuccess(request, response,
        rememberMeAuth);

      return;
     }

    }
    catch (AuthenticationException authenticationException) {
     if (logger.isDebugEnabled()) {
      logger.debug(
        "SecurityContextHolder not populated with remember-me token, as "
          + "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
          + rememberMeAuth
          + "'; invalidating remember-me token",
        authenticationException);
     }

     rememberMeServices.loginFail(request, response);

     onUnsuccessfulAuthentication(request, response,
       authenticationException);
    }
   }

   chain.doFilter(request, response);
  }
  else {
   if (logger.isDebugEnabled()) {
    logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
      + SecurityContextHolder.getContext().getAuthentication() + "'");
   }

   chain.doFilter(request, response);
  }
 }

The key point of this method is that if the currently logged in user instance cannot be obtained from the Security Context Holder, call the rememberMeServices.autoLogin() logic to log in. Let's take a look at this method:

@Override
 public final Authentication autoLogin(HttpServletRequest request,
   HttpServletResponse response) {
  String rememberMeCookie = extractRememberMeCookie(request);

  if (rememberMeCookie == null) {
   return null;
  }

  logger.debug("Remember-me cookie detected");

  if (rememberMeCookie.length() == 0) {
   logger.debug("Cookie was empty");
   cancelCookie(request, response);
   return null;
  }

  UserDetails user = null;

  try {
   String[] cookieTokens = decodeCookie(rememberMeCookie);
   user = processAutoLoginCookie(cookieTokens, request, response);
   userDetailsChecker.check(user);

   logger.debug("Remember-me cookie accepted");

   return createSuccessfulAuthentication(request, user);
  }
  ......

  cancelCookie(request, response);
  return null;
 }

Spring Security extracts the cookie information here and decodes the cookie information. After decoding, call the processAutoLoginCookie() method to verify.

I won't post the code of processautologincookee() method. The core process is to first obtain the user name and expiration time, then query the user password according to the user name, and then calculate the hash value through MD5 hash function. Finally, compare the obtained hash value with the hash value passed by the browser to confirm whether the token is valid, and then confirm whether the login is valid.

So far, brother Yi   Combined with the source code and underlying principles, I explained to you that the hash encryption scheme realizes automatic login, and introduced the hash encryption algorithm in this case. How do you master it? Please write to me in the comments area   One brother   Leave a message and tell me how you feel! In the next article, Yige will explain to you how to realize automatic login based on persistent token. Please look forward to it!

If you need more tutorials, just scan the code on wechat

 

👆👆👆

Don't forget to scan the code to get the information [HD Java learning roadmap]

And [full set of learning videos and supporting materials]  

 

Posted by poscribes on Fri, 22 Oct 2021 00:39:25 -0700