- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章SpringBoot 配合 SpringSecurity 实现自动登录功能的代码由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
自动登录是我们在软件开发时一个非常常见的功能,例如我们登录 QQ 邮箱:
很多网站我们在登录的时候都会看到类似的选项,毕竟总让用户输入用户名密码是一件很麻烦的事.
自动登录功能就是,用户在登录成功后,在某一段时间内,如果用户关闭了浏览器并重新打开,或者服务器重启了,都不需要用户重新登录了,用户依然可以直接访问接口数据 。
作为一个常见的功能,我们的 Spring Security 肯定也提供了相应的支持,本文我们就来看下 Spring Security 中如何实现这个功能.
。
。
为了配置方便,加入两个依赖即可:
配置类中添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Configuration
public
class
SecurityConfig
extends
WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return
NoOpPasswordEncoder.getInstance();
}
@Override
protected
void
configure(AuthenticationManagerBuilder auth)
throws
Exception {
auth.inMemoryAuthentication()
.withUser(
"yolo"
)
.password(
"123"
).roles(
"admin"
);
}
@Override
protected
void
configure(HttpSecurity http)
throws
Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.rememberMe()
.and()
.csrf().disable();
}
}
|
大家看到,这里只需要添加一个 .rememberMe() 即可,自动登录功能就成功添加进来了.
接下来我们随意添加一个测试接口:
1
2
3
4
5
6
7
|
@RestController
public
class
HelloController {
@GetMapping
(
"/hello"
)
public
String hello(){
return
"Hello Yolo !!!"
;
}
}
|
这个时候大家发现,默认的登录页面多了一个选项,就是记住我。我们输入用户名密码,并且勾选上记住我这个框,然后点击登录按钮执行登录操作.
可以看到,登录数据中,除了 username 和 password 之外,还有一个 remember-me,之所以给大家看这个,是想告诉大家,如果你你需要自定义登录页面,RememberMe 这个选项的 key 该怎么写.
登录成功之后,就会自动跳转到 hello 接口了。我们注意,系统访问 hello 接口的时候,携带的 cookie:
大家注意到,这里多了一个 remember-me,这就是这里实现的核心,关于这个 remember-me 我一会解释,我们先来测试效果.
接下来,我们关闭浏览器,再重新打开浏览器。正常情况下,浏览器关闭再重新打开,如果需要再次访问 hello 接口,就需要我们重新登录了。但是此时,我们再去访问 hello 接口,发现不用重新登录了,直接就能访问到,这就说明我们的 RememberMe 配置生效了(即下次自动登录功能生效了).
。
。
按理说,浏览器关闭再重新打开,就要重新登录,现在竟然不用等了,那么这个功能到底是怎么实现的呢?
首先我们来分析一下 cookie 中多出来的这个 remember-me,这个值一看就是一个 Base64 转码后的字符串,我们可以使用网上的一些在线工具来解码,可以自己简单写两行代码来解码:
1
2
3
4
5
6
|
@Test
void
contextLoads() {
String s =
new
String(
Base64.getDecoder().decode(
"eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"
));
System.out.println(
"s = "
+ s);
}
|
执行这段代码,输出结果如下:
s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9 。
可以看到,这段 Base64 字符串实际上用 : 隔开,分成了三部分:
(1)第一段是用户名,这个无需质疑。 (2)第二段看起来是一个时间戳,我们通过在线工具或者 Java 代码解析后发现,这是一个两周后的数据。 (3)第三段我就不卖关子了,这是使用 MD5 散列函数算出来的值,他的明文格式是 username + ":" + tokenExpiryTime + ":" + password + ":" + key,最后的 key 是一个散列盐值,可以用来防治令牌被修改.
了解到 cookie 中 remember-me 的含义之后,那么我们对于记住我的登录流程也就很容易猜到了了.
在浏览器关闭后,并重新打开之后,用户再去访问 hello 接口,此时会携带着 cookie 中的 remember-me 到服务端,服务到拿到值之后,可以方便的计算出用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将计算出的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效.
流程就是这么个流程,接下来我们通过分析源码来验证一下这个流程对不对.
3、源码分析 。
接下来,我们通过源码来验证一下我们上面说的对不对.
这里主要从两个方面来介绍,一个是 remember-me 这个令牌生成的过程,另一个则是它解析的过程.
1. 生成 。
生成的核心处理方法在:TokenBasedRememberMeServices#onLoginSuccess:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@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。指定方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Override
protected
void
configure(HttpSecurity http)
throws
Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.rememberMe()
.key(
"yolo"
)
.and()
.csrf().disable();
}
|
如果自己配置了 key,即使服务端重启,即使浏览器打开再关闭,也依然能够访问到 hello 接口 。
这是 remember-me 令牌生成的过程。至于是如何走到 onLoginSuccess 方法的,这里可以给大家稍微提醒一下思路:
AbstractAuthenticationProcessingFilter#doFilter -> AbstractAuthenticationProcessingFilter#successfulAuthentication -> AbstractRememberMeServices#loginSuccess -> TokenBasedRememberMeServices#onLoginSuccess.
2. 解析 。
那么当用户关掉并打开浏览器之后,重新访问 /hello 接口,此时的认证流程又是怎么样的呢?
我们之前说过,Spring Security 中的一系列功能都是通过一个过滤器链实现的,RememberMe 这个功能当然也不例外.
Spring Security 中提供了 RememberMeAuthenticationFilter 类专门用来做相关的事情,我们来看下 RememberMeAuthenticationFilter 的 doFilter 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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 逻辑进行登录,我们来看下这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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
(CookieTheftException cte) {
throw
cte;
}
cancelCookie(request, response);
return
null
;
}
|
可以看到,这里就是提取出 cookie 信息,并对 cookie 信息进行解码,解码之后,再调用 processAutoLoginCookie 方法去做校验,processAutoLoginCookie 方法的代码我就不贴了,核心流程就是首先获取用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将拿到的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效,进而确认登录是否有效.
4、总结 。
看了上面的文章,大家可能已经发现,如果我们开启了 RememberMe 功能,最最核心的东西就是放在 cookie 中的令牌了,这个令牌突破了 session 的限制,即使服务器重启、即使浏览器关闭又重新打开,只要这个令牌没有过期,就能访问到数据.
一旦令牌丢失,别人就可以拿着这个令牌随意登录我们的系统了,这是一个非常危险的操作.
但是实际上这是一段悖论,为了提高用户体验(少登录),我们的系统不可避免的引出了一些安全问题,不过我们可以通过技术将安全风险降低到最小 。
到此这篇关于SpringBoot 配合 SpringSecurity 实现自动登录功能的代码的文章就介绍到这了,更多相关SpringSecurity自动登录内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/nanhuaibeian/article/details/108630616 。
最后此篇关于SpringBoot 配合 SpringSecurity 实现自动登录功能的代码的文章就讲到这里了,如果你想了解更多关于SpringBoot 配合 SpringSecurity 实现自动登录功能的代码的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我已经从 2.3.7 迁移到 grails 2.5.0我依赖 编译“:spring-security-core:2.0-RC5” 我将所有出现的 grails.plugins.springsecuri
考虑向 Controller 发出 GET 请求,然后 Controller 从数据库加载资源。想象数据库资源有一个标志。该标志设置资源是否是公共(public)的。如果资源标志为 true,则返回资
1.设置token的过期时间 如果我们是从数据库来读取客户端信息的话 我们只需要在数据库设置token的过期时间 1.1 oauth_client_details表每个列的作用 cli
前言 我们知道在项目开发中,后台开发权限认证是非常重要的,springboot 中常用熟悉的权限认证框架有,shiro,还有就是springboot 全家桶的 security当然他们各有各的好处,但
我有一个与 SpringSecurityCore 插件一起设置的基本 Grails 2 应用程序。这似乎工作正常。但是,当我尝试通过扩展类向我的 User.groovy 文件添加其他属性时,我似乎无法
我完成了一些教程并为自己构建了一个 Spring Security Login。但每当我登录时,我都会从日志中收到以下错误: 2015-08-12 21:39:21 DEBUG DriverManag
这是我的场景: 网络应用程序为许多应用程序执行某种 SSO 登录用户点击链接后,应用程序会向正确的应用程序发送包含用户信息(姓名、密码 [无用]、角色)的帖子 我正在其中一个应用程序上实现 Sprin
实现原理 在之前的文章中,我们介绍了普通的帐号密码登录的方式: SpringBoot + Spring Security 基本使用及个性化登录配置。 但是现在还有一种常见的方式,就是直接通过手机短
1、使用springboot+maven搭建一个多模块项目(可以参考这篇文章 --> 这里) 2、删除父工程的src文件,删除app、browser、core下的.java文
1、问题描述 在 SpringBoot 中加入 SpringSecurity 中之后,静态资源总是被过滤,导致界面很难看: 目录结构: 2、问题解决 正常不拦截资源,我查阅资料,基
我创建了两个模块 GWTAppAuth GWTApp 当我尝试从 GWTAppAuth 发布表格时至j_spring_security_check没啥事儿。 Firebug 在控制台中显示 "Fail
我是Grails和Spring Security的新手,我的目标是使用示例注册/登录/注销功能构建示例站点。注册已经准备就绪,现在我需要登录和注销。 我已经执行了s2-quickstart命令,并且具
在grails应用程序中,我成功获取了用户选择的语言(添加到url,“...?lang = xx_XX”),如下所示: def locale = RequestContextUtils.getLoca
我的successHandler deafultTargetUrl是这个 grails.plugin.springsecurity.successHandler.deafultTargetUrl =
如果他尝试访问安全资源,我如何配置 grails 不将人员/用户重定向到登录页面。我只想发送重定向到主页的 401 状态错误。 最佳答案 您要做的是配置authenticationEntryPoint
目前我正在使用以下方法将用户登录到我的应用程序。但是我想使用 Angular 函数来实际执行登录。为此,我想创建一个休息 Web 服务来进行身份验证,但是我在 SO 上看到的所有示例都使用我认为已折旧
所以我将 Spring Security 与 Spring Boot 结合使用。我想制作自己的 AuthenticationProvider,以我自己的方式使用数据库,所以我使用这个 authenti
我有一个现有的 Spring MVC 应用程序,当前未使用 SpringSecurity。我为Hibernate审计日志编写了一个AuditInterceptor,它需要一种方法来获取当前登录的用户。
我想自动登录到 Web 应用程序,该应用程序可在公司的 Intranet 上访问。 登录将以一种方式起作用,即当用户访问应用程序时,他将自动发送其凭据(用户名和密码),例如 (company.com/
很显然,@Autowired 不能在 UserDetailsService 中工作 这是真的吗?如果没有,我如何在我的 UserDetailsService 中 Autowiring ? 如果
我是一名优秀的程序员,十分优秀!