Detailed explanation of the use and principle of spring security member me
The remember me function is to log in once after "remember me" is checked, and then log in free within the validity period.
First look at the specific configuration:
POM file:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Security configuration:
@Autowired private UserDetailsService myUserDetailServiceImpl; // 用户信息服务 @Autowired private DataSource dataSource; // 数据源 @Override protected void configure(HttpSecurity http) throws Exception { // formLogin()是默认的登录表单页,如果不配置 loginPage(url),则使用 spring security // 默认的登录页,如果配置了 loginPage()则使用自定义的登录页 http.formLogin() // 表单登录 .loginPage(SecurityConst.AUTH_REQUIRE) .loginProcessingUrl(SecurityConst.AUTH_FORM) // 登录请求拦截的url,也就是form表单提交时指定的action .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler) .and() .rememberMe() .userDetailsService(myUserDetailServiceImpl) // 设置userDetailsService .tokenRepository(persistentTokenRepository()) // 设置数据访问层 .tokenValiditySeconds(60 * 60) // 记住我的时间(秒) .and() .authorizeRequests() // 对请求授权 .antMatchers(SecurityConst.AUTH_REQUIRE,securityProperty.getBrowser().getLoginPage()).permitAll() // 允许所有人访问login.html和自定义的登录页 .anyRequest() // 任何请求 .authenticated()// 需要身份认证 .and() .csrf().disable() // 关闭跨站伪造 ; } /** * 持久化token * * Security中,默认是使用PersistentTokenRepository的子类InMemoryTokenRepositoryImpl,将token放在内存中 * 如果使用JdbcTokenRepositoryImpl,会创建表persistent_logins,将token持久化到数据库 */ @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); // 设置数据源 // tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉 return tokenRepository; }
The above myuserdetailserviceimpl is the userdetailsservice interface implemented by ourselves, and the datasource will automatically read the database configuration. The expiration time is set to 3600 seconds, or one hour
Add a line to the login page (name must be remeber me):
Basic principle of "remember me":
1. The first time an authentication request is sent, it will be intercepted by the usernamepasswordauthenticationfilter and then authenticated.
After successful authentication, there is a remebermeservices interface in abstractauthenticationprocessingfilter.
The default implementation class of this interface is nullremebermeservices. Here, another implementation abstract class abstractremebermeservices will be called
// ... private RememberMeServices rememberMeServices = new NullRememberMeServices(); protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication authResult) throws IOException,ServletException { // ... SecurityContextHolder.getContext().setAuthentication(authResult); // 登录成功后,调用RememberMeServices保存Token相关信息 rememberMeServices.loginSuccess(request,response,authResult); // ... }
2. Call the loginsuccess method of abstractmembermeservices.
You can see that the following onloginsuccess () method is called only when the name in the request is "remember me" is true. This is why the name of the form in the login page above must be "remember me":
3. After remberme () is configured in security, persistenttokenbasedremembermeservices will implement the abstract method in the parent class abstractremebermeservices.
In persistenttokenbasedremembermeservices, there is a persistenttokenrepository, which will generate a token, write the token to the cookie and return to the browser. The default implementation class of persistenttokenrepository is inmemorytokenrepositoryimpl, which saves the token in memory. Here we have configured another implementation class, jdbctokenrepositoryimpl, which will persist tokens to the database
// ... private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl(); protected void onLoginSuccess(HttpServletRequest request,Authentication successfulAuthentication) { String username = successfulAuthentication.getName(); logger.debug("Creating new persistent login for user " + username); // 创建一个PersistentRememberMeToken PersistentRememberMeToken persistentToken = new PersistentRememberMeToken( username,generateSeriesData(),generateTokenData(),new Date()); try { // 保存Token tokenRepository.createNewToken(persistentToken); // 将Token写到Cookie中 addCookie(persistentToken,request,response); } catch (Exception e) { logger.error("Failed to save persistent token ",e); } }
4. Jdbctokenrepositoryimpl persisted the token to the database
/** The default sql used by <tt>createNewToken</tt> */ public static final String DEF_INSERT_TOKEN_sql = "insert into persistent_logins (username,series,token,last_used) values(?,?,?)"; public void createNewToken(PersistentRememberMeToken token) { getJdbcTemplate().update(insertTokensql,token.getUsername(),token.getSeries(),token.getTokenValue(),token.getDate()); }
If you look at the database, you can see that you are going to persist_ A piece of data is inserted in logins:
5. Restart the service and send the second authentication request. Only cookies will be carried.
Therefore, it will be directly intercepted by membermeauthenticationfilter, and there is no authentication information in memory at this time.
You can see that the membermeservices at this time is implemented by persistenttokenbasedremembermeservices
6, in PersistentTokenBasedRememberMeServices, call the processAutoLoginCookie method to get user related information.
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) + "'"); } // 从Cookie中获取Series和Token final String presentedSeries = cookieTokens[0]; final String presentedToken = cookieTokens[1]; //在数据库中,通过Series查询PersistentRememberMeToken PersistentRememberMeToken token = tokenRepository .getTokenForSeries(presentedSeries); if (token == null) { throw new RememberMeAuthenticationException( "No persistent token found for series id: " + presentedSeries); } // 校验数据库中Token和Cookie中的Token是否相同 if (!presentedToken.equals(token.getTokenValue())) { tokenRepository.removeUserTokens(token.getUsername()); throw new CookieTheftException( messages.getMessage( "PersistentTokenBasedRememberMeServices.cookieStolen","Invalid remember-me token (Series/token) mismatch. Implies prevIoUs cookie theft attack.")); } // 判断Token是否超时 if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System .currentTimeMillis()) { throw new RememberMeAuthenticationException("Remember-me login has expired"); } if (logger.isDebugEnabled()) { logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "',series '" + token.getSeries() + "'"); } // 创建一个新的PersistentRememberMeToken PersistentRememberMeToken newToken = new PersistentRememberMeToken( token.getUsername(),new Date()); try { //更新数据库中Token tokenRepository.updateToken(newToken.getSeries(),newToken.getTokenValue(),newToken.getDate()); //重新写到Cookie addCookie(newToken,response); } catch (Exception e) { logger.error("Failed to update token: ",e); throw new RememberMeAuthenticationException( "Autologin Failed due to data access problem"); } //调用UserDetailsService获取用户信息 return getUserDetailsService().loadUserByUsername(token.getUsername()); }
7. After obtaining the relevant information of the user, call the AuthenticationManager to authenticate and authorize
The above is the whole content of this article. I hope it will help you in your study, and I hope you will support us a lot.