一:简介

Remember Me 即记住我,常用于 Web 应用的登录页目的是让用户选择是否记住用户的登录状态。当用户选择了 Remember Me 选项,则在有效期内若用户重新访问同一个 Web 应用,那么用户可以直接登录到系统中,而无需重新执行登录操作。

具体的实现思路就是通过Cookie来记录当前用户身份。当用户登录成功之后,会通过算法将用户信息、时间戳等进行加密,加密完成后,通过响应头带回前端存储在cookie中,当浏览器会话过期之后,如果再次访问该网站,会自动将Cookie中的信息发送给服务器,服务器对Cookie中的信息进行校验分析,进而确定出用户的身份,Cookie中所保存的用户信息也是有时效的,例如三天、一周等。

如果不做额外配置,那么,服务端session过期时间默认为30分钟,也就是说即使用户认证成功了,但30分钟没有向后端发送请求,那么,30分钟后认证也会失效,那时候再请求接口会直接要求重新认证,可以在yml中配置服务端会话保存时间,单位为分钟

server:servlet:session:timeout: 1

二:基本使用

开启RememberMe功能很简单,直接加一个rememberMe()方法就行

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers("/hello1").permitAll().anyRequest().authenticated().and().formLogin().successHandler(new CustomAuthenticationSuccessHandler()).failureHandler(new CustomAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new CustomLogoutSuccessHandler()).and().rememberMe();}

开启之后,我们重新打开默认登录页面,会发现,页面多了一个复选框,勾选后即可实现RememberMe功能

三:原理分析

开启RememberMe后,RememberMeAuthenticationFilter过滤器就会被激活,我们可以看看这个过滤器的doFilter方法:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;// 查看是否有SecurityContextHolder中是否有认证信息if (SecurityContextHolder.getContext().getAuthentication() == null) {// 没有认证信息,因此尝试rememberMe认证,这也是核心方法Authentication rememberMeAuth = rememberMeServices.autoLogin(request,response);if (rememberMeAuth != null) {// rememberMeAuth不为null则表示自动登录成功,现在需要对key进行校验try {rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);// 认证走到这一步就说明成功了,因为失败会抛异常// 将身份信息存储到SecurityContextHolderSecurityContextHolder.getContext().setAuthentication(rememberMeAuth);// 发布认证成功事件onSuccessfulAuthentication(request, response, rememberMeAuth);if (logger.isDebugEnabled()) {logger.debug("SecurityContextHolder populated with remember-me token: '"+ SecurityContextHolder.getContext().getAuthentication()+ "'");}// Fire eventif (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 {// 如果SecurityContextHolder中有认证信息,说明已经认证过了,则打印日志并直接放行if (logger.isDebugEnabled()) {logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"+ SecurityContextHolder.getContext().getAuthentication() + "'");}chain.doFilter(request, response);}}

从上面源码我们可以知道,核心方法是rememberMeServices.autoLogin(),它会返回一个Authentication实现类对象,并交给AuthenticationManager进行认证,AuthenticationManager认证我已经在之前的博客详细讲过,这里就不加赘述了,因此这里我们着重了解一下rememberMeServices,rememberMeServices是RememberMeServices实现类对象,我们可以看看RememberMeServices接口的源码:

public interface RememberMeServices {/*** 每当SecurityContextHolder不包含身份验证对象,并且Spring Security希望为实现提供* 使用记忆功能对请求进行身份验证的机会时就会调用此方法。Spring Security不会试图确定* 浏览器是否请求了rememberMe服务或提供了有效的cookie。此类决定留待实施。如果浏览器出于* 任何原因提供了未经授权的cookie,则应忽略它*/Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);/*** 每当尝试进行自动登录时,用户提供的凭据丢失或无效时调用* 实现应该使HttpServletRequest中指示的所有member-me令牌无效。*/void loginFail(HttpServletRequest request, HttpServletResponse response);/*** 自动登录成功时的回调* 实现可以在HttpServletResponse中自动设置remember-me令牌,不过不建议这样做。* 相反,实现通常应该寻找一个请求参数,该参数指示浏览器已提出明确的身份验证请求*/void loginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication);
}

该接口有如下实现类:

至于默认使用的是哪一个实现类,我们可以通过打断点调试,可以看到,我们进入了AbstractRememberMeServices抽象类

AbstractRememberMeServices抽象类的类图如下:

我们可以通过计算,看默认是由哪个子类进行实现的,可见默认是由TokenBasedRememberMeServices实现的


我们进入autoLogin()方法,以下是其具体实现:

@Overridepublic final Authentication autoLogin(HttpServletRequest request,HttpServletResponse response) {// 获取请求中的rememberMeCookie,通过debug计算可以知道默认是找remember-me字段String rememberMeCookie = extractRememberMeCookie(request);// 如果没有名为remember-me(默认是这个,可以自定义)的字段,则直接认证错误if (rememberMeCookie == null) {return null;}logger.debug("Remember-me cookie detected");// 如果cookie有指定字段,但指定字段没有值,也同样认证错误if (rememberMeCookie.length() == 0) {logger.debug("Cookie was empty");// 在响应上设置一个“cancel cookie”(maxAge = 0)以禁用持久登录。cancelCookie(request, response);return null;}UserDetails user = null;try {// 对cookie进行解码,并使用“:”分隔符将其分割为字符串数组返回String[] cookieTokens = decodeCookie(rememberMeCookie);// 这是抽象方法,由子类实现,默认是实现子类是TokenBasedRememberMeServices// 该方法是通过验证cookie来进行身份认证的,具体处理见子类,下文会分析user = processAutoLoginCookie(cookieTokens, request, response);userDetailsChecker.check(user);logger.debug("Remember-me cookie accepted");return createSuccessfulAuthentication(request, user);}catch (CookieTheftException cte) {cancelCookie(request, response);throw cte;}catch (UsernameNotFoundException noUser) {logger.debug("Remember-me login was valid but corresponding user not found.",noUser);}catch (InvalidCookieException invalidCookie) {logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());}catch (AccountStatusException statusInvalid) {logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());}catch (RememberMeAuthenticationException e) {logger.debug(e.getMessage());}cancelCookie(request, response);return null;}

以下是processAutoLoginCookie()方法的源码分析

protected UserDetails processAutoLoginCookie(String[] cookieTokens,HttpServletRequest request, HttpServletResponse response) {// rememberMeCookie分割成的字符串数组应该有三个元素,即用户名、时间戳、keyif (cookieTokens.length != 3) {throw new InvalidCookieException("Cookie token did not contain 3"+ " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");}long tokenExpiryTime;try {// 获取cookie的有效日期,默认有效期是两周tokenExpiryTime = new Long(cookieTokens[1]);}catch (NumberFormatException nfe) {throw new InvalidCookieException("Cookie token[1] did not contain a valid number (contained '"+ cookieTokens[1] + "')");}// 如果cookie过期则抛出异常if (isTokenExpired(tokenExpiryTime)) {throw new InvalidCookieException("Cookie token[1] has expired (expired on '"+ new Date(tokenExpiryTime) + "'; current time is '" + new Date()+ "')");}// 检查用户是否存在// 将查找用户信息放在检查过期时间之后,已尽量减少耗时的数据库调用。// 根据用户名从数据库中查找用户信息UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);// 断言,如果用户不存在,则报错Assert.notNull(userDetails, () -> "UserDetailsService " + getUserDetailsService()+ " returned null for username " + cookieTokens[0] + ". "+ "This is an interface contract violation");// 检查令牌的签名是否与数据库详细信息匹配。// 如果效率是一个主要问题,只需添加一个UserCache实现// 但回想一下,此方法通常在每个HttpSession中只调用一次,// 因为如果令牌有效,它将导致SecurityContextHolder填充,而如果无效,将导致删除cookie// 根据过期时间、用户名、用户密码做签名,生成令牌// 从makeTokenSignature源码我们可以知道,它是把下面的data做了一次md5加密// String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();String expectedTokenSignature = makeTokenSignature(tokenExpiryTime,userDetails.getUsername(), userDetails.getPassword());// 如果计算的令牌跟客户端传过来的令牌不一样,则说明客户端令牌被篡改过,或者是伪造的if (!equals(expectedTokenSignature, cookieTokens[2])) {throw new InvalidCookieException("Cookie token[2] contained signature '"+ cookieTokens[2] + "' but expected '" + expectedTokenSignature + "'");}// 如果走到这一步,就说明认证通过return userDetails;}

至此,RememberMe原理分析就结束了,以下是总结:当用户通过用户名/密码的形式登录成功后,系统会根据用户的用户名、密码以及令牌的过期时间计算出一个签名,这个签名使用MD5消息摘要算法生成,是不可逆的。然后再将用户名、令牌过期时间以及签名拼接成一个字符串, 中间用“:” 隔开,对拼接好的字符串进行Base64编码,然后将编码后的结果返回到前端,也就是我们在浏览器中看到的令牌。当会话过期之后,访问系统资源时会自动携带上Cookie中的令牌,服务端拿到Cookie中的令牌
后,先进行Base64解码,解码后分别提取出令牌中的三项数据,接着根据令牌中的数据判断令牌是否已经过期,如果没有过期,则根据令牌中的用户名查询出用户信息,接着再计算出一个签名和令牌中的签名进行对比,如果一致,表示会牌是合法令牌,自动登录成功,否则自动登录失败。

四:提高安全性

上面这套RememberMe认证其实是有安全风险的,因为cookie是保存在浏览器中的,如果当前用户的浏览器被植入病毒,cookie被窃取,那么他们也是可以将这个cookie设置到自己的浏览器进行登录的,那如何提高安全性呢。

之前我们已经分析到,RememberMeServices接口是由AbstractRememberMeServices抽象类实现,而该抽象类有两个子类,即TokenBasedRememberMeServicesPersistentTokenBasedRememberMeServices,默认是采用TokenBasedRememberMeServices,而PersistentTokenBasedRememberMeServices就是进一步提高安全性而生的

我们可以看看它对登录成功后的处理:

 protected void onLoginSuccess(HttpServletRequest request,HttpServletResponse response, Authentication successfulAuthentication) {// 获取用户名String username = successfulAuthentication.getName();logger.debug("Creating new persistent login for user " + username);// 生成持久化令牌PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, generateSeriesData(), generateTokenData(), new Date());try {tokenRepository.createNewToken(persistentToken);// 把生成的令牌写回cookieaddCookie(persistentToken, request, response);}catch (Exception e) {logger.error("Failed to save persistent token ", e);}}

既然登录成功后做了特殊的处理,那么我们就可以看看登录后的cookie认证有什么变化,以下是PersistentTokenBasedRememberMeServicesprocessAutoLoginCookie()方法的实现:

 protected UserDetails processAutoLoginCookie(String[] cookieTokens,HttpServletRequest request, HttpServletResponse response) {// cookie长度从3变成了2if (cookieTokens.length != 2) {throw new InvalidCookieException("Cookie token did not contain " + 2+ " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");}// cookie第一段是onLoginSuccess方法写回的信息final String presentedSeries = cookieTokens[0];final String presentedToken = cookieTokens[1];// 根据series去内存查询出一个PersistentRememberMeToken对象PersistentRememberMeToken token = tokenRepository.getTokenForSeries(presentedSeries);// 如果查询出来的对象是null,表示内存中并没有series对应的值,本次登录失败if (token == null) {// No series match, so we can't authenticate using this cookiethrow new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);}// 如果查询出来的token和客户端cookie解析出来的token不一样// 则说明会话令牌泄漏(恶意用户利用令牌登录后,内存中的token变了)if (!presentedToken.equals(token.getTokenValue())) {// 移除当前用户的所有自动登录记录tokenRepository.removeUserTokens(token.getUsername());// 抛出CookieTheftException异常,即cookie泄漏异常throw new CookieTheftException(messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen","Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));}// 检查令牌是否过期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对象,用户名和series不变,token重新生成,date使用当前时间PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), generateTokenData(), new Date());try {// newToken生成后,根据series去修改内存中的token和date(即每次登录都会产生新的token和date)tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),newToken.getDate());// 添加cookieaddCookie(newToken, request, response);}catch (Exception e) {logger.error("Failed to update token: ", e);throw new RememberMeAuthenticationException("Autologin failed due to data access problem");}return getUserDetailsService().loadUserByUsername(token.getUsername());}

因此,在持久化令牌方案中,最核心的是series和token两个值,这两个值都是用MD5散列计算生成的随机字符串。不同的是,series仅在用户使用密码重新登录时更新,而 token 会在每一个新的session会话中都重新生成。持久化令牌方案避免了散列加密方案中,一个令牌可以同时在多端登录的问题,这是因为每个session会话都会引发token的更新,即每个token仅支持单实例登录。其次,自动登录不会导致series变更,但每次自动登录都需要同时验证 series和 token两个值,所以这样的设计会更安全。因为当该令牌还未使用过自动登录就被盗取时,系统会在非法用户验证通过后刷新 token 值,此时在合法用户的浏览器中,该token值已经失效。当合法用户使用自动登录时,由于该series对应的 token 不同,系统可以推断该令牌可能已被盗用,从而做一些处理。例如,清理该用户的所有自动登录令牌,并通知该用户可能已被盗号等。

那要如何使用这种方式呢,其实很简单,只要配置一个rememberMeServices就可以了:

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers("/hello1").permitAll().anyRequest().authenticated().and().formLogin().successHandler(new CustomAuthenticationSuccessHandler()).failureHandler(new CustomAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new CustomLogoutSuccessHandler()).and().rememberMe().rememberMeServices(rememberMeServices());  // 指定rememberMeServices实现}@Beanpublic RememberMeServices rememberMeServices() {// 第三个参数是指定基于什么方式存储令牌,这里先使用默认的基于内存,后面我们会换成数据库return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),userDetailsService(),new InMemoryTokenRepositoryImpl());}

但事实上,这种做法并不绝对的安全,如果恶意程序在用户没有进行任何操作的时候获取到了cookie,也是可以登录账号的,这时用户本地的cookie就失效了,等用户再次上线时,会提示账号已异地登录,提示用户修改密码等,虽说可以提示用户,但是在用户上线之前,恶意用户还是可以随意进入用户账号的。因此,应该只对部分资源允许RememberMe访问:

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers("/hello1").permitAll().mvcMatchers("/hello2").rememberMe()  // 指定资源允许rememberMe().anyRequest().authenticated().and().formLogin().successHandler(new CustomAuthenticationSuccessHandler()).failureHandler(new CustomAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new CustomLogoutSuccessHandler()).and().rememberMe().rememberMeServices(rememberMeServices());}

五:令牌数据库的持久化

从上面源码分析我们知道。根据series去查询出PersistentRememberMeToken对象是默认是从内存中查找,因为tokenRepository默认是InMemoryTokenRepositoryImpl的对象,但是基于内存存储的话,一但应用重启,那么数据就会丢失,因此我们需要将其持久化存储到数据库中

我们可以看看PersistentTokenRepository的类图,可见Spring Security还提供了一种基于jdbc的实现,很明显这个实现可以持久化存储令牌

JdbcTokenRepositoryImpl部分源码如下:

public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implementsPersistentTokenRepository {/** 用于创建数据库表以存储令牌的默认SQL */public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "+ "token varchar(64) not null, last_used timestamp not null)";/** getTokenBySeries查询使用的默认SQL */public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";/** createNewToken使用的默认SQL */public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";/** updateToken使用的默认SQL */public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";/** removeUserTokens使用的默认SQL */public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";private String tokensBySeriesSql = DEF_TOKEN_BY_SERIES_SQL;private String insertTokenSql = DEF_INSERT_TOKEN_SQL;private String updateTokenSql = DEF_UPDATE_TOKEN_SQL;private String removeUserTokensSql = DEF_REMOVE_USER_TOKENS_SQL;private boolean createTableOnStartup;// 初始化时获取JdbcTemplateprotected void initDao() {if (createTableOnStartup) {getJdbcTemplate().execute(CREATE_TABLE_SQL);}}// 生成tokenpublic void createNewToken(PersistentRememberMeToken token) {getJdbcTemplate().update(insertTokenSql, token.getUsername(), token.getSeries(),token.getTokenValue(), token.getDate());}// 更新tokenpublic void updateToken(String series, String tokenValue, Date lastUsed) {getJdbcTemplate().update(updateTokenSql, tokenValue, lastUsed, series);}/*** 为提供的series加载token数据* 如果发生错误,将报告错误并返回null(因为结果应该是失败的持久登录)*/public PersistentRememberMeToken getTokenForSeries(String seriesId) {try {return getJdbcTemplate().queryForObject(tokensBySeriesSql,(rs, rowNum) -> new PersistentRememberMeToken(rs.getString(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4)), seriesId);}catch (EmptyResultDataAccessException zeroResults) {if (logger.isDebugEnabled()) {logger.debug("Querying token for series '" + seriesId+ "' returned no results.", zeroResults);}}catch (IncorrectResultSizeDataAccessException moreThanOne) {logger.error("Querying token for series '" + seriesId+ "' returned more than one value. Series" + " should be unique");}catch (DataAccessException e) {logger.error("Failed to load token for series " + seriesId, e);}return null;}// 删除tokenpublic void removeUserTokens(String username) {getJdbcTemplate().update(removeUserTokensSql, username);}/*** 旨在方便调试。在initDao方法期间初始化类时,将创建persistent_tokens数据表。*/public void setCreateTableOnStartup(boolean createTableOnStartup) {this.createTableOnStartup = createTableOnStartup;}
}

我们可以看到,源码里提供了相应的sql语句,因此我们只要提供数据源就可以了:

    private final DataSource dataSource;public WebSecurityConfig(CustomUserDetailService customUserDetailService, DataSource dataSource) {this.dataSource = dataSource;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers("/hello1").permitAll().anyRequest().authenticated().and().formLogin().successHandler(new CustomAuthenticationSuccessHandler()).failureHandler(new CustomAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new CustomLogoutSuccessHandler()).and().rememberMe().rememberMeServices(rememberMeServices());}@Beanpublic RememberMeServices rememberMeServices() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);// 创建表结构,第一次启动时设为true,之后启动数据库就已经有表了,因此之后启动需要设为falsetokenRepository.setCreateTableOnStartup(true);return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),userDetailsService(),tokenRepository);}
}

但是我在测试的过程中,使用会报错,以下是报错,很明显setCreateTableOnStartup(true)并没有为我创建表,因此插入令牌失败,我也不知道我为什么,知道的大佬可以留言区教我

org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)]; nested exception is java.sql.SQLSyntaxErrorException: Table 'eyesspace.persistent_logins' doesn't exist

因此我知道手动创建表,建表语句源码已经给出,即下面这个:

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

创建成功后,将上面的tokenRepository.setCreateTableOnStartup(true)改为false,就可以成功插入了:

还有另一种简单的方式,直接在configure(HttpSecurity http)里配置就可以了,值得一提的是,这种方式的setCreateTableOnStartup是有效的,可以为我们创建表

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers("/hello1").permitAll().anyRequest().authenticated().and().formLogin().successHandler(new CustomAuthenticationSuccessHandler()).failureHandler(new CustomAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new CustomLogoutSuccessHandler()).and().rememberMe()
//                .rememberMeServices(rememberMeServices());.tokenRepository(persistentTokenRepository());}// 给jdbc指定数据源@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);tokenRepository.setCreateTableOnStartup(false);return tokenRepository;}

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔的个人空间

Spring Security(四) —— RememberMe相关推荐

  1. 使用Spring Security添加RememberMe身份验证

    我在" 将社交登录添加到Jiwhiz博客"中提到,RememberMe功能不适用于Spring Social Security. 好吧,这是因为该应用程序现在不通过用户名和密码对用 ...

  2. spring security http.rememberMe()使用和原理解析

    spring security http.rememberMe()使用和原理解析 文章目录 spring security http.rememberMe()使用和原理解析 转载请贴上本文链接 htt ...

  3. Spring Security实现RememberMe功能以及原理探究

    在大多数网站中,都会实现RememberMe这个功能,方便用户在下一次登录时直接登录,避免再次输入用户名以及密码去登录,下面,主要讲解如何使用Spring Security实现记住我这个功能以及深入源 ...

  4. Spring Security 的 RememberMe 详解 !!!!!

    目录 目录 一.介绍 二.基本使用 2.1 开启记住我 三.原理分析 3.1 页面参数 3.2 RememberMeServices 3.3 TokenBasedRememberMeServices ...

  5. 【Spring】12、Spring Security 四种使用方式

    spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: ...

  6. Spring Security(四) —— 核心过滤器源码分析

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...

  7. Spring Security 入门 Remember-Me 记住我功能

    用户选择了"记住我"成功登录后,将会把username.随机生成的序列号.生成的token存入一个数据库表中,同时将它们的组合生成一个cookie发送给客户端浏览器. 当没有登录的 ...

  8. Spring Security 之 Remember-Me (记住我)

    效果:在用户的session(会话)过期或者浏览器关闭后,应用程序仍能记住它.用户可选择是否被记住.(在登录界面选择) "记住"是什么意思? 就是下次你再访问的时候,直接进入系统, ...

  9. Spring Security(12)——Remember-Me功能

    Remember-Me功能 目录 1.1     概述 1.2     基于简单加密token的方法 1.3     基于持久化token的方法 1.4     Remember-Me相关接口和实现类 ...

最新文章

  1. 工作中不能学的6种人
  2. 系统学习Spring之Spring in action(二)
  3. Java集合篇:Map常用遍历方式 以及 性能对比
  4. 宝塔清mysql主从日志_宝塔面板Mysql主从日志文件mysql-bin文件清除方法
  5. AIOps智能化数据体系的构建及在字节跳动的实践
  6. monitor out
  7. SketchUp 建筑分析图制作国外教程
  8. 延迟秋招总结,什么工作可以月薪过万?
  9. Go:http request cancelled 服务端感知
  10. PC改变文档显示颜色,保护眼睛,缓解眼疲劳
  11. Footprint Analytics:多角度理解Layer 2生态:概念、扩容方案及代表项目
  12. 农村有人收旧房梁,一根100多,破木头有啥用?
  13. 系统传输过程中 中文点 · 对方无法解析的问题查找
  14. Tortoise commit提交模板配置
  15. 如何编写Python爬虫
  16. 我的世界服务器物品管道,我的世界漏斗管道怎么做 教你连接漏斗箱子
  17. python散点图中如何添加拟合线并显示拟合方程与R方?
  18. 用python实现生成验证码图片
  19. 再获殊荣 用友U9 cloud荣获“2022中国制造业云ERP状元奖”
  20. C++求N以内所有的质数

热门文章

  1. 联想小新air14 2021锐龙版和2020哪个好?有什么区别?评测对比
  2. python旋转矩阵90°_用Python旋转矩阵
  3. JavaMail发邮箱(多人发送,抄送多人,多附件发送)
  4. 取消Parallels Desktop与mac共享应用程序
  5. js 计算数组的总和
  6. 解密:古人八拜之交指的是哪八拜?
  7. ubuntu 3060显卡驱动+cuda+cudnn+pytorch+pycharm+vscode
  8. php 字符串函数 教程_php字符串函数_PHP教程
  9. IntelliJ IDEA开发最佳配置(已更新至2022版)
  10. 二进制乘法的底层实现