Spring Security combat (3) - automatic login and logout login

Table of contents

1. Realize automatic login

 1. Hash encryption scheme

2. Persistent Token Scheme

 2. Log out


1. Realize automatic login

        Automatic login is a mechanism that saves the user's login information in the cookie of the user's browser, and automatically verifies and establishes a login status when the user visits next time.

        Spring Security provides two very nice tokens:

  • Encrypt the user's necessary login information with a hash algorithm and generate a token
  • Persistent tokens for persistent data storage mechanisms such as databases

 1. Hash encryption scheme

(1) Increase the automatic login function

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                //记住我
                .rememberMe().userDetailsService(userDetailsService);
    }

(2) Add a remember me checkbox to the login page

<label><input type="checkbox" name="remember-me" id="remember-me">Remember Me</label>

(3) Log in according to the normal process

Check the cookie in the browser's developer tools, and you can see that there is an additional remember-me, which is the cookie field for automatic login by Spring Security by default. When not configured, the expiration time is two weeks.

But now there is a problem, every time the service is restarted, the KEY (remember-me) will be regenerated, so we need to log in again every time the service is restarted, so there is no automatic login.

A reasonable usage is to specify the KEY. like:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .rememberMe().userDetailsService(userDetailsService)
        .key("zy");//指定key
    }

In this way, after restarting the service and logging in, even if the service is restarted again, there is no need to log in again, which realizes the automatic login function.

2. Persistent Token Scheme

         The persistent token scheme is consistent with the hash encryption scheme in terms of interaction. After the user checks Remember-me, the generated token is sent to the user's browser, and the token is read when the user visits the system next time. Authenticate. The difference is that it adopts a more rigorous security design.

        In the persistent token scheme, the core is the two values ​​of series and token , which are random strings hashed with MD5. The difference is that the series is only updated when the user re-logs in with a password, while the token is regenerated in each new session.

The benefits of this design :

        First of all, the problem that a token can be logged in on multiple terminals at the same time in the hash encryption scheme is solved. Each session will trigger token update, that is, each token only supports single-instance login .

        Secondly, automatic login will not lead to changes in series, and each automatic login needs to verify the two values ​​of series and token at the same time. When the token is stolen before automatic login is used, the system will Refresh the token value. At this time, in the legitimate user's browser, the token value has expired. When a legitimate user uses automatic login, since the tokens corresponding to the series are different, the system can infer that the token may have been stolen, and take some measures. For example, clear all the automatic login tokens of the user, and notify the user that the account may have been hacked.

(1) Create a new persistent_logins table in the database

CREATE TABLE persistent_logins (
    username VARCHAR(64) NOT NULL,
    series VARCHAR(64) NOT NULL,
    token VARCHAR(64) NOT NULL,
    last_used TIMESTAMP NOT NULL,
    PRIMARY KEY (series)
);

(2) Configure the Remember-me function of spring security

Since the persistent token scheme needs to be used, the tokenRepository is customized. TokenRepository() needs to pass in a PersistentTokenRepository instance in the parentheses . Here we use JdbcTokenRepositoryImpl. JdbcTokenRepositoryImpl is a class that implements corresponding SQL operations based on DataSource, so we need to specify DataSource.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //指定dataSource
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .rememberMe()
                .userDetailsService(userDetailsService)
        .tokenRepository(jdbcTokenRepository);
    }

(3) Restart the service

After restarting the service and logging in, restart the service again to achieve automatic login

 Decoded by base64:

JSESSIONID=E76686FEE7637DEB4F1E5C9E8A930A6F;
Idea-b5baabb9=16077984-579b-44d2-a1a2-92f5837858ef;
remember-me=1K3%24%2FLnZb590SKpZlTlpHg%3D%3D:eduptdyzPFt8x2wQI2QLA==

The part before the colon is the series, and the part after the colon is the token. When auto-login authentication, Spring Security obtains three information of user name, token, and last auto-login time through series.

  • Confirm the token's identity by username
  • Know whether the token is valid by comparing the token
  • Know whether the token has expired by the time of the last automatic login

A new token is generated after the full verification is passed. 

(4) View database information

 2. Log out

        Authentication systems often have a logout and login function, and Spring Security also provides support in this regard. In fact, from the moment we write the configuration class to inherit WebSecurityConfigurerAdapter, Spring Security has already embedded the logout logic for our system.

        The logout() method in HttpSecurity takes a LogoutConfigurer as the configuration basis to create a filter for logout.

        It registers a /logout route by default, and users can safely log out of their login status by accessing this route, including invalidating HttpSession, clearing configured Remember-me authentication, and clearing SecurityContextHolder , and redirecting to /login after successful logout ?logout page.

You can also customize the configuration, such as:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //指定dataSource
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .rememberMe()
                .userDetailsService(userDetailsService)
        .tokenRepository(jdbcTokenRepository)
        .and()
        .logout()
        .logoutUrl("/myLogout")//配置接收注销请求的路由
        .logoutSuccessUrl("/login.html")//注销成功后跳转到 login.html页面
        .logoutSuccessHandler(new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                System.out.println("用户成功退出");
            }
        })
        .invalidateHttpSession(true) //使用户的HttpSession失效
        .deleteCookies("cookie1","cookie2") //注销成功 删除指定的cookie
        .addLogoutHandler(new LogoutHandler() {
            @Override
            public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
                System.out.println("已经注销了!");
            }
        });
    }

In fact, the logout cleanup process is streamed by multiple LogoutHandlers.

Guess you like

Origin blog.csdn.net/weixin_49561506/article/details/130175695