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

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

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

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

文章目录
    • 1.前言
    • 2. HelloWorld快速入门
    • 3. 入门案例分析
      • 3.1 自动配置分析
      • 3.2 过滤器原理分析
      • 3.3 登陆页面生成源码分析
      • 3.4 默认用户与密码生成源码分析

1.前言

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

2. HelloWorld快速入门

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



  org.springframework.boot
  spring-boot-starter-web

 


  org.springframework.boot
  spring-boot-starter-security

然后我们添加一个测试类并在类中新增一个方法如下所示:

然后启动项目,当访问此接口时会自动跳转到登陆页面,当我们登陆成功才可以访问到此接口。

默认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是一个属性配置类,所以我们可以在项目配置文件自定义用户属性(覆盖默认的用户信息)。

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

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

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