Spring Security study notes (three)-automatic login and logout

Article Directory

1 Automatic login

For some other types of design, please refer to the previous articles mentioned, or enter https://github.com/LiYangSir/Spring-Security-In-Action section 5 to directly view the source code.

Automatic login mainly includes two types: 1. Hash encryption method, but this method will have security problems, and the corresponding information is stored locally. 2. The method of persisting the token is the same as the way of hashing in the interaction, sending the generated token to the browser, and verifying it the next time the user visits.

Persistent token scheme

​In the process of persisting tokens, the core is the series and token values. They both use MD5 hashed random strings. The difference is that the series is only updated when the user re-logins with the password, and the token will be in It is regenerated in every new session.

​The advantages of this design are: 1. Each session will trigger the update of the token, that is, each token only supports single-instance login. 2. Automatic login will not cause series changes, and each time you log in, you must verify the two values ​​of series and token. When the token is stolen without using automatic login, the system will refresh the token value after the illegal user logs in. At this time, in the browser of the legal user, the token has expired. When the legal user uses the automatic login, because the token corresponding to the series is different, the token is considered to have been stolen, and the user can be reminded to perform the corresponding operation.

1. Resource preparation

Automatic login needs to store the token information in the database, so a special database needs to be established, which is used here JdbcTokenRepositoryImplto realize the operation of the database. We can click on to find a statement to create a table, through this statement to create a database.

create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)
Insert picture description here


Since operating the database also requires the dependency and configuration of the operating database

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2. Create UserDetailService

When performing automatic login, the UserName needs to be parsed through the Token to further obtain the corresponding User object.

package cn.quguai.service;

@Service
public class MyUserDetailService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository; // 使用Spring Data JPA创建UserUserRepository

    @Autowired
    private PasswordEncoder passwordEncoder;  // 需要创建一个Bean: new BCryptPasswordEncoder()放入容器中

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity userEntity = userRepository.findByUsername(username);
        if (userEntity == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        userEntity.setPassword(passwordEncoder.encode(userEntity.getPassword()));
        userEntity.setAuthorityList(AuthorityUtils.commaSeparatedStringToAuthorityList(userEntity.getRoles()));
        return userEntity;
    }
}

3. Configure Spring Security

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyUserDetailService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        jdbcTokenRepository.setCreateTableOnStartup(true);
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin()
                .and().rememberMe().userDetailsService(userDetailsService).tokenRepository(jdbcTokenRepository).tokenValiditySeconds(60)
                .and().csrf().disable();
    }
}

4. Experiment

image.png

5. Summary of Principles

Spring Security will Base64 decode the Cookie, which is mainly divided into two parts, namely seriesand token. Spring Security seriesobtains a piece of data from the database (because we will find that the series is used as the primary key in the previous SQL statement) to obtain The actual real token, user name and last login information.

  1. Confirm the identity of the token by the username;
  2. Verify that the token is valid by comparing the decoded token with the token in the database;
  3. Obtain whether the token has expired through the last login information;

2 Logout

Logging out is much simpler than logging in. You only need to configure the relevant logic for logging out.
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyUserDetailService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        jdbcTokenRepository.setCreateTableOnStartup(true);
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin()
                .and().rememberMe().userDetailsService(userDetailsService).tokenRepository(jdbcTokenRepository).tokenValiditySeconds(60)
                .and().csrf().disable();
        // 增加处理注销后的逻辑:1. 删除Session  2.删除Cookie
        http.logout().logoutUrl("/myLogout").logoutSuccessUrl("/").logoutSuccessHandler((request, response, authentication) -> {
        }).invalidateHttpSession(true).deleteCookies("cookie").addLogoutHandler((request, response, authentication) -> {});
    }
}