SPRING实现登陆

使用 用户名+密码 的方式来登录,用户名、密码存储在数据库,并且支持密码输入错误三次后开启验证码,通过这样一个过程来熟悉 spring security 的认证流程,掌握 spring security 的原理。
1、基础环境
① 创建 sunny-cloud-security 模块,端口号设置为 8010,在sunny-cloud-security模块引入security支持以及sunny-starter-core:
在这里插入图片描述
在这里插入图片描述
② 开发一个TestController
在这里插入图片描述
③ 不做任何配置,启动系统,然后访问 localhost:8010/test 时,会自动跳转到SpringSecurity默认的登录页面去进行认证。那这登录的用户名和密码从哪来呢?
在这里插入图片描述
启动项目时,从控制台输出中可以找到生成的 security 密码,从 UserDetailsServiceAutoConfiguration 可以得知,使用的是基于内存的用户管理器,默认的用户名为 user,密码是随机生成的UUID。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们也可以修改默认的用户名和密码。

④ 使用 user 和生成的UUID密码登录成功后即可访问 /test 资源,最简单的一个认证就完成了。
在这里插入图片描述
在不做任何配置的情况下,security会把服务内所有资源的访问都保护起来,需要先进行身份证认证才可访问, 使用默认的表单登录或http basic认证方式。
不过这种默认方式肯定无法满足我们的需求,我们的用户名和密码都是存在数据库的。下面我们就来看看在 spring boot 中我们如何去配置自己的登录页面以及从数据库获取用户数据来完成用户登录。
2、自定义登录页面
① 首先开发一个登录页面,由于页面中会使用到一些动态数据,决定使用 thymeleaf 模板引擎,只需在 pom 中引入如下依赖,使用默认配置即可,具体有哪些配置可从 ThymeleafProperties 中了解到。
在这里插入图片描述
② 同时,在 resources 目录下,建 static 和 templates 两个目录,static 目录用于存放静态资源,templates 用于存放 thymeleaf 模板页面,同时配置MVC的静态资源映射。
在这里插入图片描述
在这里插入图片描述
③ 开发后台首页、登录页面的跳转地址,/login 接口用于向登录页面传递登录相关的数据,如用户名、是否启用验证码、错误消息等。
View Code
④ 从 spring boot 官方文档可以得知,spring security 的核心配置都在 WebSecurityConfigurerAdapter 里,我们只需继承该适配器覆盖默认配置即可。首先来看看默认的登录页面以及如何配置登录页面。
通过 HttpSecurity 配置安全策略,首先开放了允许匿名访问的地址,除此之外都需要认证,通过 formLogin() 来启用表单登录,并配置了默认的登录页面,以及登录成功后的首页地址。
在这里插入图片描述
启动系统,访问资源跳转到自定义的登录页面了:
在这里插入图片描述
⑤ 那么默认的登录页面是怎么来的呢,以及做了哪些默认配置?
从 formLogin() 可以看出,启用表单登录即启用了表单登录的配置 FormLoginConfigurer:
在这里插入图片描述
从 FormLoginConfigurer 的构造函数中可以看出,表单登录用户名和密码的参数默认配置为 username 和 password,所以,我们的登录页面中需和这两个参数配置成一样,当然了,我们也可以在 formLogin() 后自定义这两个参数。
同时,可以看出开启了 UsernamePasswordAuthenticationFilter 过滤器,用于 用户名+密码 登录方式的认证,这个之后再说明。
在这里插入图片描述
从初始化配置中可以看出,默认创建了 DefaultLoginPageGeneratingFilter 过滤器用于生成默认的登录页面,从该过滤器的初始化方法中我们也可以了解到一些默认的配置。这个过滤器只有在未配置自定义登录页面时才会生效。
在这里插入图片描述
3、SpringSecurity基本原理
在进行后面的开发前,先来了解下 spring security 的基本原理。
spring security 的核心是过滤器链,即一组 Filter。所有服务资源的请求都会经过 spring security 的过滤器链,并响应返回。
我们从控制台中可以找到输出过滤器链的类 DefaultSecurityFilterChain,在现有的配置上,可以看到当前过滤器链共有13个过滤器。
每个过滤器主要做什么可以参考:Spring Security 核心过滤器链分析
在这里插入图片描述
过滤器链的创建是通过 HttpSecurity 的配置而来,实际上,每个 HttpSecurity 的配置都会创建相应的过滤器链来处理对应的请求,每个请求都会进入 FilterChainProxy 过滤器,根据请求选择一个合适的过滤器链来处理该请求。
在这里插入图片描述
过滤器的顺序我们可以从 FilterComparator 中得知,并且可以看出 spring security 默认有25个过滤器(自行查看):
在这里插入图片描述
不难发现,几乎所有的过滤器都直接或间接继承自 GenericFilterBean,通过这个基础过滤器可以看到都有哪些过滤器,通过每个过滤器的名称我们能大概了解到 spring security 为我们提供了哪些功能,要启用这些功能,只需通过配置加入相应的过滤器即可,比如 oauth 认证。
在这里插入图片描述
过滤器链中,绿色框出的这类过滤器主要用于用户认证,这些过滤器会根据当前的请求检查是否有这个过滤器所需的信息,如果有则进入该过滤器,没有则不会进入下一个过滤器。
比如这里,如果是表单登录,要求必须是[POST /login],则进入 UsernamePasswordAuthenticationFilter 过滤器,使用用户名和密码进行认证,不会再进入BasicAuthenticationFilter;

如果使用 http basic 的方式进行认证,要求请求头必须包含 Authorization,且值以 basic 打头,则进入 BasicAuthenticationFilter 进行认证。

经过前面的过滤器后,最后会进入到 FilterSecurityInterceptor,这是整个 spring security 过滤器链的最后一环,在它身后就是服务的API。
这个过滤器会去根据配置决定当前的请求能不能访问真正的资源,主要一些实现功能在其父类AbstractSecurityInterceptor中。

[1] 拿到的是权限配置,会根据这些配置决定访问的API能否通过。
[2] 当前上下文必须有用户认证信息 Authentication,就算是匿名访问也会有相应的过滤器来生成 Authentication。不难发现,不同类型的认证过滤器对应了不同的 Authentication。使用用户名和密码登录时,就会生成 UsernamePasswordAuthenticationToken。

[3] 用户认证,首先判断用户是否已认证通过,认证通过则直接返回 Authentication,否则调用认证器进行认证。认证通过之后将 Authentication 放到 Security 的上下文,这就是为何我们能从 SecurityContextHolder 中取到 Authentication 的源头。

认证管理器是默认配置的 ProviderManager,ProviderManager 则管理者多个 AuthenticationProvider 认证器 ,认证的时候,只要其中一个认证器认证通过,则标识认证通过。

认证器:表单登录默认使用 DaoAuthenticationProvider,我们想要实现从数据库获取用户名和密码就得从这里入手。

[4] 认证通过后,使用权限决定管理器 AccessDecisionManager 判断是否有权限,管理器则管理者多个 权限投票器 AccessDecisionVoter,通过投票器来决定是否有权限访问资源。因此,我们也可以自定义投票器来判断用户是否有权限访问某个API。

最后,如果未认证通过或没有权限,FilterSecurityInterceptor 则抛出相应的异常,异常会被 ExceptionTranslationFilter 捕捉到,进行统一的异常处理分流,比如未登录时,重定向到登录页面;没有权限的时候抛出403异常等。

4、用户认证流程
从 spring security 基本原理的分析中不难发现,用户的认证过程涉及到三个主要的组件:
AbstractAuthenticationProcessingFilter:它在基于web的认证请求中用于处理包含认证信息的请求,创建一个部分完整的Authentication对象以在链中传递凭证信息。
AuthenticationManager:它用来校验用户的凭证信息,或者会抛出一个特定的异常(校验失败的情况)或者完整填充Authentication对象,将会包含了权限信息。
AuthenticationProvider:它为AuthenticationManager提供凭证校验。一些AuthenticationProvider的实现基于凭证信息的存储,如数据库,来判定凭证信息是否可以被认可。
我们从核心的 AbstractAuthenticationProcessingFilter 入手,来分析下用户认证的流程。
[1] 可以看到,首先会调用 attemptAuthentication 来获取认证后的 Authentication。attemptAuthentication 是一个抽象方法,在其子类中实现。

前面提到过,启用表单登录时,就会创建 UsernamePasswordAuthenticationFilter 用于处理表单登录。后面开发 oauth2 认证的时候则会用到 OAuth2 相关的过滤器。

从 attemptAuthentication 的实现中可以看出,主要是将 username 和 password 封装到 UsernamePasswordAuthenticationToken。

从当前 UsernamePasswordAuthenticationToken 的构造方法中可以看出,此时的 Authentication 设置了未认证状态。

【#】通过 setDetails 可以向 UsernamePasswordAuthenticationToken 中加入 Details 用于后续流程的处理,稍后我会实现AuthenticationDetailsSource 将验证码放进去用于后面的认证。

之后,通过 AuthenticationManager 进行认证,实际是 ProviderManager 管理着一些认证器,这些配置都可以通过 setter 方法找到相应配置的位置,这里就不赘述了。
不难发现,用户认证器使用的是 AbstractUserDetailsAuthenticationProvider,流程主要涉及到 retrieveUser 和 additionalAuthenticationChecks 两个抽象方法。
【#】AbstractUserDetailsAuthenticationProvider 默认只有一个实现类 DaoAuthenticationProvider,获取用户信息、用户密码校验都是在这个实现类里,因此我们也可以实现自己的 AbstractUserDetailsAuthenticationProvider 来处理相关业务。

【#】从 retrieveUser 中可以发现,主要使用 UserDetailsService 来获取用户信息,该接口只有一个方法 loadUserByUsername,我们也会实现该接口来从数据库获取用户信息。如果有复杂的业务逻辑,比如锁定用户等,还可以覆盖 retrieveUser 方法。

用户返回成功后,就会通过 PasswordEncoder 来校验用户输入的密码和数据库密码是否匹配。注意数据库存入的密码是加密后的密码,且不可逆。

用户、密码都校验通过后,就会创建已认证的 Authentication,从此时 UsernamePasswordAuthenticationToken 的构造方法可以看出,构造的是一个已认证的 Authentication。

[2] 如果用户认证失败,会调用 AuthenticationFailureHandler 的 onAuthenticationFailure 方法进行认证失败后的处理,我们也会实现这个接口来做一些失败后逻辑处理。

[3] 用户认证成功,将 Authentication 放入 security 上下文,调用 AuthenticationSuccessHandler 做认证成功的一些后续逻辑处理,我们也会实现这个接口。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值