侧边栏壁纸
博主头像
LYMTICS

海纳百川,有容乃大

  • 累计撰写 45 篇文章
  • 累计创建 37 个标签
  • 累计收到 19 条评论

目 录CONTENT

文章目录

SpringSecurity的入门使用

LYMTICS
2022-03-14 / 0 评论 / 0 点赞 / 160 阅读 / 6,503 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-03-20,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

SpringSecurity的入门使用

学习资源:

框架简介

两个重点: 认证(Authentication)授权(Authorization)

对比 ShiroShiro 本身是一个老牌的安全管理框架,有着众多的优点,例如轻量、简单、易于集成、可以在JavaSE环境中使用等。不过,在微服务时代,Shiro 就显得力不从心了,在微服务面前和扩展方面,无法充分展示自己的优势。

  • Shiro更轻量,Shiro更全面
  • SpringSecurity 和Spring 无缝融合
  • 常见:SSM + ShiroSpringBoot + SpringSecurity

整体架构

在的架构设计中,认证授权 是分开的,无论使用什么样的认证方式。都不会影响授权,这是两个独立的存在,这种独立带来的好处之一,就是可以非常方便地整合一些外部的解决方案。

认证

AuthenticationManager

在Spring Security中认证是由AuthenticationManager接口来负责的,接口定义为:

public interface AuthenticationManager { 
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
  • 返回 Authentication 表示认证成功
  • 返回 AuthenticationException 异常,表示认证失败。

AuthenticationManager 主要实现类为 ProviderManager,在 ProviderManager 中管理了众多 AuthenticationProvider 实例。在一次完整的认证流程中,Spring Security 允许存在多个 AuthenticationProvider ,用来实现多种认证方式,这些 AuthenticationProvider 都是由 ProviderManager 进行统一管理的。

Authentication

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
  • getAuthorities 获取用户权限信息
  • getCredentials 获取用户凭证信息,一般指密码
  • getDetails 获取用户详细信息
  • getPrincipal 获取用户身份信息,用户名、用户对象等
  • isAuthenticated 用户是否认证成功

SecurityContextHolder

SecurityContextHolder 用来获取登录之后用户信息。Spring Security 会将登录用户数据保存在 Session 中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到 SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将 Security SecurityContextHolder 中的数据清空。这一策略非常方便用户在 Controller、Service 层以及任何代码中获取当前登录用户数据。

授权

AccessDecisionManager

AccessDecisionManager (访问决策管理器),用来决定此次访问是否被允许。

AccessDecisionVoter

AccessDecisionVoter (访问决定投票器),投票器会检查用户是否具备应有的角色,进而投出赞成、反对或者弃权票。

AccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在 AccessDecisionManager 中会遍历每个 AccessDecisionVoter,进而决定是否允许用户访问,因而 AaccesDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager 的关系。

ConfigAttribute

ConfigAttribute,用来保存授权时的角色信息。

在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个 ROLE_ 前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具各的角色和请求某个 资源所需的 ConfigAtuibute 之间的关系。

原理

假如是你从头开始做一个权限认证的项目,你会利用什么技术,从哪里下手呢?

或者说之前你是怎么做的?

答曰:Filter

那么 SpringSecurity 其实也是使用大量的 Filter 来实现的:

SecurityFilterChain is used by FilterChainProxy to determine which Spring Security Filters should be invoked for this request.

默认过滤器并不是直接放在Web项目的原生过滤器链中,而是通过一个 FlterChainProxy 来统一管理。Spring Security 中的过滤器链通过 FilterChainProxy 嵌入到Web项目的原生过滤器链中。FilterChainProxy 作为一个顶层的管理者,将统一管理 SecurityFilter。FilterChainProxy 本身是通过 Spring 框架提供的 DplegatingFilterProxy 整合到原生的过滤器链中。

Security Filters有很多: Spring Security Reference

我们只需重点关注几个默认加载到的:

过滤器作用
WebAsyncManagerIntegrationFilter将 WebAsyncManger 与 SpringSecurity 上下文进行集成
SecurityContextPersistenceFilter在处理请求之前,将安全信息加载到 SecurityContextHolder 中
HeaderWriterFilter处理头信息加入响应中
CsrfFilter处理 CSRF 攻击
LogoutFilter处理注销登录
UsernamePasswordAuthenticationFilter处理表单登录
DefaultLoginPageGeneratingFilter配置默认登录页面
DefaultLogoutPageGeneratingFilter配置默认注销页面
BasicAuthenticationFilter处理 HttpBasic 登录
RequestCacheAwareFilter处理请求缓存
SecurityContextHolderAwareRequestFilter包装原始请求
AnonymousAuthenticationFilter配置匿名认证
SessionManagementFilter处理 session 并发问题
ExceptionTranslationFilter处理认证/授权中的异常
FilterSecurityInterceptor处理授权相关

pximage

入门使用

使用

  1. 创建 SpringBoot 项目的时候勾选 SpringSecuritySpringWeb
  2. 创建一个随意的 Controller
  3. 发起请求,会发现会被重定向到 /login 页面
  4. 在控制台可以看到随机的密码,默认用户名为 user
  5. 登陆完成后可以访问资源

问题:

  1. 为什么引入之后,什么都没有配置,所有请求都要认证呢?
  2. 在项目中明明没有登录界面,这个界面是从哪里来的?
  3. 为什么使用上述的用户名和密码可以登录,登陆时用什么验证的?

自动配置

加载默认配置的类 SpringBootWebSecurityConfiguration ,这个类是 spring boot 自动配置类,通过这个源码得知,默认情况下对所有请求进行权限控制:

@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) 
    throws Exception {
    // 要求所有请求都要认证
    http.authorizeRequests().anyRequest().authenticated()
    .and().formLogin().and().httpBasic();
        return http.build();
    }
}

所以说这就解释了第一个问题。

我们继续点进去上面的这个注解:

@ConditionalOnDefaultWebSecurity

可以发现内容如下:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
public @interface ConditionalOnDefaultWebSecurity {

}

继续点入 @Conditional 的类:

class DefaultWebSecurityCondition extends AllNestedConditions {

	DefaultWebSecurityCondition() {
		super(ConfigurationPhase.REGISTER_BEAN);
	}

	@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
	static class Classes {

	}

	@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
	static class Beans {

	}

}

这意味着我们只有在满足上述两个条件时,也就是拥有SecurityFilterChain.class, HttpSecurity.class ,且没有 WebSecurityConfigurerAdapter.class, SecurityFilterChain.class 时,前面的配置类 SpringBootWebSecurityConfiguration 才会被加载。

而如果我们像自行配置业务相关的过滤规则,一般来说只需覆盖 WebSecurityConfigurerAdapter 类就可以了。

请求拦截

我们之前发送请求的处理过程如下:

pximage

  1. 用户发起请求,通过了一些过滤器,但是在 FilterSecurityInterceptor 这里被拦截住了,它发现用户没有登录,并抛出了一个 AccessDeniedException 异常
  2. 抛出的异常会被 ExceptionTranslationFilter 捕获,这个 Filter 中会调用 LoginUrlAuthenticationEntryPoint#commence 方法给客户端返回 302,要求客户端重定向到 /login 页面
  3. 客户端发送 /login 请求
  4. /login 请求会再次被拦截器中 DefaultLoginPageGeneratingFilter 拦截到,并在拦截器中返回生成的登陆页面

这就解释了第二个问题。

身份验证

既然我们可以对用户的身份进行验证,那么我们肯定要有一个地方保存正确的用户名和密码。

SpringSecurity 中有一个接口叫 UserDetailService 负责做这件事情,它下面有若干接口:

pximage

其实我们默认没有配置的情况下,用到的密码就是这个 InMemoryUserDetailsManage 生成的,后续我们也可以换成从其他的地方获得数据信息。

UserDetailsServiceAutoConfiguration 的生效条件是:

@ConditionalOnMissingBean( value = {
    AuthenticationManager.class,
    AuthenticationProvider.class,
    UserDetailsService.class,
    AuthenticationManagerResolver.class })

所以我们可以自定义自己的 UserDetailsService 实现类来改变验证逻辑。

我们可以在配置文件中修改默认的密码:

spring:
  security:
    user:
      password: 123
      name: root

【当然这里具体的细节,类之间的关系可能要进一步学习才能理解,这里先略过】

总结

AuthenticationManager 是一个总的接口,通过 authenticate 方法进行验证,它需要传入一个不完整的 authentication 对象,如果验证成功则返回一个完整的 authntication 对象,否则抛出异常。

pximage

AuthenticationManager 下面有许多实现类,我们一般使用 ProviderManager 来完成相关业务,它具体实现了父类的 authenticate 方法,并且保存了一系列有序的用来进行验证的 provider ,在 authenticate 方法中会逐一调用 provider 来进行授权判断。

pximage

不同的 provider 则进行相关的验证工作:

pximage

如果我们想扩展 SpringSecurity 的某些功能,则可以通过自定义 WebSecurityConfigAdapter 来实现。

0

评论区