- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Spring Security基于散列加密方案实现自动登录功能由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在前面的2个章节中,一一哥 带大家实现了在Spring Security中添加图形验证码校验功能,其实Spring Security的功能不仅仅是这些,还可以实现很多别的效果,比如实现自动登录,注销登录等.
有的小伙伴会问,我们为什么要实现自动登录啊?这个需求其实还是很常见的,因为对于用户来说,他可能经常需要进行登录以及退出登录,你想想,如果用户每次登录时都要输入自己的用户名和密码,是不是很烦,用户体验是不是很不好?
所以为了提高项目的用户体验,我们可以在项目中添加自动登录功能,当然也要给用户提供退出登录的功能。接下来就跟着 一一哥 来学习如何实现这些功能吧! 。
。
我们在访问网站或app时,一般都会要求我们注册一个账号,包含用户名和密码信息,其中密码还会有长度及取值范围的限制。很多时候,我们在不同的网站上注册的账号,可能密码也不同,这就导致我们必须记住这些不同网站上的用户信息。那么在下次登录时,因为我们的密码太多了,很有可能会记不起这些账号密码。所以在几次尝试登录失败之后,很多人都会选择找回密码,从而再次陷入如何设置密码的循环里.
为了尽可能减少用户重新登录的频率,提高用户的使用体验,我们可以提供自动登录这样一个会给用户带来便利,同时也会给用户带来风险的体验性功能.
了解了自动登录出现的背景及作用后,那么我们该怎么实现自动登录呢?
首先我们知道,自动登录是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录状态的一种机制.
所以基于上面的原理,Spring Security 就为我们提供了两种比较好的实现自动登录的方案:
我上面提到的2个实现类,其实都是AbstractRememberMeServices的子类,如下图所示:
了解了这些核心API之后,我们就可以利用这两个API来实现自动登录了.
。
我先带各位利用第1种实现方案,即基于散列加密方案来实现自动登录.
首先我们还是在之前的案例基础之上进行开发,具体的项目创建过程略过,请参考之前的章节内容.
首先我们创建一个application.yml文件,在其中添加数据库配置,以及一个用来加密令牌的key字符串,字符串的值随便自定义就行.
spring:datasource: url: jdbc:mysql://localhost:3306/db-security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT username: root password: sycsecurity: remember-me: key: yyg
跟之前的案例一样,我还是要创建一个SecurityConfig类,在其中的configure(HttpSecurity http)方法中,通过JdbcTokenRepositoryImpl关联我们的数据库,并且通过rememberMe()方法开启“记住我”功能,另外还要把我们前面在配置文件中的rememberKey配置进来,作为散列加密的key.
@EnableWebSecurity(debug = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${spring.security.remember-me.key}") private String rememberKey; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { //利用JdbcTokenRepositoryImpl关联数据源 JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); http.authorizeRequests() .antMatchers("/admin/**") .hasRole("ADMIN") .antMatchers("/user/**") .hasRole("USER") .antMatchers("/app/**") .permitAll() .anyRequest() .authenticated() .and() .formLogin() .permitAll() .and() //开启“记住我”功能 .rememberMe() .userDetailsService(userDetailsService) //配置散列加密用的key .key(rememberKey) .and() .csrf() .disable(); } @Bean public PasswordEncoder passwordEncoder() { //不对登录密码进行加密 return NoOpPasswordEncoder.getInstance(); } }
为了方便后续的测试,我随便编写一个测试用的web接口.
@RestController@RequestMapping("/user")public class UserController { @GetMapping("hello") public String hello() { return "hello, user"; }}
然后我们把项目启动起来进行测试,当然你别忘了编写项目入口类,这里我就不粘贴相关代码了.
我们访问一下/user/hello接口,会先重定向到/login接口,这时候会发现在默认的登录页面上多了一个“记住我”功能.
此时如果我们打开 开发者调试工具,并且勾选“记住我”,然后发起请求,这时候我们会在控制台看到remember-me的cookie信息,说明Spring Security已经自动生成了remember-me这个cookie,且表单中的remember-me参数也处于了“on”状态.
也就是说,我们利用简单的几行代码,就实现了基于散列加密方案的自动登录.
。
你可能会很好奇,散列加密方案到底是怎么实现自动登录的呢?别急,接下来 壹哥就为你分析一下散列加密的实现原理.
我在前面给各位说过,自动登录其实就是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录状态的一种机制。所以在自动登录后,肯定会生成代表用户的cookie信息,但是为了安全,这个cookie肯定不会明文存储,需要把这个cookie进行加密处理,当然也会解码处理。所以接下来我就给各位分析一下这个cookie的加密和解码过程.
首先 壹哥 给各位解释一下所谓的散列加密算法,其实质就是把 username、expirationTime、password等字段,再加上自定义的key字段合并起来,在每个字段之间用 ":" 分隔,最后利用md5算法进行哈希运算,这样就可以得到一个加密后的字符串。Spring Security把这个加密的字符串存储到cookie中,作为用户已登录的标识信息.
然后 壹哥 带你看看TokenBasedRememberMeServices源码类中的makeTokenSignature()方法,你会看到散列加密算法的具体加密实现过程,源码如下图所示:
上面利用MD5进行了加密,用户在下次登录后,肯定需要进行信息的比对,以判断用户信息是否一致。Spring Security是先对cookie中的信息进行解码,然后与之前记录的登录信息进行比对,以此判断用户是否已登录.
Spring Security是在AbstractRememberMeServices类的decodeCookie()方法中,利用Base64对cookie进行解码,如下图所示:
对于以上2个源码方法,我们可以简化抽取出如下两行代码:
//对各字段进行散列加密hashInfo=md5Hex(username +":"+expirationTime +":"password+":"+key)//利用base64进行解码rememberCookie=base64(username+":"+expirationrime+":"+hashInfo)
其中,expirationTime是指本次自动登录的有效期,key是自己指定的一个散列盐值,用于防止令牌被修改。利用以上两个 。
分析完源码之后,壹哥给各位简单总结一下cookie的生成验证原理:
上面分析完cookie信息的加密和解码之后,接下来我再结合源码,从两个方面来介绍自动登录的实现过程,一个是 remember-me 令牌的生成的过程,另一个则是该令牌的解析过程.
我们要想知道源码中是如何生成remember-me自动登录令牌的,首先得知道Spring Security是如何进入到该令牌所在代码的,这个代码的执行与我们前一章节所讲的Spring Security的认证授权有关,请进入到前面查看.
AbstractAuthenticationProcessingFilter#doFilter -> AbstractAuthenticationProcessingFilter#successfulAuthentication -> AbstractRememberMeServices#loginSuccess -> TokenBasedRememberMeServices#onLoginSuccess 。
这个令牌生成的核心处理方法定义在:TokenBasedRememberMeServices#onLoginSuccess.
@Overridepublic 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(); } //获取登录过期时间,默认是2周 int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication); long expiryTime = System.currentTimeMillis(); expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime); //生成remember-me签名信息 String signatureValue = makeTokenSignature(expiryTime, username, password); //保存cookie 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())));}
以上源码的实现逻辑很好理解:
对于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) { // Attempt authenticaton via AuthenticationManager try { rememberMeAuth = authenticationManager.authenticate(rememberMeAuth); // Store to SecurityContextHolder SecurityContextHolder.getContext().setAuthentication(rememberMeAuth); onSuccessfulAuthentication(request, response, rememberMeAuth); if (logger.isDebugEnabled()) { logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } // Fire event if (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 { if (logger.isDebugEnabled()) { logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } chain.doFilter(request, response); } }
这个方法最关键的地方在于,如果从 SecurityContextHolder 中无法获取到当前登录用户实例,那么就调用 rememberMeServices.autoLogin()逻辑进行登录,我们来看下这个方法:
@Override 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); } ...... cancelCookie(request, response); return null; }
Spring Security就是在这里提取出 cookie 信息,并对 cookie 信息进行解码。解码之后,再调用 processAutoLoginCookie()方法去做校验。processAutoLoginCookie() 方法的代码我就不贴了,核心流程就是首先获取用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值。最后再将拿到的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效,进而确认登录是否有效.
至此,壹哥 就结合着源码和底层原理,给大家讲解了基于散列加密方案实现了自动登录,并且在本案例中给大家介绍了散列加密算法,你掌握的怎么样呢?请在评论区给 一一哥 留言,说说你的感受吧!下一篇文章中,壹哥 会给各位讲解 基于持久化令牌方案实现自动登录,敬请期待哦! 。
到此这篇关于Spring Security基于散列加密方案实现自动登录功能的文章就介绍到这了,更多相关Spring Security散列加密方案实现自动登录内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/syc000666/article/details/120481624 。
最后此篇关于Spring Security基于散列加密方案实现自动登录功能的文章就讲到这里了,如果你想了解更多关于Spring Security基于散列加密方案实现自动登录功能的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我尝试阅读有关 Spring BOM、Spring Boot 和 Spring IO 的文档。 但是没有说明,我们应该如何一起使用它们? 在我的项目中,我们已经有了自己的 Parent POM ,所以
我正在开发的很酷的企业应用程序正在转向 Spring。这对所有团队来说都是非常酷和令人兴奋的练习,但也是一个巨大的压力源。我们所做的是逐渐将遗留组件移至 Spring 上下文。现在我们有一个 huuu
我正在尝试使用 @Scheduled 运行 Spring 批处理作业注释如下: @Scheduled(cron = "* * * * * ?") public void launchMessageDi
我对这两个概念有点困惑。阅读 Spring 文档,我发现,例如。 bean 工厂是 Spring 容器。我还读到“ApplicationContext 是 BeanFactory 的完整超集”。但两者
我们有一个使用 Spring BlazeDS 集成的应用程序。到目前为止,我们一直在使用 Spring 和 Flex,它运行良好。我们现在还需要添加一些 Spring MVC Controller 。
假设我有一个类(class) Person带属性name和 age ,它可以像这样用 Spring 配置: 我想要一个自定义的 Spring 模式元素,这很容易做到,允许我在我的 Sp
如何在 Java 中以编程方式使用 Spring Data 创建 MongoDB 复合索引? 使用 MongoTemplate 我可以创建一个这样的索引:mongoTemplate.indexOps(
我想使用 spring-complex-task 执行我的应用程序,并且我已经构建了复杂的 spring-batch Flow Jobs,它执行得非常好。 你能解释一下spring批处理流作业与spr
我实现了 spring-boot 应用程序,现在我想将它用作非 spring 应用程序的库。 如何初始化 lib 类,以便 Autowiring 的依赖项按预期工作?显然,如果我使用“new”创建类实
我刚开始学习 spring cloud security,我有一个基本问题。它与 Spring Security 有何不同?我们是否需要在 spring boot 上构建我们的应用程序才能使用 spr
有很多人建议我使用 Spring Boot 而不是 Spring 来开发 REST Web 服务。我想知道这两者到底有什么区别? 最佳答案 总之 Spring Boot 减少了编写大量配置和样板代码的
您能向我解释一下如何使用 Spring 正确构建 Web 应用程序吗?我知道 Spring 框架的最新版本是 4.0.0.RELEASE,但是 Spring Security 的最新版本是 3.2.0
我如何才能知道作为 Spring Boot 应用程序的一部分加载的所有 bean 的名称?我想在 main 方法中有一些代码来打印服务器启动后加载的 bean 的详细信息。 最佳答案 如spring-
我有一个使用 Spring 3.1 构建的 RESTful API,也使用 Spring Security。我有一个 Web 应用程序,也是一个 Spring 3.1 MVC 应用程序。我计划让移动客
升级到 Spring 5 后,我在 Spring Rabbit 和 Spring AMQP 中遇到错误。 两者现在都设置为 1.5.6.RELEASE 有谁知道哪些版本应该与 Spring 5 兼容?
我现在已经使用 Spring Framework 3.0.5 和 Spring Security 3.0.5 多次了。我知道Spring框架使用DI和AOP。我还知道 Spring Security
我收到错误 Unable to Location NamespaceHandler when using context:annotation-config running (java -jar) 由
在 Spring 应用程序中嵌入唯一版本号的策略是什么? 我有一个使用 Spring Boot 和 Spring Web 的应用程序。 它已经足够成熟,我想对其进行版本控制并在运行时看到它显示在屏幕上
我正在使用 spring data jpa 进行持久化。如果存在多个具有相同名称的实体,是否有一种方法可以将一个实体标记为默认值。类似@Primary注解的东西用来解决多个bean的依赖问题 @Ent
我阅读了 Spring 框架的 DAOSupport 类。但是我无法理解这些 DAOSuport 类的优点。在 DAOSupport 类中,我们调用 getXXXTemplate() 方法来获取特定的
我是一名优秀的程序员,十分优秀!