Spring Security (7): Realize the "Remember Me" function

This chapter will continue to expand the function and come to the realization of a "remember me" function, which means that after the user logs in once, the system will remember the user for a period of time. During this time, the user can use the system without logging in again.

Remember Me Basic Principles of Function

Principle explanation

  • When a user logs in and sends an authentication request, it will be UsernamePasswordAuthenticationFilterintercepted by the authentication. After the authentication is successful, a RememberMeServiceservice will be called . There is one TokenRepositoryin the service. This service will generate a Token, and then write the Token into the cookie of the browser and use the TokenRepository to convert the generated Token. Write to the database , because this action is done after the authentication is successful, so when the Token is written into the database, the user name will be written into the database at the same time.
  • If the browser is closed to re-access the system, the user can access without logging in again. At this time, the request will pass through RememberMeAuthenticationFilterthe filter chain. The function of this filter is to read the Token in the Cookie and give it to RemeberMeService, and RemeberMeService will use TokenRepository to Check the database to check whether the Token has a record in the database. If there is a record, the user name will be taken out. After it is taken out, various verifications will be performed and a new Token will be generated before calling the previous one UserDetailServiceto obtain the user’s information, and then put the user SecurityContextPut the information in it, and log in the user here.

Illustrated

Flow chart explanation

Where is RememberMeAuthenticationFilter in the filter chain?

  • Diagram
    Insert picture description here
  • First, other authentication filters will authenticate first, and when other filters are unable to authenticate, RememberMeAuthenticationFilterthey will try to authenticate.

Remember me function concrete realization

Front page

  • When logging in, add a line to remember my check button. Note here that the name must be remember-me, which will be mentioned in the source code section below.
			<tr>
				<td colspan='2'><input name="remember-me" type="checkbox" value="true" />记住我</td>
			</tr>

Backstage

  • First configure the TokenRepositoryBean
	/**
	 * 记住我功能的Token存取器配置
	 * 
	 * @return
	 */
	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
    
    
		JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
		tokenRepository.setDataSource(dataSource);
		// 启动的时候自动创建表,建表语句 JdbcTokenRepositoryImpl 已经都写好了
		tokenRepository.setCreateTableOnStartup(true);
		return tokenRepository;
	}
  • Then you need to configure all the components of the remember me function in the configure configuration method.
	protected void configure(HttpSecurity http) throws Exception {
    
    
		ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
		http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
				.formLogin()
				.loginPage("/authentication/require")
				.loginProcessingUrl("/authentication/form")
				.successHandler(meicloudAuthenticationSuccessHandler)
				.failureHandler(meicloudAuthenticationFailureHandler)
				// 配置记住我功能
				.and()
				.rememberMe()
				// 配置TokenRepository
				.tokenRepository(persistentTokenRepository())
				// 配置Token过期时间
				.tokenValiditySeconds(3600)
				// 最终拿到用户名之后,使用UserDetailsService去做登录
				.userDetailsService(userDetailsService)
				.and()
				.authorizeRequests()
				.antMatchers("/authentication/require", securityProperties.getBrowser().getSignInPage(), "/code/image").permitAll()
				.anyRequest()
				.authenticated()
				.and()
				.csrf().disable();

	}

Remember me feature Spring Security source code analysis

"Remember me" source code flow before login

  • After successful authentication, calls successfulAuthentication method (Chapter V source part of these have already learned), the authentication information is saved to the Context after RememberMeServices就会调用它的loginSuccess方法.
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    
    
        if (this.logger.isDebugEnabled()) {
    
    
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
    
    
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
  • The loginSuccess method will first check whether there is a name remember-meparameter in the request before proceeding to the next step.
    public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
    
    
    	// this.parameter = "remember-me"
        if (!this.rememberMeRequested(request, this.parameter)) {
    
    
            this.logger.debug("Remember-me login not requested.");
        } else {
    
    
            this.onLoginSuccess(request, response, successfulAuthentication);
        }
    }
  • Then enter the onLoginSuccess method, which is mainly 写库和写Cookiethe operation.
    protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
    
    
        String username = successfulAuthentication.getName();
        this.logger.debug("Creating new persistent login for user " + username);
        // 生成Token
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
        try {
    
    
        	// 将Token和userName插入数据库
            this.tokenRepository.createNewToken(persistentToken);
            // 将Token写到Cookie中
            this.addCookie(persistentToken, request, response);
        } catch (Exception var7) {
    
    
            this.logger.error("Failed to save persistent token ", var7);
        }
    }

"Remember me" source code flow after login

  • First, it will enter RememberMeAuthenticationFilter, and it will first determine whether the previous filter has been authenticated (whether there is authentication information in the Context). If it has not been authenticated, it will call the autoLogin method of RememberMeServices.
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) {
    
    
            Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
            if (rememberMeAuth != null) {
    
    
                try {
    
    
                    rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
                    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                    this.onSuccessfulAuthentication(request, response, rememberMeAuth);
                    if (this.logger.isDebugEnabled()) {
    
    
                        this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
                    }

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

                    if (this.successHandler != null) {
    
    
                        this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
                        return;
                    }
                } catch (AuthenticationException var8) {
    
    
                    if (this.logger.isDebugEnabled()) {
    
    
                        this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);
                    }

                    this.rememberMeServices.loginFail(request, response);
                    this.onUnsuccessfulAuthentication(request, response, var8);
                }
            }
            chain.doFilter(request, response);
        } else {
    
    
            if (this.logger.isDebugEnabled()) {
    
    
                this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
            }
            chain.doFilter(request, response);
        }
    }
  • In the autoLogin method, this.processAutoLoginCookie(cookieTokens, request, response)this method is mainly called to obtain user information in the database. The steps are:
  • Parse the cookie from the front end, which contains Token and seriesId, it will使用seriesId查找数据库的Token
  • Check the Token in the Cookie and the Token found in the database是否一样
  • If the same, then check the Token in the database是否已过期
  • If all the above are met, the old user name and series will be used to renew a new Token. At this time过期时间也重新刷新
  • Then the new Token saved back 数据库, but added back Cookiein
  • Finally, call the loadUserByUsername method of UserDetailsService返回UserDetails
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
    
    
        if (cookieTokens.length != 2) {
    
    
            throw new InvalidCookieException("Cookie token did not contain 2 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
        } else {
    
    
            String presentedSeries = cookieTokens[0];
            String presentedToken = cookieTokens[1];
            PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
            if (token == null) {
    
    
                throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
            } else if (!presentedToken.equals(token.getTokenValue())) {
    
    
                this.tokenRepository.removeUserTokens(token.getUsername());
                throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
            } else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
    
    
                throw new RememberMeAuthenticationException("Remember-me login has expired");
            } else {
    
    
                if (this.logger.isDebugEnabled()) {
    
    
                    this.logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'");
                }
                PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());
                try {
    
    
                    this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
                    this.addCookie(newToken, request, response);
                } catch (Exception var9) {
    
    
                    this.logger.error("Failed to update token: ", var9);
                    throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
                }

                return this.getUserDetailsService().loadUserByUsername(token.getUsername());
            }
        }
    }
  • Back to RememberMeAuthenticationFilter, after calling the autoLogin method, rememberMeAuth is obtained, and then an authentication is performed on it. After the authentication is successful 保存到SecurityContext, the source code of the entire RememberMe automatic login process ends.

Guess you like

Origin blog.csdn.net/qq_36221788/article/details/106131699