文章目录

  • *RememberMe*
    • *6.1RememberMe简介*
    • *6.2RememberMe基本用法*
    • *6.3持久化令牌*
    • *6.4二次校验*
    • *6.5原理分析*
      • *`AbstractRememberMeServices`*
        • *`TokenBasedRememberMeServices`*
        • *`PersistentTokenBasedRememberMeServices`*

RememberMe

6.1RememberMe简介

RememberMe具体的实现思路就是通过cookie来记录当前用户身份。当用户登录成功之后,会通过一定的算法,将用户信息、时间戳等进行加密,加密完成后,通过响应头带回前端存储在cookie中,当浏览器关闭之后重新打开,如果再次访问该网站,会自动将cookie中的信息发送给服务器,服务器对cookie中的信息进行校验分析,进而确定出用户的身份,cookie中所保存的用户信息也是有时效的,例如三天、一周等。
由于相关信息存放在前端,因此存在着一定的安全隐患,不过可以通过持久化令牌以及二次校验来降低使用rememberMe所带来的安全风险。

6.2RememberMe基本用法

/*** 浏览器关闭或者服务器重启不需要重新登录。第一次进行表单登录时,多了一个请求参数remember-me: on,用来告诉服务端是否开启* RememberMe功能。* 如果自定义登录页面,那么默认情况下,是否开启RememberMe的参数就是remember-me。当请求成功后,在响应头中会多出一个* Set-Cookie,并携带remember-me字符串。以后所有请求的请求头Cookie字段,都会自动携带上这个令牌,服务端利用该令牌* 可以校验用户身份是否合法。*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("123").roles("admin");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and()// 最终会向过滤器链中添加RememberMeAuthenticationFilter过滤器.rememberMe().key("javaboy").and().csrf().disable();}
}

不过需要注意的是,这种方式隐患很大,一旦remember-me令牌泄露,恶意用户就可以拿着这个令牌去随意访问系统资源,持久化令牌和二次校验可以在一定程度上降低该问题带来的风险

6.3持久化令牌

持久化令牌在普通令牌的基础上,新增了seriestoken两个校验参数,当使用用户名/密码的方式登录时,series才会自动更新;而一旦有了新的会话,token就会重新生成。
所以,如果令牌被人盗用,一旦对方基于remember-me登录成功后,就会生成新的token,自己的登录令牌就会失效,这样就能及时发现账户泄露并作出处理,比如清除自动登录令牌、通知用户账号泄露等。
Spring security中对于持久化令牌提供了两种实现:JdbcTokenRepositoryImplInMemoryTokenRepositoryImpl,前者是基于JdbcTemplate来操作数据库,后者则是操作存储在内存中的数据,不过后者的使用场景很少,因此主要介绍前者的相关配置。
首先需要一张表来记录令牌信息,创建表的SQL脚本在JdbcTokenRepositoryImpl类中的CREATE_TABLE_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)";

接下来,引入JdbcTemplate和mysql依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>

之后配置好数据库信息即可。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredDataSource dataSource;/*** 提供JdbcTokenRepositoryImpl实例,并配置数据源*/@BeanJdbcTokenRepositoryImpl jdbcTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);return jdbcTokenRepository;}@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("123").roles("admin");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe()// 通过tokenRepository方法指定JdbcTokenRepositoryImpl实例.tokenRepository(jdbcTokenRepository()).and().csrf().disable();}
}

登录成功后,可以发现数据库表中多了一条记录。此时,如果关闭浏览器重新打开,再去访问/hello接口,访问时并不需要登录,但是访问成功之后,数据库中的token字段会发生变化。同时,如果服务端重启之后,浏览器再去访问/hello接口,依然不需要登录,但是token字段也会更新,因为这两种情况中都有新会话的建立,而series则不会更新。当然,如果用户注销登录,则数据库中和该用户相关的登录记录会自动清除。

6.4二次校验

二次校验就是将系统中的资源分为敏感的和不敏感的,如果用户使用了remember-me的方式登录,则访问敏感资源时会自动跳转到登录页面,要求用户重新登录;如果使用了用户名/密码的方式登录,则可以访问所有资源。

@RestController
public class HelloController {// 认证后可以访问,无论通过何种认证方式@GetMapping("/hello")public String hello() {return "hello";}// 必须通过用户名/密码方式认证后才可以访问@GetMapping("/admin")public String admin() {return "admin";}// 必须通过remember-me方式认证后才可以访问@GetMapping("/rememberme")public String rememberme() {return "rememberme";}
}@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {// 省略其他配置,同上@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 必须通过用户名/密码的方式认证后才可以访问.antMatchers("/admin").fullyAuthenticated()// 必须通过RememberMe的方式认证后才可以访问,至于/hello接口,认证后即可访问,无论通过何种认证方式.antMatchers("/rememberme").rememberMe().anyRequest().authenticated().and().formLogin().and().rememberMe().key("javaboy").tokenRepository(jdbcTokenRepository()).and().csrf().disable();}
}

6.5原理分析

RememberMeServices接口开始介绍:

public interface RememberMeServices {// 从请求中提取出需要的参数,完成自动登录功能Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);// 自动登录失败的回调void loginFail(HttpServletRequest request, HttpServletResponse response);// 自动登录成功的回调void loginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication);
}

NullRememberMeServices是一个空的实现,不做讨论。

AbstractRememberMeServices

AbstractRememberMeServices对于RememberMeServices接口中定义的方法提供了基本的实现。
首先来看autoLogin及其相关方法:

@Override
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {// 从cookie中提取出remember-me对应的值String rememberMeCookie = extractRememberMeCookie(request);if (rememberMeCookie == null) {return null;}// 如果remember-me对应的值长度为0,则在返回null之前,执行cancelCookie,将remember-me值置为nullif (rememberMeCookie.length() == 0) {cancelCookie(request, response);return null;}try {// 对获取到的令牌进行解析,还原之后的字符串分为三部分,彼此之间用":"隔开,第一部分是当前登录用户名,// 第二部分是时间戳,第三部分是一个签名,而浏览器看到的是一个Base64编码后的字符串String[] cookieTokens = decodeCookie(rememberMeCookie);// 对cookie进行验证,如果验证通过,则返回登录用户对象,然后对用户状态进行校验(账户是否可用、是否锁定等)UserDetails user = processAutoLoginCookie(cookieTokens, request, response);this.userDetailsChecker.check(user);// 创建登录成功的用户对象,其类型是RememberMeAuthenticationTokenreturn createSuccessfulAuthentication(request, user);}catch (CookieTheftException ex) {cancelCookie(request, response);throw ex;}catch (UsernameNotFoundException ex) {this.logger.debug("Remember-me login was valid but corresponding user not found.", ex);}catch (InvalidCookieException ex) {this.logger.debug("Invalid remember-me cookie: " + ex.getMessage());}catch (AccountStatusException ex) {this.logger.debug("Invalid UserDetails: " + ex.getMessage());}catch (RememberMeAuthenticationException ex) {this.logger.debug(ex.getMessage());}cancelCookie(request, response);return null;
}/*** 提取出需要的cookie信息,即remember-me对应的值。如果这个值为null,表示本次请求携带的cookie中没有remember-me,* 这次不需要自动登录,直接返回null即可*/
protected String extractRememberMeCookie(HttpServletRequest request) {Cookie[] cookies = request.getCookies();if ((cookies == null) || (cookies.length == 0)) {return null;}for (Cookie cookie : cookies) {if (this.cookieName.equals(cookie.getName())) {return cookie.getValue();}}return null;
}protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) {Cookie cookie = new Cookie(this.cookieName, null);cookie.setMaxAge(0);cookie.setPath(getCookiePath(request));if (this.cookieDomain != null) {cookie.setDomain(this.cookieDomain);}cookie.setSecure((this.useSecureCookie != null) ? this.useSecureCookie : request.isSecure());response.addCookie(cookie);
}protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) {RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, user,this.authoritiesMapper.mapAuthorities(user.getAuthorities()));auth.setDetails(this.authenticationDetailsSource.buildDetails(request));return auth;
}

再来看下自动登录成功和自动登录失败的回调:

@Override
public final void loginFail(HttpServletRequest request, HttpServletResponse response) {// 取消cookie的设置cancelCookie(request, response);// 调用onLoginFail方法(空方法)onLoginFail(request, response);
}// 一般来说不需要重写
protected void onLoginFail(HttpServletRequest request, HttpServletResponse response) {}@Override
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication) {// 判断当前请求是否开启了自动登录if (!rememberMeRequested(request, this.parameter)) {return;}onLoginSuccess(request, response, successfulAuthentication);
}protected abstract void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication);// 根据前端参数判断是否是remember-me请求,也可以在服务端配置,这样无论前端参数是什么,都会开启自动登录
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {if (this.alwaysRemember) {return true;}String paramValue = request.getParameter(parameter);if (paramValue != null) {if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {return true;}}return false;
}

最后再来看看AbstractRememberMeServices中一个比较重要的方法setCookie,在自动登录成功后,将调用该方法把令牌信息放入响应头中并最终返回到前端:

protected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) {String cookieValue = encodeCookie(tokens);// 配置cookieCookie cookie = new Cookie(this.cookieName, cookieValue);cookie.setMaxAge(maxAge);cookie.setPath(getCookiePath(request));if (this.cookieDomain != null) {cookie.setDomain(this.cookieDomain);}if (maxAge < 1) {cookie.setVersion(1);}cookie.setSecure((this.useSecureCookie != null) ? this.useSecureCookie : request.isSecure());cookie.setHttpOnly(true);// 将cookie对象放入到响应头中response.addCookie(cookie);
}// 将数组中的数据拼接成一个字符串并用":"隔开,然后对其进行Base64编码
protected String encodeCookie(String[] cookieTokens) {StringBuilder sb = new StringBuilder();for (int i = 0; i < cookieTokens.length; i++) {try {sb.append(URLEncoder.encode(cookieTokens[i], StandardCharsets.UTF_8.toString()));}catch (UnsupportedEncodingException ex) {this.logger.error(ex.getMessage(), ex);}if (i < cookieTokens.length - 1) {sb.append(DELIMITER);}}String value = sb.toString();sb = new StringBuilder(new String(Base64.getEncoder().encode(value.getBytes())));while (sb.charAt(sb.length() - 1) == '=') {sb.deleteCharAt(sb.length() - 1);}return sb.toString();
}
TokenBasedRememberMeServices

TokenBasedRememberMeServicesAbstractRememberMeServices中所定义的两个抽象方法processAutoLoginCookieonLoginSuccess做出了相应的实现。

// 验证cookie中的令牌信息是否合法
@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,HttpServletResponse response) {// 判断cookieTokens长度是否为3,不为3说明格式不对,则直接抛出异常if (cookieTokens.length != 3) {throw new InvalidCookieException("Cookie token did not contain 3" + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");}// 提取出过期时间,判断令牌是否过期,如果已经过期,则抛出异常long tokenExpiryTime = getTokenExpiryTime(cookieTokens);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]);// 生成一个签名,将用户名、令牌过期时间、用户密码以及key组成一个字符串,中间用":"隔开,然后通过MD5消息摘要算法// 对该字符串进行加密String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),userDetails.getPassword());// 判断生成的签名和通过cookie传来的签名是否相等,如果不相等则抛出异常if (!equals(expectedTokenSignature, cookieTokens[2])) {throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]+ "' but expected '" + expectedTokenSignature + "'");}return userDetails;
}@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication) {// 获取用户名和密码String username = retrieveUserName(successfulAuthentication);String password = retrievePassword(successfulAuthentication);// 如果找不到用户名和密码,只需中止,因为在这种情况下,TokenBasedMemberMeservices无法构造有效的令牌if (!StringUtils.hasLength(username)) {return;}if (!StringUtils.hasLength(password)) {UserDetails user = getUserDetailsService().loadUserByUsername(username);password = user.getPassword();if (!StringUtils.hasLength(password)) {return;}}// 计算令牌的过期时间,默认有效期是两周int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);long expiryTime = System.currentTimeMillis();// SEC-949expiryTime += 1000L * ((tokenLifetime < 0) ? TWO_WEEKS_S : tokenLifetime);// 根据令牌的过期时间、用户名、密码计算出一个签名String signatureValue = makeTokenSignature(expiryTime, username, password);// 设置cookiesetCookie(new String[] { username, Long.toString(expiryTime), signatureValue }, tokenLifetime, request,response);
}
  • 当用户通过用户名/密码的形式登录成功后,系统会根据用户的用户名、密码以及令牌的过期时间计算出一个签名,这个签名使用MD5消息摘要算法生成,是不可逆的。
  • 然后再将用户名、令牌过期时间以及签名拼接成一个字符串,中间用":"隔开,对拼接好的字符串进行Base64编码,然后将编码后的结果返回到前端,也就是在浏览器中看到的令牌。
  • 当用户关闭浏览器再次打开,访问系统资源时会自动携带上cookie中的令牌,服务端拿到cookie中的令牌后,先进行Base64解码,解码后分别提取出令牌中的三项数据。
  • 接着根据令牌中的数据判断令牌是否已经过期,如果没有过期,则根据令牌中的用户名查询出用户信息。
  • 接着再计算出一个签名和令牌中的签名进行对比,如果一致,表示令牌是合法令牌,自动登录成功,否则自动登录失败。
PersistentTokenBasedRememberMeServices

在持久化令牌中,存储在数据库中的数据被封装成了一个对象PersistentRememberMeToken

public class PersistentRememberMeToken {// 登录用户名private final String username;// 自动生成private final String series;// 自动生成private final String tokenValue;// 上次使用时间private final Date date;// 省略getter/setter
}

PersistentTokenBasedRememberMeServices里边重要的方法也是processAutoLoginCookieonLoginSuccess

@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,HttpServletResponse response) {// 第一项是series,第二项是tokenif (cookieTokens.length != 2) {throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '"+ Arrays.asList(cookieTokens) + "'");}String presentedSeries = cookieTokens[0];String presentedToken = cookieTokens[1];// 根据series去数据库中查询出一个PersistentRememberMeToken对象PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);if (token == null) {// 没有series匹配,所以我们无法使用此cookie进行身份验证throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);}// 如果token不相同,说明自动登录令牌已经泄露(恶意用户利用令牌登录后,数据库中的token发生变化了)if (!presentedToken.equals(token.getTokenValue())) {// 令牌和series值不匹配。删除该用户的所有登录信息,并抛出一个异常来警告他们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."));}// 判断token是否过期if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {throw new RememberMeAuthenticationException("Remember-me login has expired");}// token也匹配,所以登录是有效的。更新token值,保持相同的seriesPersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(),generateTokenData(), new Date());try {// 根据series去修改数据库中的token和datethis.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());addCookie(newToken, request, response);}catch (Exception ex) {throw new RememberMeAuthenticationException("Autologin failed due to data access problem");}return getUserDetailsService().loadUserByUsername(token.getUsername());
}private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) {setCookie(new String[] { token.getSeries(), token.getTokenValue() }, getTokenValiditySeconds(), request,response);
}@Override
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,Authentication successfulAuthentication) {String username = successfulAuthentication.getName();this.logger.debug(LogMessage.format("Creating new persistent login for user %s", username));PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, generateSeriesData(),generateTokenData(), new Date());try {this.tokenRepository.createNewToken(persistentToken);addCookie(persistentToken, request, response);}catch (Exception ex) {this.logger.error("Failed to save persistent token ", ex);}
}

PersistentTokenBasedRememberMeServicesTokenBasedRememberMeServices还是有一些明显的区别的:前者返回给前端的令牌是将seriestoken组成的字符串进行base64编码后返回给前端;后者返回给前端的令牌则是将用户名、过期时间以及签名组成的字符串进行base64编码后返回给前端。
那么,RememberMeServices是在何时被调用的呢?
当开发者配置.rememberMe().key("javaboy")时,实际上是引入了配置类RememberMeConfigurer,其中最重要的就是initconfigure方法:

@Override
public void init(H http) throws Exception {// 验证rememberMeServices和rememberMeCookieName没有同时设置validateInput();// 获取配置的key,如果没有配置,则会自动生成一个UUID字符串。如果开发者使用普通的remember-me,// 即没有使用持久化令牌,则建议自行配置该key,因为使用默认的UUID字符串,系统每次重启都会生成新的key,// 会导致之前下发的remember-me失效String key = getKey();// 如果开发者配置了tokenRepository,则获取到的实例是PersistentTokenBasedRememberMeServices,// 否则获取到TokenBasedRememberMeServicesRememberMeServices rememberMeServices = getRememberMeServices(http, key);http.setSharedObject(RememberMeServices.class, rememberMeServices);LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);if (logoutConfigurer != null && this.logoutHandler != null) {logoutConfigurer.addLogoutHandler(this.logoutHandler);}// 配置RememberMeAuthenticationProvider实例,其主要用来校验keyRememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(key);authenticationProvider = postProcess(authenticationProvider);http.authenticationProvider(authenticationProvider);initDefaultLoginFilter(http);
}@Override
public void configure(H http) {// 创建RememberMeAuthenticationFilter,同时传入RememberMeServices实例RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(http.getSharedObject(AuthenticationManager.class), this.rememberMeServices);if (this.authenticationSuccessHandler != null) {rememberMeFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);}// 加入到spring容器中rememberMeFilter = postProcess(rememberMeFilter);// 加入到过滤器链中http.addFilter(rememberMeFilter);
}

再来看一下RememberMeAuthenticationFilterdoFilter方法是如何运作的:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {// 判断SecurityContextHolder中是否有值,没值的话表示用户尚未登录,此时调用autoLogin方法进行自动登录if (SecurityContextHolder.getContext().getAuthentication() != null) {chain.doFilter(request, response);return;}Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);// 不为空表示自动登录成功if (rememberMeAuth != null) {// 尝试通过AuthenticationManager进行身份验证try {// 调用RememberMeAuthenticationProvider的authenticate方法对key进行校验rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);// 存储到SecurityContextHolder对象中SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);// 调用登录成功回调(空方法)onSuccessfulAuthentication(request, response, rememberMeAuth);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 ex) {// 如果自动登录失败,则调用rememberMeServices.loginFail处理登录失败回调this.rememberMeServices.loginFail(request, response);// 空方法onUnsuccessfulAuthentication(request, response, ex);}}chain.doFilter(request, response);
}

这就是RememberMeAuthenticationFilter过滤器所做的事情,成功将RememberMeServices的服务集成进来。
需要注意的是,RememberMeServices#loginSuccess方法的调用位置,是在AbstractAuthenticationProcessingFilter#successfulAuthentication中触发的,也就是说,无论是否开启了remember-me功能,该方法都会被调用。只不过在RememberMeServices#loginSuccess方法的具体实现中,会去判断是否开启了remember-me,进而决定是否在响应中添加对应的cookie。

6.Spring security中的rememberMe相关推荐

  1. Spring Security 中取得 RememberMe 的 cookie 值

    为什么80%的码农都做不了架构师?>>>    Spring Security 中的 RememberMe 对应的 cookie 名称是可配置的--相信一般情况下大家也不会使用那个默 ...

  2. spring security 学习三-rememberMe

    spring security 学习三-rememberMe 功能:登录时的"记住我"功能 原理: rememberMeAuthenticationFilter在security过 ...

  3. Spring Security中HttpSecurity常用方法及说明

    本文来说下spring security中HttpSecurity常用方法,这个类在spring security中使用的非常多,功能十分丰富,其中包含的方法也是非常多,在实际的开发中,需要重写里面的 ...

  4. 【Spring Security】的RememberMe功能流程与源码详解

    文章目录 前言 原理 基础版 搭建 初始化sql 依赖引入 配置类 验证 源码分析 进阶版 集成 源码分析 疑问1 疑问2 鉴权 升级版 集成 初始化sql 配置类 验证 源码分析 鉴权 流程 扩展版 ...

  5. Spring Security中文文档

    Spring Security中文文档 来源:https://www.springcloud.cc/spring-security.html#overall-architecture 作者 Ben A ...

  6. Spring Security 中最流行的权限管理模型!

    前面和大家说了 ACL,讲了理论,也给了一个完整的案例,相信小伙伴们对于 ACL 权限控制模型都已经比较了解了. 本文我要和大家聊一聊另外一个非常流行的权限管理模型,那就是 RBAC. 1.RBAC ...

  7. 一起搞清楚 Spring Security 中的 UserDetails

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 170元买400元书的机会又来啦! 1. 前言 前一篇介绍了 Spring Security ...

  8. springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...

    今日干货 刚刚发表查看:66666回复:666 公众号后台回复 ssm,免费获取松哥纯手敲的 SSM 框架学习干货. 上篇文章松哥和大家聊了什么是 CSRF 攻击,以及 CSRF 攻击要如何防御.主要 ...

  9. Http基本身份验证在Spring Security中如何工作?

    在上一篇文章中,您学习了如何在基于Spring安全性的Java应用程序中启用Http基本身份验证 ,现在,我们将进一步进一步了解http基本身份验证在Spring安全性中的工作原理. 如果您还记得的话 ...

最新文章

  1. 不丢失精度的获取照片的Gps经纬度
  2. Ubuntu 13.10 安装软件失败后出现的问题——已安装 post-installation 脚本 返回了错误号 1...
  3. 在当当买了python怎么下载源代码-Python爬取当当网APP数据
  4. 剑指offer之 调整奇数偶数数组位置
  5. 【Iphone 游戏开发】游戏引擎剖析
  6. e7xue.php漏洞_简要分析最近的dedecms通杀漏洞以及漏洞补丁的绕过
  7. C语言嵌入系统编程修炼-性能优化
  8. java程序设计_Java程序设计--接口interface(笔记)
  9. 贺:MSN-.NET 技术交流群荣登群首页
  10. 全渠道数字化营销平台
  11. 手机微信html整人代码大全,2018年微信整人代码有哪些?2018年微信整人代码大全!...
  12. win10更新完提示未安装任何音频输出设备2019-11-13解决
  13. 机器学习项目汇总,值得收藏!
  14. XUL使用中的常见错误
  15. windows之在局域网内共享和共同编辑EXCEL
  16. 2019.7.13--jzDay9
  17. 客户端开发 Windows驱动开发(1)SDK WDK DDK WDM的关系
  18. mac电脑删除多余输入法
  19. 如何将pip更新到最新版本?
  20. android打开另外的app两种方式,内置到自己本身的app,重新打开app,

热门文章

  1. java画球_我的世界 如何用指令画球 JAVA 1.13+
  2. DNF域名解析全过程
  3. 在计算机海洋里摸爬滚打搜集的一些资源
  4. openlayers摸爬滚打 5.openlayers使用GeoJSON绘制点、线
  5. 2016鄂教版小学信息技术初识计算机软件,鄂教版(2016)五年级全册信息技术 25.揭秘计算机工作世界--初识计算机工作原理 教案...
  6. java实现第六届蓝桥杯立方体自身
  7. 科技计算机作文200字,关于网络的作文200字(5篇)
  8. 程序员的核心竞争力是什么?为什么?
  9. 荣耀magic3会用鸿蒙,荣耀magic3怎么样-荣耀magic3配置分析
  10. 1w存银行一年多少利息_五百万存银行 一年利息有多少呢?