依赖包
基础包
<!-- 核心框架和类 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${springSecurity.version}</version>
</dependency>
<!-- 支持web层 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springSecurity.version}</version>
</dependency>
<!-- 整体配置支持 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springSecurity.version}</version>
</dependency>
<!-- 支持JSP标签库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${springSecurity.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
额外支持包
- 支持CAS
<!-- Addition: 支持CAS--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>${springSecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas-client</artifactId> <version>${springSecurity.version}</version> </dependency>
- 支持LDAP
<!-- Addition:支持LDAP --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> <version>${springSecurity.version}</version> </dependency>
- 支持ACL,加入
spring-security-acl-nnn.jar
- 支持OpenID ,加入
spring-security-openid-nnn.jar
装载SpringSecurity
配置web.xml文件
- context-param
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:beans.xml,classpath*:security.xml</param-value> </context-param>
- listener
- 加载Spring监听器 (因为下面的过滤器Filter中要使用到此Bean,所以使用Listener的方式加载spring)
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
- 加载Security Session生命周期事件监听器
<!--设置session 超时时间间隔:以分钟为单位,值必须为整数(如果值为零或负数,则表示会话将永远不会超时)--> <session-config> <session-timeout>60</session-timeout> </session-config> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener>
- 加载Spring监听器 (因为下面的过滤器Filter中要使用到此Bean,所以使用Listener的方式加载spring)
- filter
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
PS:
DelegatingFilterProxy
将过滤处理委托给一个由Security注入Spring容器的Filter
(我们无法对注册在web.xml中的Servlet Filter进行Bean注入, 但通过Spring的DelegatingFilterProxy
,使得我们可以在外部配置文件中配置实际工作的Filter)- 这个
filter-name
为springSecurityFilterChain对SpringSecurity
是有意义的, 用于在Spring上下文中查找Filter Bean
装载过程说明
- 容器创建一个
ServletContext(上下文)
- 这个WEB项目所有部分都将共享这个上下文
- 容器将
<context-param>
转化为键值对,交给ServletContext
- Spring监听器
ContextLoaderListener
,初始化Spring容器(调用contextInitialized(ServletContextEvent event)
)ServletContext sc= event.getServletContext();
this.contextLoader.initWebApplicationContext(sc);
- 获取
<context-param>
中contextConfigLocation
指定的配置文件context-param的值 = sc.getInitParameter("context-param的键");
- 交给
WebApplicationContext
,解析并注入配置文件中定义的Bean
- 获取
- 解析Security配置文件(
security.xml
)中的标签,创建注入Security Bean到Spring容器- Security解析包为:
spring-security-config.jar
,包下META-INF中有两个文件:- spring.schemas:标签的规范、约束
- spring.handlers:定义真正解析自定义标签的类
- 内容为:
http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
- 即spring security的标签解析由
SecurityNamespaceHandler
来处理
- 内容为:
- 注意:Security配置文件需添加Spring Security命名空间):
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> ... </beans>
- Security解析包为:
- 加载Security代理过滤器
DelegatingFilterProxy
,截获/*
所有请求DelegatingFilterProxy
- 位于
spring-web.jar
包,本身是和springSecurity无关 - 通过
registerBeanComponent
方法将FilterChainProxy
注册到Spring容器中,并控制注册的别名为springSecurityFilterChain
- 其实该类仅仅是初始化一个
FilterChainProxy
,然后把所有拦截的Request交给Spring容器中的FilterChainProxy
处理
- 位于
FilterChainProxy
- 是一个特殊的Servlet Filter,它可以链接任意一个或多个其他的过滤器
- package org.springframework.security.web
SecurityFilters
对于Web资源,我们大约可以只用6个过滤器来保护我们的应用系统:
Spring Security依赖一系列的Servlet Filter来提供不同的安全特性 (不需要显示的声明springSecurityFilterChain以及它所链接在一起的其他Filters)
- Spring Security会根据在xml中配置的标签,自动创建所需要的Bean
- 通过使用Java EE的servlet过滤器链按顺序组合起来
PS:
Spring Security中默认Filter顺序,通过enum SecurityFilters
定义
Value [Integer.MIN_VALUE,100~2100,Integer.MAX_VALUE]
,每个间隔100- 对于customer配置的Filter,有
after
,before
,position
三种顺序 (+-1)
100 CHANNEL_FILTER
ChannelProcessingFilter
:通道处理过滤器
- 只要
intercept-url标签
中包含requires-channel属性
,该过滤器就被创建,并把决策权交给ChannelDecisionManager决策处理器
处理 requires-channel
值有以下三种:值 含义 说明 any 任何通道都支持 决策管理器不做处理 https 只支持安全通道 决策管理器把决策任务交给ChannelProcessor列表循环处理
(若以http方式访问,则重定向到https通道,以SSL方式访问)http 只支持http 决策管理器把决策任务交给ChannelProcessor列表循环处理
(若以https方式访问,则重定向到http通道,以不安全的方式访问)
200 SECURITY_CONTEXT_FILTER
SecurityContextPersistenceFilter
:持久化SecurityContext
interface SecurityContext
:
- 用于存放认证实体
Authentication
- 方法:
Authentication getAuthentication();
获取认证实体void setAuthentication(Authentication authentication);
设置认证实体
- 实际存储在:
- Session中,可通过
getAttribute("SPRING_SECURITY_CONTEXT")
获取 - SecurityContextHolder中,可通过
SecurityContextHolder.getContext()
获取,有三种保存策略(SecurityContextHolderStrategy):- ThreadLocal 默认 (不能跨越多个request存在,即一次request有效)
- InheritableThreadLocal
- Global
- Session中,可通过
300 CONCURRENT_SESSION_FILTER
ConcurrentSessionFilter
:Session并发控制
- 从session缓存中获取当前session信息
- 若发现过期了,则跳转到
expired-url
配置的url,或者响应session失效提示信息
配置:
<sec:session-management>
<sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url= "/xxx"/>
</sec:session-management>
属性 | 说明 |
---|---|
max-sessions | Session最大并发数:当前认证实体的已注册的最大session数 |
expired-url | 跳转到指定url 若未配置,则提示:This session has been expired (possibly due to multiple concurrent logins being attempted as the same user). |
error-if-maximum-exceeded | true:若max-sessions为1,则第二次登录不了 false(默认) :若max-sessions为1,则第二次是能够登录的,但第一次登录的账号再次发起的请求,会跳转到 expired-url |
session-registry-ref | 默认使用:SessionRegistryImpl ,完成对发布的Session的生命周期事件的处理 |
400 LOGOUT_FILTER
LogoutFilter
:登出过滤器
监控一个实际为退出功能的URL(默认为/j_spring_security_logout
),
- 监控处理
filterProcessesUrl
请求(退出功能的URL,默认为/j_spring_security_logout
) - 循环
List<LogoutHandler>
每个LogoutHandler
的logout()
方法 - 成功后交由有
logoutSuccessHandler
处理(跳转到指定URL)
配置:
<sec:logout
logout-success-url=""
success-handler-ref=""
logout-url=""
invalidate-session="false"
deleteCookies=“XXX,XXX”
/>
属性 | 说明 |
---|---|
logout-url | 默认为: /j_spring_security_logout 对应filterProcessesUrl字段,设置要监控的退出URL |
logout-success-url | 默认为: / 会使用 SimpleUrlLogoutSuccessHandler 实现页面跳转 |
success-handler-ref | 指向一个LogoutSuccessHandler 的实现类 用于完成退出系统后的后续动作 |
invalidate-session | 用于配置SecurityContextHandler 的invalidateHttpSession 属性 |
PS:
logout-success-url
和 success-handler-ref
只能设置其中之一
- 使用
logout-success-url
:<sec:logout logout-success-url="/index"/>
- 使用
success-handler-ref
<sec:logout success-handler-ref="myLogoutSuccessHandler"/> <!-- myLogoutSuccessHandler:自定义的LogoutSuccessHandler 功能:为LogoutURL加入动态时间参数,以防止被浏览器缓存 --> <bean id="myLogoutSuccessHandler" class="com.cj.security.core.auth.MyLogoutSuccessHandler"> <property name="logoutSuccessUrl" value="https://sso.seagate.com/logout.html?doc="/> </bean>
500 X509_FILTER
X509AuthenticationFilter
: X509认证过滤器
600 PRE_AUTH_FILTER
RequestHeaderAuthenticationFilter
:预认证过滤器(需手动注入)
- 从RequestHeader中获取预认证用户信息,默认从header attribute
SM_USER
中获取用户名 extends AbstractPreAuthenticatedProcessingFilter
- 使用
PreAuthenticatedAuthenticationProvider
认证器处理PreAuthenticatedAuthenticationToken
获取认证实体- 可以使用
PreAuthenticatedGrantedAuthoritiesUserDetailsService
从Request中获取权限列表 - 也可使用
UserDetailsByNameServiceWrapper<T extends Authentication>
从其他地方获取权限列表
- 可以使用
例如:
<sec:http>
...
<sec:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
<sec:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM-USER"/>
<property name="exceptionIfHeaderMissing" value="false"/>
<property name="authenticationManager" ref="myAuthenticationManager" />
</bean>
```xml
<sec:http>
...
<sec:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
<sec:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM-USER"/>
<property name="exceptionIfHeaderMissing" value="false"/>
<property name="authenticationManager" ref="myAuthenticationManager" />
</bean>
<sec:authentication-manager id="myAuthenticationManager" >
<sec:authentication-provider ref="preauthAuthProvider" />
...
</sec:authentication-manager>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="myUserDetailsService"/>
</bean>
</property>
</bean>
<!-- 通过myUserDetailsService获取用户权限列表-->
<bean id="myUserDetailsService" class="com.cj.support.security.service.MyUserDetailsService"/>
700 CAS_FILTER
CAS认证过滤器(需手动注入)
CasAuthenticationEntryPoint
:认证入口点,会跳转到CAS Server提供的登录界面CAS Server
:对登录信息进行处理,如果登录成功,就跳转回本系统指定的URL,并携带ticket- 拦截URL:默认为
/j_spring_cas_security_check
- 对ticket进行验证
- 构建认证实体
CasAuthenticationToken
PS: CAS(Center Authentication Service) 中心认证服务,提供单点登录门户 (SSO:Sigle Sign-On Portal)
800 FORM_LOGIN_FILTER
UsernamePasswordAuthenticationFilter
:登录认证过滤器
- 对登录账号进行认证处理,并把认证通过的实体保存到
SecurityContext
中 - 默认处理请求:
POST : /j_spring_security_check?j_username=?&j_password=?
extends AbstractAuthenticationProcessingFilter
- doFilter是由父类AbstractAuthenticationProcessingFilter完成
- 认证
abstract Authentication attemptAuthentication(req,resp)
- session策略处理认证信息
sessionStrategy.onAuthentication(auth,req,resp)
(通过<session-management>
标签定义) - 结果处理
- 认证
@Override Authentication attemptAuthentication(req,res)
- 默认只对
POST
方法处理,否则throw new AuthenticationServiceException
- 从request中获取
username
&password
- 构造未认证的
UsernamePasswordAuthenticationToken
- 构建details
authenticationDetailsSource.buildDetails(request)
- 通过
AuthenticationManager
完成认证任务,返回认证实体authenticationManager.authenticate(Authentication token)
- 默认只对
- doFilter是由父类AbstractAuthenticationProcessingFilter完成
配置:
<sec:form-login
login-page="/index"
authentication-failure-url="/index?login_error=t"
default-target-url="/index"
login-processing-url="/common/login-process"
password-parameter="myPassword"
username-parameter="myUsername" />
相当于:
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="postOnly " value="true"/> <!-- 默认即为true,只处理post请求 -->
<property name="filterProcessesUrl" value="/common/login-process"/>
<property name="usernameParameter" value="myPassword "/>
<property name="passwordParameter" value="myUsername"/>
</bean>
属性 | 说明 |
---|---|
login-page | 登录页面 |
authentication-failure-url | 认证失败时跳转的URL |
default-target-url | 认证成功时跳转的URL 默认为: /spring_security_login?login_error=xxx |
authentication-failure-handler-ref | 登录失败处理器 |
authentication-success-handler-ref | 登录成功处理器 |
login-processing-url | 默认为:/j_spring_security_check |
username-parameter | 默认为: j_username |
password-parameter | 默认为: j_password |
900 OPENID_FILTER
OpenIDAuthenticationFilter
: OpenID认证
1000 LOGIN_PAGE_FILTER
DefaultLoginPageGeneratingFilter
: 生成默认登录页
1100 DIGEST_AUTH_FILTER
DigestAuthenticationFilter
: Digest认证过滤器(需手动注入)
<sec:http>
<sec:custom-filter ref="digestFilter" position="DIGEST_AUTH_FILTER" />
...
</sec:http>
<bean id="digestFilter" class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<property name="userDetailsService" ref="myUserService" />
<property name="authenticationEntryPoint" ref="digestEntryPoint" />
</bean>
<bean id="digestEntryPoint" class="org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<property name="realmName" value="shuttle vendor" />
<property name="key" value="zero" />
</bean>
1200 BASIC_AUTH_FILTER
BasicAuthenticationFilter
: 基本认证
配置<sec:http-basic/>
1300 REQUEST_CACHE_FILTER
RequestCacheAwareFilter
: 用于用户认证成功后,重新恢复因为登录被打断的请求(由requestCache处理)
说明:
未认证用户发送一个请求Request时,会有ExceptionTranslationFilter
捕获到Authentication
或匿名用户引发的AccessDeniedException
,并将此Request暂时保存到Session,供下次通过认证时恢复这个Request
interface RequestCache
实现类有:HttpSessionRequestCache
(默认),NullRequestCache
配置不恢复打断请求:
<sec:request-cache ref="nullRequestCache"/>
<bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
1400 SERVLET_API_SUPPORT_FILTER
SecurityContextHolderAwareRequestFilter
: 封装一些ServletApi到Request对象
- 功能: 包装
Request对象
到SecurityContextHolderAwareRequestWrapper对象
,传递到下一个Filter - 原理:使用装饰模式(Decorate Model),装饰HttpServletRequest对象
SecurityContextHolderAwareRequestWrappe
增加对非匿名认证实体的查询和判断apiboolean isUserInRole(String role)
:判断非匿名认证实体是否授权String getRemoteUser()
:获取非匿名认证实体的登录账号Principal getUserPrincipal()
:获取非匿名认证实体
1500 JAAS_API_SUPPORT_FILTER
JaasApiIntegrationFilter
1600 REMEMBER_ME_FILTER
RememberMeAuthenticationFilter
: RememberMe AutoLogin 认证
说明: 当SecurityContextHolder中不存在Authentication用户授权信息时,此Filter会调用rememberMeServices 的autoLogin()方法从cookie中获取用户信息自动登录
- 创建条件:配置了
<remember-me>
- 执行条件:SecurityContext实例中不存在Authentication认证信息时 (即没有认证实体时)
- 处理:
- 从cookie获取认证实体Authentication
rememberMeServices.autoLogin(request, response)
- 交由AuthenticationManager验证
authenticationManager.authenticate(auth)
(通过后设置认证信息到SecurityContext中) - 下一个Filter
- 从cookie获取认证实体Authentication
Remember me
功能设置了一个cookie在用户的浏览器上,它包含一个Base64编码的字符串,包含以下内容:
- 一个MD5的散列值,包括过期日期/时间、用户名和密码;
- 应用的key值,是在
元素的key属性中定义的
使用:
通过配置
<remember-me>
,添加此Filter到过滤器链中<sec:remember-me key="" data-source-ref="" user-service-ref="" token-validity-seconds="" token-repository-ref=""/>
属性 说明 Key 建议key值包含应用的唯一名称以及至少36位长度的随机字符,eg: 420046A65F774C9ABBF83E73DA1C07C9 token-validity-seconds cookie的过期时间 data-source-ref / token-repository-ref 默认使用TokenBasedRememberMeServices,也可配置使用PersistentTokenBasedRememberMeServices 在
<form>
中触发rememberMe功能<!-- URL?_spring_security_ remember_me=true --> <input id="remember" name="_spring_security_ remember_me" type="checkbox" value="true"/> <label for="remember "> Remember Me? </label>
1700 ANONYMOUS_FILTER
AnonymousAuthenticationFilter
: 无认证实体时添加一个匿名认证实体
- 执行条件:当前SecurityContext中没有认证实体
- 功能: 产生一个匿名认证实体,并保存到SecurityContext中
即若前面Filter(UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter等)都未认证成功,则在当前的SecurityContext中添加一个经过匿名认证的token
配置:
<sec:anonymous key="test" granted-authority="ROLE_GUEST" username="guest"/>
属性 | 说明 |
---|---|
granted-authority | 设置权限,注入到authorities授权列表 默认为: ROLE_ANONYMOUS |
username | 匿名认证账号 默认为: anonymousUser |
PS:
通过servlet的getRemoteUser
等方法无法获取到登录账号,
因为SecurityContextHolderAwareRequestFilter
过滤器在此Filter的前面
1800 SESSION_MANAGEMENT_FILTER
SessionManagementFilter
: Session管理,提供两大类功能:
- session固化保护:通过session-fixation-protection配置
- session并发控制:通过concurrency-control配置
配置security.xml:
<sec:session-management session-fixation-protection="migrateSession" invalid-session-url="/common/timeout.jsp">
<sec:concurrency-control max-sessions="1" expired-url= "/index?error=This session has been expired"/>
</sec:session-management>
属性 | 含义 | 说明 |
---|---|---|
session-fixation-protection | session固化保护 | none :使得session固化攻击失效(未配置其他属性);migrateSession :当用户经过认证后分配一个新的session,它保证原session的所有属性移到新session中;newSession :当用户认证后建立一个新的session,原(未认证时)session的属性不会进行移到新session中来;invalid-session-url : 设置invalidSessionStrategy的跳转URL,默认使用 SimpleRedirectInvalidSessionStrategy |
concurrency-control | session并发控制 | 从session缓存中获取当前session信息,如果发现过期了,就跳转到expired-url配置的url或者响应session失效提示信息 1. error-if-maximum-exceeded="true",max-sessions="1" : 第二次登录时,是登录不了的;2. error-if-maximum-exceeded="false" : 第二次是能够登录到系统的,但是第一个登录的账号再次发起请求时,会跳转到expired-url配置的url中;3. 如果没有配置,则显示提示信息:This session has been expired (possibly due to multiple concurrent logins being attempted as the same user). (参见ConcurrentSessionFilter) |
配置web.xml:
<!-- 设置session 超时时间 -->
<session-config>
<session-timeout>60</session-timeout>
</session-config>
<!-- 让servelt容器通知SpringSecurity session失效 -->
<!-- HttpSessionEventPublisher监听器:主要监听sessionCreated、sessionDestroyed事件,发布通知-->
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
PS:
ConcurrentSessionFilter
过滤器也是处理session失效,但是它所处理的仅仅是封装后的SessionInformation
类,如果这个类满足失效条件,再执行session.invalidate
强制失效
1900 EXCEPTION_TRANSLATION_FILTER
ExceptionTranslationFilter
:仅捕获此Filter之后发生的异常
仅捕获此Filter之后发生的异常: FilterSecurityInterceptor,SwitchUserFilter,1900 Filter之后的自定义拦截器的异常
主要拦截两类安全异常:
AuthenticationException
认证异常- 清空SecurityContext中的认证实体Authentication
- 保存当前被打断的请求
requestCache.saveRequest(request, response);
- 认证入口点
AuthenticationEntryPoint
处理此异常
AccessDeniedException
访问拒绝异常- 匿名用户引发 (同认证异常Authentication的处理)
- 非匿名用户引发 (由
AccessDeniedHandler
处理)
配置:
<sec:http auto-config="false"
entry-point-ref="http403EntryPoint"
access-denied-page="/home/accessDenied" >
...
</sec:http>
属性 | 说明 |
---|---|
entry-point-ref | 认证入口点,指向认证失败或匿名用户拒绝访问异常时的处理bean 默认:根据 http标签 下具体配置的认证类型加载对应的AuthenticationEntryPoint |
access-denied-page | 访问拒绝时跳转的页面 为设置 access-denied-handler 时,对应AccessDeniedHandlerImpl 的errorPage |
手动指定AuthenticationEntryPoint:
<!--
这里使用Security自带的AuthenticationEntryPoint,也可使用自定义的
( Response 403 error,msg: "Access Denied" )
-->
<bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
2000 FILTER_SECURITY_INTERCEPTOR
FilterSecurityInterceptor
: Web级别的访问控制
- 功能:完成URL级别的授权处理
extends abstract class AbstractSecurityInterceptor
- 获取资源权限列表 (通过
SecurityMetadataSource
资源权限获取器) - 认证 (通过
AuthenticationManager
认证管理器) - 访问控制判断 (通过
AccessDecisionManager
访问决策器)
- 获取资源权限列表 (通过
配置:
<sec:http pattern="/resources/**" security="none"/>
<sec:http>
<sec:intercept-url pattern="/security/**" access="ROLE_ADMIN"/>
<sec:intercept-url pattern="/back/**" access="ROLE_ADMIN,ROLE_USER"/>
...
</sec:http>
<sec:http use-expressions="true" >
<sec:intercept-url pattern="/" access="hasRole('ROLE_GUEST') or authenticated"/>
<sec:intercept-url pattern="/" access="permitAll"/>
<sec:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
<sec:intercept-url pattern="/api/**" method="GET" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
...
</sec:http>
<sec:intercept-url> 的属性 |
说明 |
---|---|
pattern | a URL pattern |
path-type | 设置pattern的匹配类型 (用于判断Request和设置的URL pattern是否匹配) 可以设置为: ant (默认),regex ,ciRegex (由对应的 RequestMatcher 处理) |
access | security rules 若设置 use-expressions='true' ,则可使用SpEL (Spring expressions language) |
method | allowed Http Method (GET , POST , HEAD , OPTIONS , PUT , DELETE , TRACE )default value: null (allow all) |
required-channel | 允许的通道:http ,https ,any (默认) |
security | none 表示不进行安全检查,即不做授权判断,直接通过 |
SpEL expressions added by Spring Security 3.0 :
expression | 说明 |
---|---|
authentication | The user’s authentication object |
denyAll | Always evaluates to false |
hasAnyRole(list of roles) | true if the user has been granted any of the roles specified |
hasRole(role) | true if the user has been granted the specified role |
hasIpAddress(IP Address) | The user's IP address (only available in web security) |
isAnonymous() | true if the current user is an anonymous user |
isAuthenticated() | true if the current user is not anonymous |
isFullyAuthenticated() | true if the current user is neither an anonymous nor a remember-me user |
isRememberMe() | Always evaluates to true |
permitAll | Always evaluates to true |
principal | The user’s principal object |
authentication | The user’s authentication object |
2100 SWITCH_USER_FILTER
SwitchUserFilter
: 切换用户
http标签
HttpSecurityBeanDefinitionParser
解析
最小化配置:
<sec:http auto-config="true" >
<sec:intercept-url pattern="/**" access="ROLE_ADMIN"/>
<!--使用Ant风格的路径-->
</sec:http>
- 使用
<http>
标签将会自动创建一个FilterChainProxy
(它会委托给配置在web.xml
中的DelegatingFilterProxy
)以及链中的所有过滤器Bean - 通过配置
auto-config='true'
,将得到三个免费赠品:- 额外登陆页
- http基本认证
- 登出功能
- 即等价于显示配置了:
<form-login/>
,<http-basic/>
,<logout/>
标签<sec:http auto-config="true" > <sec:form-login/> <sec:http-basic/> <sec:logout/> <sec:intercept-url pattern="/**" access="ROLE_ADMIN"/> </sec:http>
认证
一般处理过程:
XxxAuthenticationFilter
拦截Request,获取需认证的信息,构建要进行认证的认证实体xxxTokenAuthenticationManager
认证管理器,对xxxToken进行认证 (实际调用实现类ProviderManager
的authenticate(xxxToken)
方法)- 循环
List<AuthenticationProvider>
认证器列表 - 使用支持此认证Token的AuthenticationProvider认证器(
XxxAuthenticationProvider
)进行认证,返回认证Tokenprovider.supports(authentication.getClass())
找到支持此认证xxxToken的认证器provider.authenticate(authentication)
对此xxxToken进行认证,返回认证过的认证实体- 注意:只要有一个provider认证成功,则成功,后续的provider将不会执行
- 循环
简单配置:
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider>
<sec:user-service>
<sec:user authorities="ROLE_USER" name="tom" password="123"/>
<sec:user authorities="ROLE_USER" name="jerry" password="456"/>
<sec:user authorities="ROLE_ADMIN" name="admin" password="111"/>
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
PS:
用户的认证信息通过SecurityContextHolder
信息传递。
eg: SecurityContextHolder.getContext().getAuthentication();
Authentication接口可以实现的方法:
方法签名 | 描述 |
---|---|
Object getPrincipal() |
返回安全实体的唯一标识(如,一个用户名) |
Object getCredentials() |
返回安全实体的凭证信息 |
List<GrantedAuthority> getAuthorities() |
得到安全实体的权限集合,根据认证信息的存储决定的 |
Object getDetails() |
返回一个跟认证提供者相关的安全实体细节信息 |
JDBC认证
SpringSecurity使用DaoAuthenticationProvider
支持UsernamePasswordAuthenticationToken
需使用自定义的UserDetailsService
实现类,从DB获取用户信息构建UserDetails
对象,例如:
<bean id="myUserDetailsService" class="com.cj.security.service.MyUserDetailsServiceImpl"/>
public class MyUserDetailsService implements UserDetailsService{
static Logger logger = Logger.getLogger(MyUserDetailsService.class);
private ISecurityService securityService;
public ISecurityService getSecurityService(){
return securityService;
}
@Inject
public void setSecurityService(ISecurityService securityService){
this.securityService = securityService;
}
@Override
public UserDetails loadUserByUsername(String account)
throws UsernameNotFoundException{
//MyUser implements UserDetails, CredentialsContainer
//一般获取用户的account,password,salt信息
MyUser user=securityService.getUser(account);
if(user==null)
throw new UsernameNotFoundException("No User");
//获取用户角色列表
List<GrantedAuthority> glist=securityService.getGrantedAuthorities(account);
if(glist==null || glist.size()==0)
throw new UsernameNotFoundException("No GrantedAuthority");
UserDetails userDetails=new MyUser(user, glist);
return userDetails;
}
}
password使用plaintext明文(即不加密)
<sec:authentication-manager alias="myAuthenticationManager" >
<sec:authentication-provider user-service-ref="myUserDetailsService" />
</sec:authentication-manager>
使用MD5+salt:username 加密 (即对{usermane}password加密)
<sec:authentication-manager>
<sec:authentication-provider user-service-ref="myUserDetailsService">
<sec:password-encoder hash="md5">
<sec:salt-source user-property="username"/>
</sec:password-encoder>
</sec:authentication-provider>
</sec:authentication-manager>
相当于:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailsService">
<password-encoder ref="passwordEncoder">
<salt-source ref="saltSource"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
<bean class="org.springframework.security.authentication.dao.ReflectionSaltSource" id="saltSource">
<property name="userPropertyToUse" value="username"/>
</bean>
使用MD5+salt:radom 加密(推荐!)
<sec:authentication-manager alias="myAuthenticationManager" >
<sec:authentication-provider user-service-ref="mySaltUserDetailsService" >
<sec:password-encoder hash="md5">
<sec:salt-source user-property="usersalt"/>
</sec:password-encoder>
</sec:authentication-provider>
</sec:authentication-manager>
PS:扩展数据库schema,salt要与用户记录一起保存在数据库
,eg: SysUser(account,password,enable,usersalt)
- addUser/changePassword(SysUser u)
int salt=(int)(Math.random()*1000000); u.setUsersalt(String.valueOf(salt)); u.setPassword(passwordEncoder.encodePassword(u.getPassword(), u.getUsersalt()));
- SQL insert user
insert into sys_user(username, password, enabled, usersalt) values ('admin','admin',true,CAST(RAND()*1000000000 AS varchar));
LDAP认证
使用LdapAuthenticationProvider
支持UsernamePasswordAuthenticationToken
LdapAuthenticator authenticator;
用于认证用户,不通过则throw BadCredentialsException
LdapAuthoritiesPopulator authoritiesPopulator;
用于获取权限信息列表(即角色列表)boolean hideUserNotFoundExceptions = true;
必须加入依赖包:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>${springSecurity.version}</version>
</dependency>
LDAP 介绍:
- Lightweight Directory Access Protocol 轻量级目录访问协议
- 是目录服务在TCP/IP上的实现
- 允许根据需要使用ACI(一般都称为ACL或者访问控制列表)控制对数据读和写的权限
PS: ACI可以根据谁访问数据、访问什么数据、数据存在什么地方以及其它对数据进行访问控制。 因为这些都是由LDAP目录服务器完成的,所以不用担心在客户端的应用程序上是否要进行安全检查。
LDIF文件:
- LDIF用文本格式表示目录数据库的信息,以方便用户创建、阅读和修改
- 按照树型结构组织,由条目Entry组成(相当于DB中Table的记录)
SpringSecurity LDAP 提供两种认证方式:
BindAuthenticator
绑定认证- LDAP服务器进行认证和校验用户密码
- 默认
PasswordComparisonAuthenticator
密码对比认证- SpringSecurity负责将密码编码成目录期望的格式然后与目录进行对比认证校验
- 需配置
<sec:password-compare/>
,默认LDAP的密码编码算法为SHA
- 为了安全,密码对比策略并不能真正从目录上读取(基于安全策略,读取目录的密码通常会被拒绝)
- 作为替代,PasswordComparisonAuthenticator会从用户的目录条目作为根节点进行一个LDAP查找,试图查找与SpringSecurity编码密码值匹配的密码属性值
配置LdapServer:
<sec:ldap-server url="ldap://ldap.sing.seagate.com:389/o=xxx" id="ldapServer" />
通过LDAP完成用户认证,并加载用户的权限列表
配置从LDAP上获取认证用户(包括角色列表),直接使用LdapAuthenticationProvider
(下面示例使用密码对比认证):
<sec:authentication-manager alias="myAuthenticationManager">
<sec:ldap-authentication-provider
server-ref="ldapLocal"
user-search-base="ou=people"
user-search-filter="(uid={0})"
group-search-base="ou=Groups">
<sec:password-compare/>
</sec: ldap-authentication-provider>
</sec:authentication-manager>
属性 | 说明 | 默认 |
---|---|---|
group-search-base | 用于查找group(角色) 定义了基础的DN,LDAP集成应该基于此往下为用户查找一个或多个的匹配项。 |
默认值会在LDAP根中进行查找,这可能会代价较高; |
group-search-filter | LDAP查找的过滤器,用来匹配用户的DN与group-search-base之下的条目属性; 过滤器通过两个参数进行参数化设置: 第一个({0})作为用户的DN 第二个作为({1})作为用户的名字 |
默认值为(uniqueMember={0}) |
group-role-attribute | 它定义了匹配条目中用来组装用户GrantedAuthority的属性; | 默认值为cn |
role-prefix | 要拼到在group-role-attribute中发现值的前缀以产生Spring Security的GrantedAuthority | 默认值为“ROLE_” |
user-search-base | 用于查找users | / |
user-search-filter | 用于过滤users | / |
user-details-class | 匹配构建UserDetails对象方式: inetOrgPerson,person | person |
通过LDAP完成用户认证,从其他地方加载用户的权限列表
配置
AuthenticationManager
&LdapAuthenticationProvider
<sec:authentication-manager alias="myAuthenticationManager"> <!-- <sec:ldap-authentication-provider user-search-base="ou=people" user-search-filter="(uid={0})" /> --> <sec:authentication-provider ref="ldapAuthenticationProvider"/> </sec:authentication-manager> <bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg name="authenticator" ref="bindAuthenticator"/> <constructor-arg name="authoritiesPopulator" ref="ldapAuthoritiesPopulator"/> <!-- <property name="hideUserNotFoundExceptions" value="false"/> --> </bean>
配置Provider中的
LdapAuthenticator
用于认证用户(DirContextOperations
对象)<bean id="bindAuthenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg name="contextSource" ref="ldapServer"/> <property name="userSearch" ref="ldapSearch"/> </bean> <bean id="ldapSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg name="searchBase" value="ou=people"/> <!-- user-search-base --> <constructor-arg name="searchFilter" value="(uid={0})"/> <!-- user-search-filter --> <constructor-arg name="contextSource" ref="ldapServer"/> </bean>
配置Provider中的
LdapAuthoritiesPopulator
用于获取权限信息列表(Collection<? extends GrantedAuthority>
)方式1:扩展
UserDetailsService
<bean class="org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator" id="ldapAuthoritiesPopulator"> <constructor-arg ref="myUserDetailsService"/> </bean> <bean id="myUserDetailsService" class="com.cj.security.service.MyUserDetailsServiceImpl"/>
public class MyUserDetailsService implements UserDetailsService{ static Logger logger = Logger.getLogger(MyUserDetailsService.class); private ISecurityService securityService; public ISecurityService getSecurityService(){ return securityService; } @Inject public void setSecurityService(ISecurityService securityService){ this.securityService = securityService; } @Override public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException{ //获取用户角色列表 List<GrantedAuthority> glist=securityService.getGrantedAuthorities(account); if(glist==null || glist.size()==0) throw new UsernameNotFoundException("No GrantedAuthority"); UserDetails userDetails=new User(account,null,glist); return userDetails; } }
方式二:扩展
LdapAuthoritiesPopulator
<bean id="ldapAuthoritiesPopulator" class="com.cj.support.security.ldap.MyLdapAuthoritiesPopulator"/>
public class MyLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator{ static Logger logger = Logger.getLogger(MyLdapAuthoritiesPopulator.class); private ISecurityService securityService; public ISecurityService getSecurityService(){ return securityService; } @Inject public void setSecurityService(ISecurityService securityService){ this.securityService = securityService; } @Override public Collection<? extends GrantedAuthority> getGrantedAuthorities( DirContextOperations userData, String username){ return securityService.getGrantedAuthorities(username); } }
CAS
使用必须额外加入CAS包
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>${springSecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas-client</artifactId> <version>${springSecurity.version}</version> </dependency>
将
CasAuthenticationFilter
加入FilterChain,并注入Cas AuthenticationEntryPoint
<sec:http auto-config="true" entry-point-ref="casAuthEntryPoint" access-denied-page="/home/accessDenied"> <sec:custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"/> <sec:logout logout-success-url="/index"/> </sec:http>
配置CAS 认证入口点
AuthenticationEntryPoint
<bean id="casAuthEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="https://server:8443/cas/login"/> <property name="serviceProperties" ref="casService"/> </bean> <bean id="casService" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="http://localhost:8888/docms/j_spring_cas_security_check"/> </bean>
配置
CasAuthenticationFilter
,注入authenticationManager
<bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationmanager"/> </bean> <sec:authentication-manager alias="authenticationmanager"> <sec:authentication-provider ref="casAuthenticationProvider"/> </sec:authentication-manager>
配置
CasAuthenticationProvider
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <property name="ticketValidator" ref="casTicketValidator"/> <property name="serviceProperties" ref="casService"/> <property name="key" value="docms"/> <property name="authenticationUserDetailsService" ref="authenticationUserDetailsService"/> </bean> <bean id="casTicketValidator" class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg value="https://server:8443/cas/"/> </bean> <bean id="authenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userDetailsManager"/> </bean>
Siteminder
使用RequestHeaderAuthenticationFilter
获取经过Siteminder认证后的用户信息,通过PreAuthenticatedAuthenticationProvider
认证器进一步处理PreAuthenticatedAuthenticationToken
构建认证实体
具体请参考上文中【600 PRE_AUTH_FILTER】的相关介绍
访问控制
SpringSecurity提供两个访问控制的级别:
- Web层(RequestURL级别):基于Servlet Filter
- 使用
FilterSecurityInterceptor
(URL拦截器)
- 使用
- 方法层(Method级别):基于Spring AOP
- 使用
MethodSecurityInterceptor
(Method拦截器)
- 使用
这两个拦截器都继承自abstract class AbstractSecurityInterceptor
,包含:
interface SecurityMetadataSource
资源权限获取器:资源权限表,SpringSecurity启动时就收集创建interface AuthenticationManager
认证管理器:对认证实体重新进行认证interface AccessDecisionManager
访问决策器:用于验证认证实体Authentication是否有权限访问资源accessDecisionManager.decide(authenticated, fi, attributes);
- 通过统计投票者的投票结果,完成决策(调用
List<AccessDecisionVoter> decisionVoters
投票者们的vote()
方法,统计投票) - 有三种访问决策管理器的实现类:
AffirmativeBased
(默认,一票赞成则通过)ConsensusBased
(多数:赞成票>反对票)UnanimusBased
(无异议:全部赞成则通过)
- 默认加载的投票器
AccessDecisionVoter
为:AuthenticatedVoter
RoleVoter
- 注意: 若
<sec:http>
中使用use-expressions="true"
则注入WebExpressionVoter
,不再加载其他AccessDecisionVoter
Web层(RequestURL级别)
基于Servlet Filter
一般处理过程
FilterSecurityInterceptor
拦截Request- 从
FilterSecurityMetadataSource
获取访问Request所需的权限列表 - 使用
AuthenticationManager
对当前认证实体重新认证 - 使用
AccessDecisionManager
实现类 的decide(authentication, fi, attributes)
方法决策当前Request是否有访问权限- 默认为
AffirmativeBased
决策访问器 - 循环投票器,统计投票结果
List<AccessDecisionVoter>
- 投票器
AccessDecisionVoter
:boolean support(attr)
判断是否支持,不支持则返回ACCESS_ABSTAIN
,表弃票int vote(auth,fi,attributes)
投票,并返回投票结果
- 默认为
扩展:从其他地方获取访问权限
PS: 因为SpringSecurity自带的FilterSecurityInterceptor
会在SecurityNamespaceHandler
中就加载构建,无法修改,故在此Filter之前构建自己的Filter进行授权处理
配置
<!-- 1. 设置Filter--> <sec:http> <sec:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="mySecurityFilter" /> <!-- 定义匿名用户--> <sec:anonymous key="test" granted-authority="ROLE_GUEST" username="guest"/> </sec:http> <!-- 2. 自定义FilterSecurityInterceptor --> <bean id="mySecurityFilter" class="com.cj.security.core.filter.MySecurityFilter"> <property name="securityMetadataSource" ref="mySecurityMetadataSource"/> <property name="accessDecisionManager" ref="myAccessDecisionManager"/> <property name="authenticationManager" ref="myAuthenticationManager"/> </bean> <!--3. 自定义SecurityMetadataSource --> <bean id="mySecurityMetadataSource" class="com.cj.support.security.intercept.MySecurityMetadataSource"> <constructor-arg name="securityService" ref="securityService"/> </bean> <!-- 4. 定义认证管理--> <sec:authentication-manager id="myAuthenticationManager" > <sec:authentication-provider user-service-ref="myUserDetailsService" /> </sec:authentication-manager> <!-- 5. 自定义决策管理:增加匿名用户的访问控制--> <bean id="myAccessDecisionManager" class="com.cj.security.core.access.MyAccessDecisionWithGuestManager"> <property name="anonymous" value="ROLE_GUEST"/> </bean>
自定义FilterSecurityInterceptor
public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter{ ... @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{ //System.out.println("MySecurityFilter-------"); FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } private void invoke(FilterInvocation fi) throws IOException, ServletException { Collection<ConfigAttribute> attributes=this.obtainSecurityMetadataSource().getAttributes(fi); InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } }
自定义SecurityMetadataSource,在构造时加载所有访问控制信息
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private ISecurityService securityService; private static Map<RequestMatcher, Collection<ConfigAttribute>> resourceMap = null; public MySecurityMetadataSource(ISecurityService securityService) { this.securityService = securityService; resourceMap=securityService.loadResourceDefine(); } @Override public boolean supports(Class<?> clazz){ return true; } //其他方法实现,可参考DefaultFilterInvocationSecurityMetadataSource类 ... }
自定义决策管理:增加匿名用户的访问控制判断
public class MyAccessDecisionWithGuestManager implements AccessDecisionManager{ private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); private String anonymous="ROLE_ANONYMOUS"; public String getAnonymous(){ return anonymous; } public void setAnonymous(String anonymous){ this.anonymous = anonymous; } private boolean isFullyAuthenticated(Authentication authentication) { return (!authenticationTrustResolver.isAnonymous(authentication) && !authenticationTrustResolver.isRememberMe(authentication)); } @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException{ if(configAttributes == null) { return; } //所请求的资源拥有的权限(一个资源对多个权限) Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while(iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //访问所请求资源所需要的权限 String attribute = configAttribute.getAttribute(); //System.out.println("needPermision:"+attribute); if(this.anonymous.equals(attribute)){ if(authenticationTrustResolver.isAnonymous(authentication) || isFullyAuthenticated(authentication) || authenticationTrustResolver.isRememberMe(authentication)) return; } //用户所拥有的权限authentication for(GrantedAuthority ga : authentication.getAuthorities()) { if(attribute.equals(ga.getAuthority())) return; } } //没有权限 throw new AccessDeniedException("Access Denied -- You don't have permission!"); } @Override public boolean supports(ConfigAttribute attribute){ return true; } @Override public boolean supports(Class<?> clazz){ return true; } }
方法层(Method级别)
基于Spring AOP
一般处理过程
- 在
SecurityNamespaceHandler
中注入GlobalMethodSecurityBeanDefinitionParser
,MethodSecurityMetadataSourceBeanDefinitionParser
- 收集配置的访问控制信息存入
MethodSecurityMetadataSource
实例中
支持四种方法层的安全
Spring 简单注解:
<global-method-security secured-annotations="enabled" />
@Secured
/* 指认证的用户必须被授予"ROLE_SPITTER", "ROLE_ADMIN"其中的一个权限才能访问该方法; 如果没有这两个中任何一个权限,将抛出异常或是AuthenticationException or AccessDeniedException的子异常; 如果方法在Web请求中调用,异常将被SpringSecurity自动调用 */ @Secured({"ROLE_SPITTER", "ROLE_ADMIN"}) public void addSpittle(Spittle spittle) { // ... }
JSR-250标准:
<!-- JSR-250's @RolesAllowed与secured-annotations可同时被启用, 推荐使用该方式启用注解,因为他是Java的标准注解。--> <global-method-security jsr250-annotations="enabled" />
@RolesAllowed
@RolesAllowed({"ROLE_USER","ROLE_ADMIN"}) public void changePassword(String username, String password);
@PermitAll
@DenyAll
Pre-/Post-invocation (可使用SpEL表达式):
<global-method-security pre-post-annotations="enabled" />
@PreAuthorized
: 基于表达式的结果在调用方法或类前限制被访问。/* 拥有角色ROLE_SPITTER的用户的参数spittle字符只能少于140个, 而拥有角色ROLE_PREMIUM的用户则不受此限制 */ @PreAuthorize("(hasRole('ROLE_SPITTER') and #spittle.text.length() <= 140) or hasRole('ROLE_PREMIUM')") public void addSpittle(Spittle spittle) { ... }
@PostAuthorized
:如果表达式结果为false,允许方法被调用,同时抛出一个安全异常。该注解主要是基于被保护的方法的返回值来决定执行表达式/* 当返回的Spittle对象属于被认证的用户时才可以访问该方法 本例中returnObject是SpringEL提供的一个name用于方便获取返回的对象, 而principal是SpringSecuroty提供的用于代表被当前认证的用户。 如果认证失败,则AccessDeniedException异常将抛出 */ @PostAuthorize("returnObject.spitter.username == principal.username") public Spittle getSpittleById(long id) { ... }
@PreFilter
:允许一个方法被调用,但是用每一个表达式去过滤方法的结果。@PostFilter
:允许一个方法被调用,但是过滤在进如方法之前的输入。
使用protect-pointcut添加安全切点
<!-- 该配置将识别任何拥有@Sensitive的方法 access属性则指,认证的用户必须拥有的角色去访问表达式识别的 --> <global-method-security> <protect-pointcut access="ROLE_SPITTER" expression="execution(@com.habuma.spitter.Sensitive * *.*(String))"/> </global-method-security>
结合SpringMVC使用
在mvc的servlet配置文件(xxx-servlet.xml):
<sec:global-method-security pre-post-annotations="enabled"/>
PS: 若要在MVC Controller方法上加
@PreAuthorize
,@PostAuthorize
,@PreFilter
,@PostFilter
, 此配置一定要放在spring mvc的xml中,否则无效在XxxController方法上添加方法注解:
@PostAuthorize("!(#type=='user' and returnObject[extra].senderType=='Group' and !hasRole('ROLE_GROUP'))") @RequestMapping(value={"/inputs/{type}/{status}/{id}"},method=RequestMethod.GET) @ResponseBody public Object getInput(@PathVariable("type") String type,@PathVariable("status") String status,@PathVariable int id) { ... }