栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Boot+Spring Security 实现自动登录功能(实战+源码分析)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Boot+Spring Security 实现自动登录功能(实战+源码分析)

可以看到,这段 base64 字符串实际上用 : 隔开,分成了三部分:

  1. 第一段是用户名,这个无需质疑。

  2. 第二段看起来是一个时间戳,我们通过在线工具或者 Java 代码解析后发现,这是一个两周后的数据。

  3. 第三段我就不卖关子了,这是使用 MD5 散列函数算出来的值,他的明文格式是 username + “:” + tokenExpiryTime + “:” + password + “:” + key,最后的 key 是一个散列盐值,可以用来防止令牌被修改。

了解到 cookie 中 remember-me 的含义之后,那么我们对于记住我的登录流程也就很容易猜到了了。

在浏览器关闭后,并重新打开之后,用户再去访问 hello 接口,此时会携带着 cookie 中的 remember-me 到服务端,服务到拿到值之后,可以方便的计算出用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将计算出的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效。

流程就是这么个流程,接下来我们通过分析源码来验证一下这个流程对不对。

[](

)3.源码分析

=========================================================================

接下来,我们通过源码来验证一下我们上面说的对不对。

这里主要从两个方面来介绍,一个是 remember-me 这个令牌生成的过程,另一个则是它解析的过程。

[](

)3.1 生成

=========================================================================

生成的核心处理方法在:TokenbasedRememberMeServices#onLoginSuccess:

@Override

public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,

Authentication successfulAuthentication) {

String username = retrieveUserName(successfulAuthentication);

String password = retrievePassword(successfulAuthentication);

if (!StringUtils.hasLength(password)) {

UserDetails user = getUserDetailsService().loadUserByUsername(username);

password = user.getPassword();

}

int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);

long expiryTime = System.currentTimeMillis();

expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);

String signaturevalue = makeTokenSignature(expiryTime, username, password);

setcookie(new String[] { username, Long.toString(expiryTime), signaturevalue },

tokenLifetime, request, response);

}

protected String makeTokenSignature(long tokenExpiryTime, String username,

String password) {

String data = username + “:” + tokenExpiryTime + “:” + password + “:” + getKey();

MessageDigest digest;

digest = MessageDigest.getInstance(“MD5”);

return new String(Hex.encode(digest.digest(data.getBytes())));

}

这段方法的逻辑其实很好理解:

  1. 首先从登录成功的 Authentication 中提取出用户名/密码。

  2. 由于登录成功之后,密码可能被擦除了,所以,如果一开始没有拿到密码,就再从 UserDetailsService 中重新加载用户并重新获取密码。

  3. 再接下来去获取令牌的有效期,令牌有效期默认就是两周。

  4. 再接下来调用 makeTokenSignature 方法去计算散列值,实际上就是根据 username、令牌有效期以及 password、key 一起计算一个散列值。如果我们没有自己去设置这个 key,默认是在 RememberMeConfigurer#getKey 方法中进行设置的,它的值是一个 UUID 字符串。

  5. 最后,将用户名、令牌有效期以及计算得到的散列值放入 cookie 中。

关于第四点,我这里再说一下。

由于我们自己没有设置 key,key 默认值是一个 UUID 字符串,这样会带来一个问题,就是如果服务端重启,这个 key 会变,这样就导致之前派发出去的所有 remember-me 自动登录令牌失效,所以,我们可以指定这个 key。指定方式如下:

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.anyRequest().authenticated()

.and()

.formLogin()

.and()

.rememberMe()

.key(“javaboy”)

.and()

.csrf().disable();

}

如果自己配置了 key,「即使服务端重启,即使浏览器打开再关闭」,也依然能够访问到 hello 接口。

这是 remember-me 令牌生成的过程。至于是如何走到 onLoginSuccess 方法的,大家可以参考松哥之前的文章:松哥手把手带你捋一遍 Spring Security 登录流程。这里可以给大家稍微提醒一下思路:

AbstractAuthenticationProcessingFilter#doFilter -> AbstractAuthenticationProcessingFilter#successfulAuthentication -> AbstractRememberMeServices#loginSuccess -> TokenbasedRememberMeServices#onLoginSuccess。

[](

)3.2 解析

=========================================================================

那么当用户关掉并打开浏览器之后,重新访问 /hello 接口,此时的认证流程又是怎么样的呢?

我们之前说过,Spring Security 中的一系列功能都是通过一个过滤器链实现的,RememberMe 这个功能当然也不例外。

Spring Security 中提供了 RememberMeAuthenticationFilter 类专门用来做相关的事情,我们来看下 RememberMeAuthenticationFilter 的 doFilter 方法:

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 = rememberMeServices.autoLogin(request,

response);

if (rememberMeAuth != null) {

rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

onSuccessfulAuthentication(request, response, rememberMeAuth);

if (this.eventPublisher != null) {

eventPublisher

.publishEvent(new InteractiveAuthenticationSuccessEvent(

SecurityContextHolder.getContext()

.getAuthentication(), this.getClass()));

}

if (successHandler != null) {

successHandler.onAuthenticationSuccess(request, response,

rememberMeAuth);

return;

}

}

chain.doFilter(request, response);

}

else {

chain.doFilter(request, response);

}

}

可以看到,就是在这里实现的。

这个方法最关键的地方在于,如果从 SecurityContextHolder 中无法获取到当前登录用户实例,那么就调用 rememberMeServices.autoLogin 逻辑进行登录,我们来看下这个方法:

public final Authentication autoLogin(HttpServletRequest request,

HttpServletResponse response) {

String rememberMecookie = extractRememberMecookie(request);

if (rememberMecookie == null) {

return null;

}

logger.debug(“Remember-me cookie detected”);

if (rememberMecookie.length() == 0) {

logger.debug(“cookie was empty”);

cancelcookie(request, response);

return null;

}

UserDetails user = null;

try {

String[] cookieTokens = decodecookie(rememberMecookie);

user = processAutoLogincookie(cookieTokens, request, response);

userDetailsChecker.check(user);

logger.debug(“Remember-me cookie accepted”);

return createSuccessfulAuthentication(request, user);

}

catch (

【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取

cookieTheftException cte) {

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/358208.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号