SpringSecurity简介

一、什么是Spring Security?

springsecurity是一个功能强大且高度可定制的身份验证和访问控制框架。springsecurity是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring安全性的真正威力在于它可以很容易地扩展以满足定制需求。

实际上,在Spring boot出现之前,Spring security已经发展了很多年,到那时由于强大的shiro,它一直不温不火。因为相对于Shiro,在SSH/SSM中整合Spring Security都是比较麻烦的操作,然而自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了 自动化配置方案,可以零配置使用 Spring Security。

所以一般的推荐方案是:

  • SSM + Shiro
  • Spring boot/Spring Cloud + Spring Security

二、Spring Security 原理解析

Spring Security对Web资源的保护是靠Filter实现的。当初始化Spring Security时,会创建一个名为 springSecurityFilterChain 的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过虑器链结构图:

filter实现的security

这里FilterChainProxy是一个代理,真正起作用的是FilterChainProxySecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理。

所以spring Security功能的实现主要是由一系列过滤器链相互配合完成,如下图:

Spring Security Filters

  • SecurityContextPersistenceFilter :这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截 器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext。
  • UsernamePasswordAuthenticationFilter :用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变。
  • FilterSecurityInterceptor: 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问

这里需要重点讨论的有两个概念,身份认证授权。这也是Spring Security的主要职责。

2.1 身份认证

  • 首先在usernamePasswordAuthenticationFilter中来拦截登录请求,并调用AuthenticationManager

  • AuthenticationManager调用Provider

  • provider调用userDetaisService来根据username获取真实的数据库信息。

  • 最终验证帐号密码的类是org.springframework.security.authentication.dao.DaoAuthenticationProvider

 身份认证时序图

2.2 授权

  • Spring Security默认使用AffirmativeBased实现 AccessDecisionManager的decide 方法来实现授权
  • 调用AccessDecisionVoter进行vote(投票)
  • 只要有投通过(ACCESS_GRANTED)票,则直接判为通过。
  • 如果没有投通过则 deny++ ,最后判断if(deny>0 抛出AccessDeniedException(未授权)
授权结构图

授权时序图

三、核心组件

3.1 SecurityContextHolder

SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认是使用ThreadLocal实现的(ThreadLocal相关原理分析),这样就保证了本线程内所有的方法都可以获得SecurityContext对象。

可以通此方法过来获取当前操作用户信息:

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

默认返回的对象是UserDetails实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。比如下面只对token进行操作就可以吧token作为属性放入UserDetails实现类中。

3.2 Authentication

Authentication是Spring Security方式的认证主体。

1) Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是最高级别的身份/认证的抽象。
2) 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
authentication.getPrincipal()返回了一个Object,我们将Principal强转成了Spring Security中最常用的UserDetails,这在Spring Security中非常常见,接口返回Object,使用instanceof判断类型,强转成对应的具体实现类。接口详细解读如下:

  • getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
  • getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
  • getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
  • getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。

3.3 AuthenticationManager

AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中身份认证的方式有多种,一般不使用AuthenticationManager,而是使用AuthenticationManager的实现类ProviderManager ,ProviderManager内部会维护一个List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式对应不同的AuthenticationProvider。

总结:

  • SecurityContextHolder:存放身份信息的容器
  • Authentication:用户信息的抽象
  • AuthenticationManager:身份认证器

四、认证流程

  1. 通过过滤器过滤到用户请求的接口,获取身份信息(假如有多个认证方式会配置provider的顺序)
  2. 一般将身份信息封装到封装成Authentication下的实现类UsernamePasswordAuthenticationToken
  3. 通过AuthenticationManager 身份管理器(通过配置找到对应的provider)负责验证这个UsernamePasswordAuthenticationToken
  4. 认证成功后(认证逻辑一般在service中),AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
  5. SecurityContextHolder安全上下文容器将第2步填充了信息的UsernamePasswordAuthenticationToken,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中来建立安全上下文(security context)。

五、简单示例

5.1 数据库表结构

  • 添加用户表角色表用户角色关系表
  • 插入数据

5.2 Spring + mybatis配置

  • 添加pom
  • 修改yml配置
  • 主启动类
  • 配置DBconfig.java (数据源)、MyBatisConfig.java (扫描mapper.xml文件)、MyBatisScannerConfig.java (dao 扫描器)、TransactionConfig.java (开启事物管理)

5.3 java bean

  • 有三个bean ,sysuser(用户),sysrole(角色),msg(信息,用于和页面传递信息使用)
  • dao 层实现(mapper.xml)

5.4 添加WebSecurityConfig

  • 添加WebSecurityConfig.java 配置文件,用于管控登录访问权限
  • url权限分配给角色
  • 新建CustomUserService 用于将用户权限交给 springsecurity进行管控;需要实现 UserDetailsService接口

5.5 页面实现

  • 添加login.html页面
  • springMVC配置,注册访问/login 转向login.html 页面

5.6 OAuth2.0

OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。

OAuth2.0大致流程:

  1. 用户想操作存放在服务提供方的资源。
  2. 用户登录客户端向服务提供方请求一个临时令牌。
  3. 服务提供方验证客户端的身份后,授予一个临时令牌。
  4. 客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方。
  5. 用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。
  6. 授权成功后,服务提供方引导用户返回客户端的网页。
  7. 客户端根据临时令牌从服务提供方那里获取访问令牌。
  8. 服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌。
  9. 客户端使用获取的访问令牌访问存放在服务提供方上的受保护的资源。

授权模式

  • 授权码模式(authorization code)---- 授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

  • 隐藏模式(implicit)---- 有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)。

  • 密码模式(resource owner password credentials)---- 如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

  • 客户端模式(client credentials)---- 适用于没有前端的命令行应用,即在命令行下请求令牌。