Spring Security 从入门到精通之环境搭建与HelloWorld案例源码详解(二)

1.前言

在上一篇文章:Spring Security 从入门到精通之架构理解 我们熟悉了Spring Security授权、认证基本概念,下面将搭建HelloWorld入门案例。

2. HelloWorld快速入门

由于Spring Security 与 Spring 生态无缝衔接,所以在Spring Boot中只需引入web与security的依赖即可,依赖如下所示:

<!--Spring boot Web容器-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<!--引入SpringSecurity依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

然后我们添加一个测试类并在类中新增一个方法如下所示:
在这里插入图片描述
然后启动项目,当访问此接口时会自动跳转到登陆页面,当我们登陆成功才可以访问到此接口。
在这里插入图片描述
默认Spring Security 内置了一个用户账户:user,其默认密码在项目控制台可以看到如下:
在这里插入图片描述
当输入用户名和密码后登陆成功后,就可以直接访问接口了。这样一个简单的入门案例就完成了。

3. 入门案例分析

3.1 自动配置分析

只需要引入相关依赖,Spring Security 默认就将我们的接口给保护起来了,必须认证才能继续访问,那么 Spring Security 是如何做到的呢?首先我们需要关注一下 Spring Boot 自动配置Spring Security类 SecurityAutoConfiguration 如下所示:
在这里插入图片描述
我们看到这个类默认导入了SpringBootWebSecurityConfiguration(web Security的默认配置类),此类定义内容如下所示:
在这里插入图片描述
通过上面defaultSecurityFilterChain 方法我们可以看到其默认对任何请求开启了认证。这也是我们默认在Spring Boot项目中引入Spring Security 后接口需要认证才可以访问的根本原因。3.1.2 章节我们提到了Spring Security 过滤器原理。defaultSecurityFilterChain 默认返回的是一个SecurityFilterChain 实例,我们可以看看其默认配置的filter有如下15个:
在这里插入图片描述

过滤器作用说明
过滤器过滤器作用是否默认加载
ChannelProcessingFilter过滤请求协议如Http与Https
WebAsyncManagerIntegrationFilter将WebAsyncManager与 Spring Security 进行集成
SecurityContextPersistenceFilter在处理请求前,将安全信息加载到SecurityContextHolder中以方便后续使用,请求结束再从SecurityContextHolder删除
HeaderWriterFilter头信息加入响应中
CorsFilter处理跨域问题
CsrfFilter 处理CSRF攻击
LogoutFilter注销登陆
OAuth2AuthorizationRequestRedirectFilter处理Oauth2认证重定向
Saml2WebSsoAuthenticationRequestFilter处理SAML认证
X509AuthenticationFilter处理X509认证
AbstractPreAuthenticatedProcessingFilter处理预认证
CasAuthenticationFilter处理CAS单点登录
OAuth2LoginAuthenticationFilter处理Oauth2认证
Saml2WebSsoAuthenticationFilter处理SAML认证
UsernamePasswordAuthenticationFilter处理表单认证
OpenIDAuthenticationFilter处理OpenID认证
DefaultLoginPageGeneratingFilter配置默认登陆页面
DefaultLogoutPageGeneratingFilter配置默认注销页面
ConcurrentSessionFilter处理Session有效期
DigestAuthenticationFilter处理Http摘要认证
BearerTokenAuthenticationFilter处理Oauth2认证时的Access Token
BasicAuthenticationFilter处理Http Basic认证
RequestCacheAwareFilter处理请求缓存
SecurityContextHolderAwareRequestFilter包装原始请求
JaasApiIntegrationFilter处理JAAS认证
RememberMeAuthenticationFilter处理记住我认证
AnonymousAuthenticationFilter配置匿名认证
OAuth2AuthorizationCodeGrantFilter处理Oauth2中的授权码
SessionManagementFilter处理Session并发问题
ExceptionTranslationFilter处理异常认证/授权中的情况
FilterSecurityInterceptor处理授权
SwitchUserFilter处理账户切换

Spring Security 内置了30个过滤器,在我们一个Hello World案例中默认启动了15个,Spring Security正是通过这些过滤器来实现其认证与授权。下面我们大概了解一下 Spring Security 的过滤器实现。

3.2 过滤器原理分析

Spring Security 认证模块、授权模块都是基于过滤器完成的,需要注意的是Spring Security默认并不是直接使用web Servlet容器中的Filter,而是实现了自己的过滤器并提供了一个DelegatingFilterProxy类,这个类会将Spring Security定义的过滤器转化为原生的Servlet容器中的Filter,并将这个Filter注册到原生过滤器链(Servlet容器过滤器链)中,如下图所示:
在这里插入图片描述
Spring Security中过滤器不仅仅只有一个,可能会有多个并组成一个滤器链。所以Spring Security 提供了一个SecurityFilterChain 用来管理多个过滤器。而这个过滤器了链又被一个特殊的过滤器 FilterChainProxy统一管理,此过滤器内嵌在DelegatingFilterProxy中,如下图所示:
在这里插入图片描述
Spring Security 可以通过不同的请求url 形成多个过滤器链路,多个过滤器需要指定优先级。当请求到达DelegatingFilterProxy中通过FilterChainProxy进行分发,匹配上那些过滤器链就用那些过滤器处理请求。
在这里插入图片描述

3.3 登陆页面生成源码分析

首先我们知道Spring Security 的认证与授权都是基于过滤器完成的,当我们在浏览器中输入:http://127.0.0.1:8001/helloWorld 后,这条请求先后都会经过默认启用的15个过滤器。
我们通过Debug 源码可以跟踪其登陆流程,关键跟踪源代码节点如下:

  • FilterSecurityInterceptor拦截请求并抛出AccessDeniedException。

首先helloWorld请求经过若干过滤器后,最终到达过滤器FilterSecurityInterceptor#doFilter方法
在这里插入图片描述
doFilter 方法调用了本类的invoke方法,接着调用了父类AbstractSecurityInterceptor的beforeInvocation方法的如下所示:
在这里插入图片描述
父类beforeInvocation方法调用了自己的attemptAuthorization方法,方法定义如下:
在这里插入图片描述
此方法调用了访问决策器接口的decide方法,此方法实现是由类AffirmativeBased实现如下所示:
在这里插入图片描述
投票器AccessDecisionVoter 投了反对票,所以此方法抛出了AccessDeniedException异常。

  • 抛出AccessDeniedException异常被ExceptionTranslationFilter捕获并交由LoginUrlAuthenticationEntryPoint类处理并重定向到登陆页面。

请求抛出的异常被ExceptionTranslationFilter捕获并调用了handleSpringSecurityException方法进行处理如下:
在这里插入图片描述
在这个方法中又调用了handleAccessDeniedException方法如下所示:
在这里插入图片描述
然后接着调用了sendStartAuthentication方法如下所示:
在这里插入图片描述
接着调用了在sendStartAuthentication方法中继续调用了类的authenticationEntryPoint实例#commence方法如下:
在这里插入图片描述
此实例是由LoginUrlAuthenticationEntryPoint类实现,我们关注其commence方法如下所示:
在这里插入图片描述
可以清晰看到请求重定向 http://127.0.0.1:8001/login。然后这个请求又要过一次过滤器链直到DefaultLoginPageGeneratingFilter的doFilter方法如下所示:
在这里插入图片描述
在这个方法中response输入了一个静态的html代码,所以我们就能看到最开始登陆的页面:
在这里插入图片描述
整个流程总结如下所示:
在这里插入图片描述

3.4 默认用户与密码生成源码分析

Spring Security中定义了UserDetailsService接口规范开发者自定义的用户对象接口查询,这样很方便用户将系统集成Spring Security 认证体系中,此接口只有一个查询用户详情的方法:
在这里插入图片描述
此方法参数只有一个username参数,注意这里的username并不是前端表单输入的用户名而是用户登陆成功后返回的用户名。在实际项目中,需要用户手动实现UserDetailsService。在HelloWorld案例中Spring Security提供了UserDetailsService接口的默认实现。
在这里插入图片描述
当我们只引入了Spring Security依赖,UserDetailsService 默认实现为InMemoryUserDetailsManager。此类提供了用户新增、删除、更新方法,这些方法都是基于内存实现(用户信息保存在Map中)。
在这里插入图片描述
再回到上面提到的UserDetailsService#loadUserByUsername 返回了一个用户详情对象,此对象接口提供了如下方法:
在这里插入图片描述
Spring Security 默认配置UserDetailsService的实现类为UserDetailsServiceAutoConfiguration。
在这里插入图片描述
在上面的代码中我们不难发现,InMemoryUserDetailsManager默认实现条件(@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean注解),在inMemoryUserDetailsManager方法中我们可以看到默认初始化的用户信息来自于类SecurityProperties,这个类是Spring Security属性配置类,类定义如下所示:
在这里插入图片描述
这个类中内部维护一个用户对象,用户类定义如下:
在这里插入图片描述
在这里我们就很清晰了解了为什么Spring Security内置的用户名是user,以及密码是长长的UUID字符串原因了。由于SecurityProperties是一个属性配置类,所以我们可以在项目配置文件自定义用户属性(覆盖默认的用户信息)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值