• Spring Security核心概念介绍


    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。

  • 相关阅读:
    弹出 提示窗口 背景显示灰色 可移动 模板
    rar文件的格式信息描述(中英文对照)
    android学习笔记48_实现软件国际化,实现文字国际化,和图片国际化
    php 7.2 安装 mcrypt 扩展
    laraveladmin 安装(总结)
    Laravel 精选资源大全
    Laravel 出现 No application encryption key has been specified
    Laravel5.5/6 报错call to undefined function openssl cipher iv length()
    一起谈.NET技术,Silverlight 游戏开发小技巧:动感小菜单2 狼人:
    一起谈.NET技术,你应该知道的15个Silverlight诀窍 狼人:
  • 原文地址:https://www.cnblogs.com/longhuihu/p/10604423.html
Copyright © 2020-2023  润新知