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. Spring Security 与 OAuth2 介绍

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

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

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

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

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

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

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

  7. Spring框架核心知识介绍

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

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

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

  9. Spring Boot 入门概念介绍

    使用spring-boot快速开发spring应用 转 http://itindex.net/detail/49108-spring-boot-%E5%BC%80%E5%8F%91 spring多年以 ...

随机推荐

  1. sizeToFit()使用心得

    sizeToFit()使用心得: 很多的初学者,包括我在内,当初在学习的时候,特别纠结什么时候用这个sizeToFit(). 下面我就来分享一下我的一些使用心得. 一.我们先来看看官方文档对sizeT ...

  2. JVM-触发Full GC的情况

    除直接调用System.gc外,触发Full GC执行的情况有如下四种: 1.老年代空间不足 老年代空间只有在新生代对象转入及创建为大对象.大数组时才会出现不足现象,当执行Full GC后空间仍然不足 ...

  3. Android特效专辑(三)——自定义不一样的Toast

    Android特效专辑(三)--自定义不一样的Toast 大家都知道,Android的控件有时候很难满足我们的需求,所以我们需要自定义View.自定义的方式很多,有继承原生控件也有直接自定义View的 ...

  4. ArcGIS Server注册数据库——以oracle为例

    原创文章,转载须标明出处自: https://www.cnblogs.com/gisspace/p/9089117.html ------------------------------------- ...

  5. 关于枚举,enum、Enum、EnumSet、RegularEnumSet、JumboEnumSet

    Apache Commons Lang. 在版本3中,enum相关的工具就留下EnumUtils. 首先, 所有enum,都默认实现了抽象类 java.lang.Enum .所以,所有enum都具备E ...

  6. 剑指offer十四之链表中倒数第k个结点

    一.题目 输入一个链表,输出该链表中倒数第k个结点. 二.思路 两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指正走(k-1)步,到达第k个节点.然后两个指针同时往后移动,当第一个结 ...

  7. Git 的安装步骤

    Git 的安装步骤 一.下载Git Git 的官网:https://git-scm.com/ 在 Git 的官网中点击Downloads,进入如下页面: 选择对应的操作系统,以博主为例,点击Windo ...

  8. (转)windows环境vue+webpack项目搭建

    首先,vue.js是一种前端框架,一般利用vue创建项目是要搭配webpack项目构建工具的,而webpack在执行打包压缩的时候是依赖node.js的环境的,所以,要进行vue项目的开发,我们首先要 ...

  9. poj 2541 Binary Witch

    Binary Witch http://poj.org/problem?id=2541 Time Limit: 1000MS   Memory Limit: 65536K       Descript ...

  10. awk特征相同行的合并

    [root@linux-node1 ~]# cat test.txt hisk01 hisk02 hisk03 hisk04 hisk05 hisk06 hisk07 hisk08 [root@lin ...