Spring Security是一个强大的java应用安全管理库,特别适合用作后台管理系统。这个库涉及的模块和概念有一定的复杂度,而大家平时学习Spring的时候也不会涉及;这里基于官方的参考文档,把Spring Security的基本套路介绍一下。

参考的Spring Security文档地址:https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/preface.html

Spring Securitys示例https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/samples.html;对于新手入门,看一下示例很有必要,但是一般的产品的安全的策略都比示例要复杂得多,很难通过模仿示例程序来达成你的目标。

说明:这篇文章不打算手把手教大家如何使用Spring Security,所以不会有详细的代码以及配置;少量的代码和配置示例,仅仅用来阐述概念和设计,这些示例代码和配置并不一定适合在项目中使用。

Over View

认证和鉴权("authentication" and "authorization" )

应用安全一般可分成两个方面,一是认证:确认使用者的身份,创建对应的principal(这个词代表一个经过确认的身份信息);二是鉴权:判定某个principal是否有访问某个资源或执行某个操作的权限。

Spring Security支持很多的认证方式比如HTTP BASIC, OPEN ID,FORM LOGI等等,这里不列举。而对于鉴权,支持3种主要类型:web请求,方法调用,以及domain对象。

由于Spring Security支持的功能很广泛,这篇文章不会一一介绍。将背景限定为:一个通过http协议访问的web系统,采用表单登录,用户信息存储在数据库里面。

Security-Core

这是使用Spring Security的必然要依赖的一个库,其中包含了最基本的数据结构和接口。在Spring Security 3.0版本以后,这个库经过简化,不再包含web、ldap、configuration相关的功能。从DDD的角度来看,这个库是Spring Security的领域模型。下面介绍一下几个最基本的类。

SecurityContextHolder

SecurityContextHolder是存放当前安全相关上下文对象的地方,它包含一个Authentication对象,包含认证用户的多有信息。它使用ThreadLocal来存放信息,请求执行结束以后清除相关信息。因此如果你需要在其他线程访问Security上下文信息,请注意这一点。

下面的代码展示了如何通过contextHolder访问principal。

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

UserDetails

大多数情况下,principal是一个UserDetails实例。UserDetails是一个很重要的接口,代表认证用户的详细信息。我们可以通过自定义的类来实现它,或者使用security库提供的简单实现。不管如何,它是业务层用户数据和spring security之间的桥梁,在必要时,我们可以把UserDetails转换回具体类型,来访问额外的字段。

创建UserDetails对象是一个叫做UserDetailsService的接口,它只有一个方法:

UserDetails loadUserByUsername(String username)

即通过用户名字查询UserDetails对象。不管实现如何,UserDetailsService被视作一个类似DAO的角色,参与到认证过程中来。

GrantedAuthority

除了principal,Authentication还包含一个GrantedAuthority数组。GrantedAuthority代表赋予principal的一项权限,最通常的情况,是代表某个角色,比如“ROLE_ADMINISTRATOR”。

GrantedAuthority接口只有一个方法,就是String getAuthority(),意味着如果你的鉴权机制通过字符串的处理就能完成,那么通过字符串表达就好。前缀“ROLE_"就是一个约定,代表基于角色的权限。如果你的权限需要更复杂的数据结构来表示,那么请自定义GrantedAuthority具体实现,这样的话权限鉴定(后面会讲)的过程也需要自定义。

认证

一个简化的Spring Security认证过程如下:

  1. 用户输入用户名和密码;被包装成UsernamePasswordAuthenticationToken(Authentication的实现);
  2. 这个token传递到AuthenticationManager
  3. AuthenticationManager验证后,返回一个完全填充(fully populated)的Authentication对象;
  4. 通过SecurityContextHolder.getContext().setAuthentication,完成安全上下文的创建。

web应用的认证过程会稍微复杂一些,同样经过简化可以表述如下:

  1. 用户访问某个受保护的url
  2. AbstractSecurityInterceptor拦截这个请求,并抛出没有权限
  3. ExceptionTranslationFilter捕获这个异常
  4. 如果检测到用户没有认证,于是通过AuthenticationEntryPoint重定向用户到一个登录页面;
  5. 如果发现已经认证但是权限不足,通常返回HTTP 403.
  6. 接下来的认证过程和上面是类似的。

鉴权机制

鉴权决策的核心接口是AccessDecisionManager,它的核心方法是

void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)

第一个参数我们已经知道是认证信息,第二个参数代表要访问的受保护对象,第三个参数是这个资源的权限属性集。

什么是受保护对象?可能是一个url请求,或者是某一个方法调用。不同类型的受保护资源,会有不同的拦截器(接口AbstractSecurityInterceptor)来拦截正常的访问流程,插入权限决策机制。

拦截器完成以下工作:

  1. 查找当前受保护对象的权限属性;
  2. 将受保护对象(secure object),权限属性(configuration attributes),当前的认证信息(authentication),提交给AccessDecisionManager来鉴权;
  3. 如果鉴权通过,继续执行正常的访问;
  4. 否则抛出异常;

权限属性(configuration attribute)是受保护对象的,与权限相关的属性数据,一般就是普通的字符串。对这个属性的解释取决于AccessDecisionManager的实现。通过为AbstractSecurityInterceptor配置SecurityMetadataSource来实现权限属性的查找。比如,在xml配置里面看到<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>,那么配置属性“ROLE_A”和“ROLE_B”暗示角色A和B能访问这个pattern的url;当然实际是否如此还要看AccessDecisionManager的配置。这里再强调一下,"ROLE_"这个前缀是Spring Security内的一种约定,用于基于角色的鉴权机制。

核心的服务

AuthenticationManager仅仅是一个接口,具体的实现取决于认证的方式。Spring Security的默认实现叫做ProviderManager,它把认证功能委托给一个AuthenticationProvider列表。每个AuthenticationProvider可以返回一个完全填充(fully populated)的Authentication对象(认证成功),或抛出一个异常;可见,ProviderManager可以组合多种认证方式,一个ProviderManager bean的配置类似如下:

<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="ldapAuthenticationProvider"/>
</list>
</constructor-arg>
</bean>

上一章节讲到UserDetailsService可以通过用户名加载用户的信息(UserDetails),实现该种认证方式的Manager是DaoAuthenticationProvider,他内部配置一个UserDetailsService引用:

<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>

上面的PasswordEncoder用于用户密码的编解码。一般用户的密码不会以明文形式存储,同时加密方式也不会支持逆向解密;PasswordEncoder可以将输入的密码进行加密,再与存储的密文进行比较。为了支持同时多种加密方式,Spring Security设计了叫做DelegatingPasswordEncoder的encoder,他将加密委托给多种具体加密方式,依据密文类型查询。于是默认的密文存储格式就变成了{id}encodedPassword。一个特殊的encode是NoOpPasswordEncoder,表示明文存储,不做任何处理。

Web应用安全

这部分讲一下Spring Security和Spring MVC的结合。上面讲了Spring Security的核心配置,在web应用中,security的功能通过servlet filter的方式与web功能链接在一起。这会使得整个配置更加复杂,因此Spring Security提供了简洁的xml配置方式,一个简单的标签后面,完成了大量功能。

DelegatingFilterProxy

我们都知道filter应该配置在web.xml中,实际上Spring Security只配置一个唯一的fiter,叫做DelegatingFilterProxy。它将实际的功能委托给其他配置在Spring Context内的Spring Bean。

FilterChainProxy

上面DelegatingFilterProxy将功能委托给FilterChainProxy,FilterChainProxy是一个普通的Spring bean,如果要在xml里面声明它,注意bean id应当于web.xml里面声明DelegatingFilterProxy的filter-name是一样的。

FilterChainProxy的名字暗示了,它背后有很多filter组成的chain,实际上它可以包含多个chain,下面看示例:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<sec:filter-chain pattern="/restful/**" filters="
securityContextPersistenceFilterWithASCFalse,
basicAuthenticationFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
<sec:filter-chain pattern="/**" filters="
securityContextPersistenceFilterWithASCTrue,
formLoginFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</list>
</constructor-arg>
</bean>

上面对不同的url路径使用了不同的安全策略,而不同的安全策略是通过一个filter chain来实现的。这些filter的全部实现java.servlet.filter接口,但并不由web容器来管理。前面所说的那些“认证”,“鉴权”相关功能实现都隐藏在这些filter背后。

这些Filter有严格的顺序,比如授权相关的Filter就要出现在鉴权相关的Fiter之前,关于位置,Spring Security定义了一组常量。当你想提供一个自定义的Filter的时候,可能会用到。

xml配置之http(示例)

我们基本不会相上面那样配置FilterChain,在xml文件里面一个http元素,就意味着一条完整的FilterChain被配置,Spring Security的配置模块为我们完成大量的工作。

<!-- Stateless RESTful service using Basic authentication -->
<http pattern="/restful/**" create-session="stateless">
<intercept-url pattern='/**' access="hasRole('REMOTE')" />
<http-basic />
</http> <!-- Empty filter chain for the login page -->
<http pattern="/login.htm*" security="none"/> <!-- Additional filter chain for normal users, matching all other requests -->
<http>
<intercept-url pattern='/**' access="hasRole('USER')" />
<form-login login-page='/login.htm' default-target-url="/home.htm"/>
<logout />
</http>

上面三个http元素,第一个对/restful/**使用基于http-basic的登录方式,使用基于角色的鉴权方式(角色REMOTE可以访问)。

第二个对/login.htm*不使用任何安全过滤;和第一个相比,使用form-login的登录方式,并指定了登录url和登录后跳转的url,还配置了登出时的默认行为。

http元素的每个属性,每个子元素,都可能对filter chain产生或大或小的影响,简洁的同时也让人不免晕头转向。

核心安全过滤器

FilterSecurityInterceptor

这是负责鉴权的Filter,典型的配置如下:

<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source>
<security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-security-metadata-source>
</property>
</bean>

前面的章节讲过,鉴权的过程就是将Authentication,安全对象,安全对象的配置属性,提交给AccessDecisionManager。上面的配置可见,这个Filter包含AuthenticationManager,AccessDecisionManager引用,而内嵌的securityMetadataSource提供了安全对象的配置属性。

ExceptionTranslationFilter

ExceptionTranslationFilter应当位于FilterSecurityInterceptor之前,它起一个粘合剂的作用。负责捕获权限相关的异常,如果用户当前没有登录,则引导应用去登录界面,否则返回失败结果:

<bean id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean> <bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean> <bean id="accessDeniedHandler"
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.htm"/>
</bean>

这个配置展示了ExceptionTranslationFilter的典型功能,authenticationEntryPoint定义了登录入口,accessDeniedHandler配置了鉴权失败的返回页面。

SecurityContextPersistenceFilter

这个Filter用来在request之间保存Security Context;它的默认配置如下,使用HttpSession来保存conext。

<bean id="securityContextPersistenceFilter"
class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<property name='securityContextRepository'>
<bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
<property name='allowSessionCreation' value='true' />
</bean>
</property>
</bean>

UsernamePasswordAuthenticationFilter

前面说了ExceptionTranslationFilter里面有个authenticationEntryPoint,引导用户去登录。如果是采用用户名和密码登录的方式,输入的用户名和密码会被UsernamePasswordAuthenticationFilter接收,并完成认证。

<bean id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>

UsernamePasswordAuthenticationFilter里面配置一个AuthenticationManager,关于AuthenticationManager的配置,前面已经讲过。还可以配置AuthenticationSuccessHandler,AuthenticationFailureHandler,对登录成功或失败后续行为进行定制。

xml配置之http(解释)

当第一个http元素出现在xml配置文件里时,一个名叫springSecurityFilterChain的FilterChainProxy会被创建出来,并且http的具体配置会用于创建一个完整的filter chain。继续添加http元素,意味着创建额外的filter chain。 每个http元素至少会创建SecurityContextPersistenceFilter, ExceptionTranslationFilter,FilterSecurityInterceptor这三个Filter,并且无法替换成自定义版本(这里指“无法通过配置http元素的属性和子元素来替换”)。

如果在配置文件里面使用定义了AuthenticationManager,那么http动创建的所有Filter,都会按需自动注入这个manager。

http元素的重要属性如下:

  • access-decision-manager-ref 指向AccessDecisionManager,后者通过Spring bean来定义;
  • authentication-manager-ref 指向AuthenticationManager,后者通过Spring bean来定义;
  • entry-point-ref,指向AuthenticationEntryPoint,后者通过Spring bean来定义;
  • pattern,指定urli匹配模式
  • security,如果要设置的话,只能是none,表示对url pattern不使用安全策略;

http元素的重要子元素如下:

  • access-denied-handler,鉴权失败的处理器;
  • form-login,会创建UsernamePasswordAuthenticationFilter,以及LoginUrlAuthenticationEntryPoint;
  • logout, 创建LogoutFilter,后者和SecurityContextLogoutHandler一起工作;
  • session-management,创建SessionManagementFilter;
  • custom-filter, 添加自定义的Fiter,后者通过spring bean定义;通过属性,可指定该filter放在某个标准filter的前面,后面,或取代这个标准filter。

具体xml配置请参考文档,这里对http元素做简要说明,主要为了阐述如何围绕http这个元素,来构建一个完成的Filter chain。

Spring Security核心概念介绍的更多相关文章

  1. spring技术核心概念纪要

    一.背景 springframework 从最初的2.5版本发展至今,期间已经发生了非常多的修正及优化.许多新特性及模块的出现,使得整个框架体系显得越趋庞大,同时也带来了学习及理解上的困难. 本文阐述 ...

  2. Spring Security——核心类简介——获得登录用户的相关信息

    核心类简介 目录 1.1     Authentication 1.2     SecurityContextHolder 1.3     AuthenticationManager和Authenti ...

  3. Java开发学习(一)----初识Spring及其核心概念

    一. Spring系统架构 1.1 系统架构图 Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基. Spring Framework的发展也经历了很多版本的变更,每 ...

  4. Spring Security 与 OAuth2 介绍

    个人 OAuth2 全部文章 Spring Security 与 OAuth2(介绍):https://www.jianshu.com/p/68f22f9a00ee Spring Security 与 ...

  5. webpack的四个核心概念介绍

    前言 webpack 是一个当下最流行的前端资源的模块打包器.当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后 ...

  6. ElasticSearch入门及核心概念介绍

      Elasticsearch研究有一段时间了,现特将Elasticsearch相关核心知识和原理以初学者的角度记录下来,如有不当,烦请指正! 0. 带着问题上路——ES是如何产生的? (1)思考:大 ...

  7. Apache Maven的入门使用之常用操作以及核心概念介绍(2)

    我们接着上篇文章,来继续介绍Maven中几个核心的概念: POM (Project Object Model) Maven 插件 Maven 生命周期 Maven 依赖管理 Maven 库 POM ( ...

  8. Spring框架核心知识介绍

    一:spring框架介绍   1.spring框架是为了解决复杂的企业级应用而创建的, 使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情.但是Spring不仅仅局限于服务器 ...

  9. Spring Aop重要概念介绍及应用实例结合分析

    转自:http://bbs.csdn.net/topics/390811099 此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题.最近项目中遇到了以下几点需求,仔细 ...

随机推荐

  1. Node.js 搭建Web

    Express Express 是整个 Node.js 之中最为常见的一个框架(开发包),可以帮助我们快速构建一个WEB项目.(http://expressjs.com) 1.在 F 盘新建 node ...

  2. kibana5.6 源码分析以--环境搭建&技术准备

    Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的.你可以用kibana搜索.查看.交互存放在Elasticsearch索引里的数据,使用各种不同的图表.表格 ...

  3. ambari安装集群下python连接hbase之安装thrift

    简介: python连接hbase是需要通过thrift连进行连接的,ambari安装的服务中貌似没有自带安装hbase的thrift,我是看配置hbase的配置名称里面没有thrift,cdh版本的 ...

  4. Quartz.net 基于配置的调度程序实践

    1.Nuget 搜索并安装Quartz.net 2.3.3  2.添加配置到App.config <?xml version="1.0" encoding="utf ...

  5. winform实现QQ聊天气泡200行代码

    c# winform实现QQ聊天气泡界面,原理非常简单,通过webKitBrowser(第三方浏览器控件,因为自带的兼容性差)加载html代码实现,聊天界面是一个纯HTML的代码,与QQ的聊天界面可以 ...

  6. 安装和使用PhantomJS

    一.安装PhantomJS(linux环境安装) 将PhantomJS下载在/usr/local/src/packet/目录下(这个看个人喜好) 操作系统:CentOS 7 64-bit 1.下载地址 ...

  7. 如何使用 libtorch 实现 LeNet 网络?

    如何使用 libtorch 实现 LeNet 网络? LeNet 网络论文地址: http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf

  8. 微信js分享朋友圈(二)

    近期又用到微信分享的功能了.虽然不是第一次用了,依然我又有幸踩到了一个坑,所以分享一下吧. 根据微信sdk写的代码一步步很顺利,但是后面就是获取微信返回的分享结果的回调的时候IOS老是有问题,然后就网 ...

  9. 【题解】P5151 HKE与他的小朋友

    [题解]P5151 HKE与他的小朋友 实际上,位置的关系可以看做一组递推式,\(f(a_i)=f(a_j),f(a_j)=f(a_t),etc...\)那么我们可以压进一个矩阵里面. 考虑到这个矩阵 ...

  10. centos7手动编译安装Libvirt常见问题

    由于功能需要,体验了手动编译安装Libvrt,还是碰到了不少问题,这里总结如下仅限于centos7: 1.configure: error: You must install the pciacces ...