Spring Security(四) —— RememberMe
一:简介
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
抽象类实现,而该抽象类有两个子类,即TokenBasedRememberMeServices
和PersistentTokenBasedRememberMeServices
,默认是采用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认证有什么变化,以下是PersistentTokenBasedRememberMeServices
对processAutoLoginCookie()
方法的实现:
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相关推荐
- 使用Spring Security添加RememberMe身份验证
我在" 将社交登录添加到Jiwhiz博客"中提到,RememberMe功能不适用于Spring Social Security. 好吧,这是因为该应用程序现在不通过用户名和密码对用 ...
- spring security http.rememberMe()使用和原理解析
spring security http.rememberMe()使用和原理解析 文章目录 spring security http.rememberMe()使用和原理解析 转载请贴上本文链接 htt ...
- Spring Security实现RememberMe功能以及原理探究
在大多数网站中,都会实现RememberMe这个功能,方便用户在下一次登录时直接登录,避免再次输入用户名以及密码去登录,下面,主要讲解如何使用Spring Security实现记住我这个功能以及深入源 ...
- Spring Security 的 RememberMe 详解 !!!!!
目录 目录 一.介绍 二.基本使用 2.1 开启记住我 三.原理分析 3.1 页面参数 3.2 RememberMeServices 3.3 TokenBasedRememberMeServices ...
- 【Spring】12、Spring Security 四种使用方式
spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: ...
- Spring Security(四) —— 核心过滤器源码分析
摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...
- Spring Security 入门 Remember-Me 记住我功能
用户选择了"记住我"成功登录后,将会把username.随机生成的序列号.生成的token存入一个数据库表中,同时将它们的组合生成一个cookie发送给客户端浏览器. 当没有登录的 ...
- Spring Security 之 Remember-Me (记住我)
效果:在用户的session(会话)过期或者浏览器关闭后,应用程序仍能记住它.用户可选择是否被记住.(在登录界面选择) "记住"是什么意思? 就是下次你再访问的时候,直接进入系统, ...
- Spring Security(12)——Remember-Me功能
Remember-Me功能 目录 1.1 概述 1.2 基于简单加密token的方法 1.3 基于持久化token的方法 1.4 Remember-Me相关接口和实现类 ...
最新文章
- 工作中不能学的6种人
- 系统学习Spring之Spring in action(二)
- Java集合篇:Map常用遍历方式 以及 性能对比
- 宝塔清mysql主从日志_宝塔面板Mysql主从日志文件mysql-bin文件清除方法
- AIOps智能化数据体系的构建及在字节跳动的实践
- monitor out
- SketchUp 建筑分析图制作国外教程
- 延迟秋招总结,什么工作可以月薪过万?
- Go:http request cancelled 服务端感知
- PC改变文档显示颜色,保护眼睛,缓解眼疲劳
- Footprint Analytics:多角度理解Layer 2生态:概念、扩容方案及代表项目
- 农村有人收旧房梁,一根100多,破木头有啥用?
- 系统传输过程中 中文点 · 对方无法解析的问题查找
- Tortoise commit提交模板配置
- 如何编写Python爬虫
- 我的世界服务器物品管道,我的世界漏斗管道怎么做 教你连接漏斗箱子
- python散点图中如何添加拟合线并显示拟合方程与R方?
- 用python实现生成验证码图片
- 再获殊荣 用友U9 cloud荣获“2022中国制造业云ERP状元奖”
- C++求N以内所有的质数
热门文章
- 联想小新air14 2021锐龙版和2020哪个好?有什么区别?评测对比
- python旋转矩阵90°_用Python旋转矩阵
- JavaMail发邮箱(多人发送,抄送多人,多附件发送)
- 取消Parallels Desktop与mac共享应用程序
- js 计算数组的总和
- 解密:古人八拜之交指的是哪八拜?
- ubuntu 3060显卡驱动+cuda+cudnn+pytorch+pycharm+vscode
- php 字符串函数 教程_php字符串函数_PHP教程
- IntelliJ IDEA开发最佳配置(已更新至2022版)
- 二进制乘法的底层实现