【转】Spring_Security_3应用的11个步骤

1.Spring Security

11个步骤为应用程序添加安全防护

2. 历史与现状
自2003年出现的Spring扩展插件Acegi Security发展而来。
目前最新 版本为3.x,已成为Spring的一部分。
为J2EE企业应用程序提供可靠的安全性服务。

3.Authentication vs. Authorization
区分概念验证与授权
验证
这 个用户是谁?
用户身份可靠吗?
授权
某用户A是否可以访问资源R
某用户A是否可以执行M操作
某用户A是否可以对资 源R执行M操作

4.SS中的验证特点
支持多种验证方式
支持多种加密格式
支持组件的扩展和替换
可以本地化 输出信息

5.SS中的授权特点
支持多种仲裁方式
支持组件的扩展和替换
支持对页面访问、方法访问、对象访问 的授权。

6.SS核心安全实现
Web安全
通过配置Servlet Filter激活SS中的过滤器链
实现 Session一致性验证
实现免登陆验证(Remember-Me验证)
提供一系列标签库进行页面元素的安全控制
方法安全
通 过AOP模式实现安全代理
Web安全与方法安全均可以使用表达式语言定义访问规则

7.配置SS
配置Web.xml,应用安全过滤器
配置Spring,验证与授权部分
在web页面 中获取用户身份
在web页面中应用安全标签库

实现方法级安全

8.配置web.xml
Xml代码 收藏代码

1. <filter>
2. <filter-name>springSecurityFilterChain</filter-name>
3. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
4. </filter>
5. <filter-mapping>
6. <filter-name>springSecurityFilterChain</filter-name>
7. <url-pattern>/*</url-pattern>
8. </filter-mapping>
9. <context-param>
10. <param-name>contextConfigLocation</param-name>
11. <param-value>classpath:spring.xml</param-value>
12. </context-param>
13. <listener>
14. <listener-class>
15. org.springframework.web.context.ContextLoaderListener
16. </listener-class>
17. </listener>

<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>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>


9.Spring配置文件中设置命名空间
Xml代码 收藏代码

1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans:beansxmlnsbeans:beansxmlns="http://www.springframework.org/schema/security"
3. xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://www.springframework.org/schema/beans
5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
6. http://www.springframework.org/schema/security
7. http://www.springframework.org/schema/security/spring-security-3.0.xsd">
8. </beans:beans>

<?xml version="1.0" encoding="UTF-8"?>
<beans:beansxmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
</beans:beans>


10.配置最基本的验证与授权
Xml代码 收藏代码

1. <http auto-config="true">
2. <intercept-url pattern="/**" access="ROLE_USER" />
3. </http>
4. <authentication-manager>
5. <authentication-provider>
6. <user-service>
7. <user name="tom" password="123" authorities="ROLE_USER, ROLE_A" />
8. <user name="jerry" password="123" authorities="ROLE_USER, ROLE_B" />
9. </user-service>
10. </authentication-provider>
11. </authentication-manager>

<http auto-config="true">
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="tom" password="123" authorities="ROLE_USER, ROLE_A" />
<user name="jerry" password="123" authorities="ROLE_USER, ROLE_B" />
</user-service>
</authentication-provider>
</authentication-manager>



11.通过数据库验证用户身份
Xml代码 收藏代码

1. <authentication-manager>
2. <authentication-provider>
3. <password-encoder hash=“md5”/>
4. <jdbc-user-service data-source-ref="dataSource"/>
5. </authentication-provider>
6. </authentication-manager>

<authentication-manager>
<authentication-provider>
<password-encoder hash=“md5”/>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>


数据表结构见SS说明手册附录A

12.完善web页面验证规则
Html代码 收藏代码

1. <http auto-config="true">
2. <intercept-url pattern="/js/**" filters="none"/>
3. <intercept-url pattern="/css/**" filters="none"/>
4. <intercept-url pattern="/images/**" filters="none"/>
5. <intercept-url pattern="/a.jsp" access="ROLE_A" />
6. <intercept-url pattern="/b.jsp" access="ROLE_B" />
7. <intercept-url pattern="/c.jsp" access="ROLE_A, ROLE_B" />
8. <intercept-url pattern="/**" access="ROLE_USER" />
9. </http>

<http auto-config="true">
<intercept-url pattern="/js/**" filters="none"/>
<intercept-url pattern="/css/**" filters="none"/>
<intercept-url pattern="/images/**" filters="none"/>
<intercept-url pattern="/a.jsp" access="ROLE_A" />
<intercept-url pattern="/b.jsp" access="ROLE_B" />
<intercept-url pattern="/c.jsp" access="ROLE_A, ROLE_B" />
<intercept-url pattern="/**" access="ROLE_USER" />
</http>



13.自定义验证配置
Xml代码 收藏代码

1. <!-- 指 定登陆页面、成功页面、失败页面-->
2. <form-login login-page="/login.jsp" default-target-url="/index.jsp" authentication-failure-url="/login.jsp" />
3. <!-- 尝 试访问没有权限的页面时跳转的页面 -->
4. <access-denied-handler error-page="/accessDenied.jsp"/>
5. <!-- 使 用记住用户名、密码功能,指定数据源和加密的key -->
6. <remember-me data-source-ref="dataSource" />
7. <!-- logout 页面,logout后清除session -->
8. <logout invalidate-session="true" logout-success-url="/login.jsp" />
9. <!-- session 失 效后跳转的页面,最大登陆次数 -->
10. <session-management invalid-session-url="/sessionTimeout.htm">
11. <concurrency-control max-sessions="1" expired-url="/sessionTimeout.htm" />
12. </session-management>
13. </http>

<!-- 指 定登陆页面、成功页面、失败页面-->
<form-login login-page="/login.jsp" default-target-url="/index.jsp" authentication-failure-url="/login.jsp" />
<!-- 尝 试访问没有权限的页面时跳转的页面 -->
<access-denied-handler error-page="/accessDenied.jsp"/>
<!-- 使 用记住用户名、密码功能,指定数据源和加密的key -->
<remember-me data-source-ref="dataSource" />
<!-- logout 页面,logout后清除session -->
<logout invalidate-session="true" logout-success-url="/login.jsp" />
<!-- session 失 效后跳转的页面,最大登陆次数 -->
<session-management invalid-session-url="/sessionTimeout.htm">
<concurrency-control max-sessions="1" expired-url="/sessionTimeout.htm" />
</session-management>
</http>



可以使用SS自带的登陆页面作为login.jsp的模板

14.本地化消息输出
拷贝本地化资源文件后,在配置文件中加载该文件:
Xml代码 收藏代码

1. <!-- 加载错误信息资源文件 -->
2. <beans:bean id="messageSource"
3. class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
4. <beans:property name="basename" value="classpath:messages"/>
5. </beans:bean>

<!-- 加载错误信息资源文件 -->
<beans:bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="classpath:messages"/>
</beans:bean>



资 源文件在SS核心包:spring-security-core-3.0.2.RELEASE.jar的orgspringframeworksecurity目录 中

15.在web页面中获取用户信息
方式 一:Java代码
Java代码 收藏代码

1. Authentication auth = SecurityContextHolder.getContext().getAuthentication();
2. Collection<GrantedAuthority> col = auth.getAuthorities();

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Collection<GrantedAuthority> col = auth.getAuthorities();


方 式二:标签库
Html代码 收藏代码

1. <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
2. <sec:authentication property="name“/>
3. <sec:authentication property="authorities“/>

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="name“/>
<sec:authentication property="authorities“/>


16.在web页面进行元素安全控制
方式一
Html代码 收藏代码

1. <sec:authorizeifAnyGrantedsec:authorizeifAnyGranted="ROLE_A">
2. <a href="a.jsp">你可以访问a.jsp</a>
3. </sec:authorize>
4. <sec:authorizeifNotGrantedsec:authorizeifNotGranted="ROLE_A">
5. 你不可以访问a.jsp
6. </sec:authorize>

<sec:authorizeifAnyGranted="ROLE_A">
<a href="a.jsp">你可以访问a.jsp</a>
</sec:authorize>
<sec:authorizeifNotGranted="ROLE_A">
你不可以访问a.jsp
</sec:authorize>


方式二
Html代码 收藏代码

1. <sec:authorizeurlsec:authorizeurl="/a.jsp">
2. <a href="a.jsp">你可以访问a.jsp</a>
3. </sec:authorize>

<sec:authorizeurl="/a.jsp">
<a href="a.jsp">你可以访问a.jsp</a>
</sec:authorize>



17.全局方法安全控制
Xml代码 收藏代码

1. <global-method-security pre-post-annotations="enabled">
2. <protect-pointcut expression="execution(* com.xasxt.*Service.add*(..))" access="ROLE_A"/>
3. <protect-pointcut expression="execution(* com.xasxt.*Service.delete*(..))" access="ROLE_B"/>
4. </global-method-security>

<global-method-security pre-post-annotations="enabled">
<protect-pointcut expression="execution(* com.xasxt.*Service.add*(..))" access="ROLE_A"/>
<protect-pointcut expression="execution(* com.xasxt.*Service.delete*(..))" access="ROLE_B"/>
</global-method-security>


此 处使用了AspectJ中常用的切入点表达式(百度:AspectJ execution)

18.使用注解进行方法安全控制
Java代码 收藏代码

1. public class DemoService {
2. @PreAuthorize("hasRole(&apos;ROLE_A&apos;)")
3. public void methodA() {
4. }
5. @PreAuthorize("hasAnyRole(&apos;ROLE_A, ROLE_B&apos;)")
6. public void methodB() {
7. }
8. }

public class DemoService {
@PreAuthorize("hasRole(&apos;ROLE_A&apos;)")
public void methodA() {
}
@PreAuthorize("hasAnyRole(&apos;ROLE_A, ROLE_B&apos;)")
public void methodB() {
}
}


hasRole 与hasAnyRole为SS通用内置表达式(google : spring security Common Built- In Expressions)

下一步做什么???
采用更安全的验证方式
采用安全的数据传输方式
实现动态授权
自 定义验证与授权部件
实现数据级安全

本Blog所有内容不得随意转载,版权属于作者所有。如需转载请与作者联系( fastzch@163.com QQ:9184314)。
未经许可的转载,本人保留一切法律权益。
一直以来,发现有某些人完全不尊重我的劳动成果,随意转载,提醒一下那些人小心哪天惹上官司。


Spring Security 3.x 出来一段时间了,跟Acegi是大不同了,与2.x的版本也有一些小小的区别,网上有一些文档,也有人翻译Spring Security 3.x的guide,但通过阅读guide,无法马上就能很容易的实现一个完整的实例。

我花了点儿时间,根据以前的实战经验,整理了一份完整的入门教程,供需要的朋友们参考。

1,建一个web project,并导入所有需要的lib,这步就不多讲了。

2,配置web.xml,使用Spring的机制装载:
Xml代码 收藏代码

1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
5. http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
6. <context-param>
7. <param-name>contextConfigLocation</param-name>
8. <param-value>classpath:applicationContext*.xml</param-value>
9. </context-param>
10.
11. <listener>
12. <listener-class>
13. org.springframework.web.context.ContextLoaderListener
14. </listener-class>
15. </listener>
16.
17. <filter>
18. <filter-name>springSecurityFilterChain</filter-name>
19. <filter-class>
20. org.springframework.web.filter.DelegatingFilterProxy
21. </filter-class>
22. </filter>
23. <filter-mapping>
24. <filter-name>springSecurityFilterChain</filter-name>
25. <url-pattern>/*</url-pattern>
26. </filter-mapping>
27.
28.
29. <welcome-file-list>
30. <welcome-file>login.jsp</welcome-file>
31. </welcome-file-list>
32. </web-app>

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<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>


<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>


这个文件中的内容我相信大家都很熟悉了,不再多说了。

来看看applicationContext-security.xml这个配置文件,关于Spring Security的配置均在其中:

Xml代码 收藏代码

1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans:beans xmlns="http://www.springframework.org/schema/security"
3. xmlns:beans="http://www.springframework.org/schema/beans"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xsi:schemaLocation="http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
7. http://www.springframework.org/schema/security
8. http://www.springframework.org/schema/security/spring-security-3.0.xsd">
9.
10. <http access-denied-page="/403.jsp"><!-- 当访问被拒绝时,会转到403.jsp -->
11. <intercept-url pattern="/login.jsp" filters="none" />
12. <form-login login-page="/login.jsp"
13. authentication-failure-url="/login.jsp?error=true"
14. default-target-url="/index.jsp" />
15. <logout logout-success-url="/login.jsp" />
16. <http-basic />
17. <!-- 增加一个filter,这点与Acegi是不一样的,不能修改默认的filter了,这个filter位于FILTER_SECURITY_INTERCEPTOR之前 -->
18. <custom-filter before="FILTER_SECURITY_INTERCEPTOR"
19. ref="myFilter" />
20. </http>
21.
22. <!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,
23. 我们的所有控制将在这三个类中实现,解释详见具体配置 -->
24. <beans:bean id="myFilter" class="com.robin.erp.fwk.security.MyFilterSecurityInterceptor">
25. <beans:property name="authenticationManager"
26. ref="authenticationManager" />
27. <beans:property name="accessDecisionManager"
28. ref="myAccessDecisionManagerBean" />
29. <beans:property name="securityMetadataSource"
30. ref="securityMetadataSource" />
31. </beans:bean>
32.
33. <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
34. <authentication-manager alias="authenticationManager">
35. <authentication-provider
36. user-service-ref="myUserDetailService">
37. <!-- 如果用户的密码采用加密的话,可以加点“盐”
38. <password-encoder hash="md5" />
39. -->
40. </authentication-provider>
41. </authentication-manager>
42. <beans:bean id="myUserDetailService"
43. class="com.robin.erp.fwk.security.MyUserDetailService" />
44.
45. <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
46. <beans:bean id="myAccessDecisionManagerBean"
47. class="com.robin.erp.fwk.security.MyAccessDecisionManager">
48. </beans:bean>
49.
50. <!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 -->
51. <beans:bean id="securityMetadataSource"
52. class="com.robin.erp.fwk.security.MyInvocationSecurityMetadataSource" />
53.
54. </beans:beans>

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">

<http access-denied-page="/403.jsp"><!-- 当访问被拒绝时,会转到403.jsp -->
<intercept-url pattern="/login.jsp" filters="none" />
<form-login login-page="/login.jsp"
authentication-failure-url="/login.jsp?error=true"
default-target-url="/index.jsp" />
<logout logout-success-url="/login.jsp" />
<http-basic />
<!-- 增加一个filter,这点与Acegi是不一样的,不能修改默认的filter了,这个filter位于FILTER_SECURITY_INTERCEPTOR之前 -->
<custom-filter before="FILTER_SECURITY_INTERCEPTOR"
ref="myFilter" />
</http>

<!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,
我们的所有控制将在这三个类中实现,解释详见具体配置 -->
<beans:bean id="myFilter" class="com.robin.erp.fwk.security.MyFilterSecurityInterceptor">
<beans:property name="authenticationManager"
ref="authenticationManager" />
<beans:property name="accessDecisionManager"
ref="myAccessDecisionManagerBean" />
<beans:property name="securityMetadataSource"
ref="securityMetadataSource" />
</beans:bean>

<!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
<authentication-manager alias="authenticationManager">
<authentication-provider
user-service-ref="myUserDetailService">
<!-- 如果用户的密码采用加密的话,可以加点“盐”
<password-encoder hash="md5" />
-->
</authentication-provider>
</authentication-manager>
<beans:bean id="myUserDetailService"
class="com.robin.erp.fwk.security.MyUserDetailService" />

<!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<beans:bean id="myAccessDecisionManagerBean"
class="com.robin.erp.fwk.security.MyAccessDecisionManager">
</beans:bean>

<!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 -->
<beans:bean id="securityMetadataSource"
class="com.robin.erp.fwk.security.MyInvocationSecurityMetadataSource" />

</beans:beans>


来看看自定义filter的实现:

Java代码 收藏代码

1. package com.robin.erp.fwk.security;
2. import java.io.IOException;
3.
4. import javax.servlet.Filter;
5. import javax.servlet.FilterChain;
6. import javax.servlet.FilterConfig;
7. import javax.servlet.ServletException;
8. import javax.servlet.ServletRequest;
9. import javax.servlet.ServletResponse;
10.
11. import org.springframework.security.access.SecurityMetadataSource;
12. import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
13. import org.springframework.security.access.intercept.InterceptorStatusToken;
14. import org.springframework.security.web.FilterInvocation;
15. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
16.
17. public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
18. implements Filter {
19.
20. private FilterInvocationSecurityMetadataSource securityMetadataSource;
21.
22. // ~ Methods
23. // ========================================================================================================
24.
25. /**
26. * Method that is actually called by the filter chain. Simply delegates to
27. * the {@link #invoke(FilterInvocation)} method.
28. *
29. * @param request
30. * the servlet request
31. * @param response
32. * the servlet response
33. * @param chain
34. * the filter chain
35. *
36. * @throws IOException
37. * if the filter chain fails
38. * @throws ServletException
39. * if the filter chain fails
40. */
41. public void doFilter(ServletRequest request, ServletResponse response,
42. FilterChain chain) throws IOException, ServletException {
43. FilterInvocation fi = new FilterInvocation(request, response, chain);
44. invoke(fi);
45. }
46.
47. public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
48. return this.securityMetadataSource;
49. }
50.
51. public Class<? extends Object> getSecureObjectClass() {
52. return FilterInvocation.class;
53. }
54.
55. public void invoke(FilterInvocation fi) throws IOException,
56. ServletException {
57. InterceptorStatusToken token = super.beforeInvocation(fi);
58. try {
59. fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
60. } finally {
61. super.afterInvocation(token, null);
62. }
63. }
64.
65. public SecurityMetadataSource obtainSecurityMetadataSource() {
66. return this.securityMetadataSource;
67. }
68.
69. public void setSecurityMetadataSource(
70. FilterInvocationSecurityMetadataSource newSource) {
71. this.securityMetadataSource = newSource;
72. }
73.
74. @Override
75. public void destroy() {
76. }
77.
78. @Override
79. public void init(FilterConfig arg0) throws ServletException {
80. }

package com.robin.erp.fwk.security;
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
implements Filter {

private FilterInvocationSecurityMetadataSource securityMetadataSource;

// ~ Methods
// ========================================================================================================

/**
* Method that is actually called by the filter chain. Simply delegates to
* the {@link #invoke(FilterInvocation)} method.
*
* @param request
* the servlet request
* @param response
* the servlet response
* @param chain
* the filter chain
*
* @throws IOException
* if the filter chain fails
* @throws ServletException
* if the filter chain fails
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}

public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}

public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
}

public void invoke(FilterInvocation fi) throws IOException,
ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}

public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}

public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}

@Override
public void destroy() {
}

@Override
public void init(FilterConfig arg0) throws ServletException {
}


最核心的代码就是invoke方法中的InterceptorStatusToken token = super.beforeInvocation(fi);这一句,即在执行doFilter之前,进行权限的检查,而具体的实现已经交给 accessDecisionManager了,下文中会讲述。

来看看authentication-provider的实现:

Java代码 收藏代码

1. package com.robin.erp.fwk.security;
2. import java.util.ArrayList;
3. import java.util.Collection;
4.
5. import org.springframework.dao.DataAccessException;
6. import org.springframework.security.core.GrantedAuthority;
7. import org.springframework.security.core.authority.GrantedAuthorityImpl;
8. import org.springframework.security.core.userdetails.User;
9. import org.springframework.security.core.userdetails.UserDetails;
10. import org.springframework.security.core.userdetails.UserDetailsService;
11. import org.springframework.security.core.userdetails.UsernameNotFoundException;
12.
13. public class MyUserDetailService implements UserDetailsService {
14.
15. @Override
16. public UserDetails loadUserByUsername(String username)
17. throws UsernameNotFoundException, DataAccessException {
18. Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();
19. GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");
20. auths.add(auth2);
21. if(username.equals("robin1")){
22. auths=new ArrayList<GrantedAuthority>();
23. GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_ROBIN");
24. auths.add(auth1);
25. }
26.
27. // User(String username, String password, boolean enabled, boolean accountNonExpired,
28. // boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities) {
29. User user = new User(username,
30. "robin", true, true, true, true, auths);
31. return user;
32. }
33.
34. }

package com.robin.erp.fwk.security;
import java.util.ArrayList;
import java.util.Collection;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class MyUserDetailService implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();
GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");
auths.add(auth2);
if(username.equals("robin1")){
auths=new ArrayList<GrantedAuthority>();
GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_ROBIN");
auths.add(auth1);
}

// User(String username, String password, boolean enabled, boolean accountNonExpired,
// boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities) {
User user = new User(username,
"robin", true, true, true, true, auths);
return user;
}

}


在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等,我想这么简单的代码就不再多解释了。

对于资源的访问权限的定义,我们通过实现FilterInvocationSecurityMetadataSource这个接口来初始化数据。

Java代码 收藏代码

1. package com.robin.erp.fwk.security;
2. import java.util.ArrayList;
3. import java.util.Collection;
4. import java.util.HashMap;
5. import java.util.Iterator;
6. import java.util.Map;
7.
8. import org.springframework.security.access.ConfigAttribute;
9. import org.springframework.security.access.SecurityConfig;
10. import org.springframework.security.web.FilterInvocation;
11. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
12. import org.springframework.security.web.util.AntUrlPathMatcher;
13. import org.springframework.security.web.util.UrlMatcher;
14.
15. /**
16. *
17. * 此类在初始化时,应该取到所有资源及其对应角色的定义
18. *
19. * @author Robin
20. *
21. */
22. public class MyInvocationSecurityMetadataSource
23. implements FilterInvocationSecurityMetadataSource {
24. private UrlMatcher urlMatcher = new AntUrlPathMatcher();;
25. private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
26.
27. public MyInvocationSecurityMetadataSource() {
28. loadResourceDefine();
29. }
30.
31. private void loadResourceDefine() {
32. resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
33. Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
34. ConfigAttribute ca = new SecurityConfig("ROLE_ADMIN");
35. atts.add(ca);
36. resourceMap.put("/index.jsp", atts);
37. resourceMap.put("/i.jsp", atts);
38. }
39.
40. // According to a URL, Find out permission configuration of this URL.
41. public Collection<ConfigAttribute> getAttributes(Object object)
42. throws IllegalArgumentException {
43. // guess object is a URL.
44. String url = ((FilterInvocation)object).getRequestUrl();
45. Iterator<String> ite = resourceMap.keySet().iterator();
46. while (ite.hasNext()) {
47. String resURL = ite.next();
48. if (urlMatcher.pathMatchesUrl(url, resURL)) {
49. return resourceMap.get(resURL);
50. }
51. }
52. return null;
53. }
54.
55. public boolean supports(Class<?> clazz) {
56. return true;
57. }
58.
59. public Collection<ConfigAttribute> getAllConfigAttributes() {
60. return null;
61. }
62.
63. }

package com.robin.erp.fwk.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher;

/**
*
* 此类在初始化时,应该取到所有资源及其对应角色的定义
*
* @author Robin
*
*/
public class MyInvocationSecurityMetadataSource
implements FilterInvocationSecurityMetadataSource {
private UrlMatcher urlMatcher = new AntUrlPathMatcher();;
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

public MyInvocationSecurityMetadataSource() {
loadResourceDefine();
}

private void loadResourceDefine() {
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
ConfigAttribute ca = new SecurityConfig("ROLE_ADMIN");
atts.add(ca);
resourceMap.put("/index.jsp", atts);
resourceMap.put("/i.jsp", atts);
}

// According to a URL, Find out permission configuration of this URL.
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
// guess object is a URL.
String url = ((FilterInvocation)object).getRequestUrl();
Iterator<String> ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(url, resURL)) {
return resourceMap.get(resURL);
}
}
return null;
}

public boolean supports(Class<?> clazz) {
return true;
}

public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

}


看看loadResourceDefine方法,我在这里,假定index.jsp和i.jsp这两个资源,需要ROLE_ADMIN角色的用户才能访问。
这个类中,还有一个最核心的地方,就是提供某个资源对应的权限定义,即getAttributes方法返回的结果。注意,我例子中使用的是 AntUrlPathMatcher这个path matcher来检查URL是否与资源定义匹配,事实上你还要用正则的方式来匹配,或者自己实现一个matcher。

剩下的就是最终的决策了,make a decision,其实也很容易,呵呵。

Java代码 收藏代码

1. package com.robin.erp.fwk.security;
2. import java.util.Collection;
3. import java.util.Iterator;
4.
5. import org.springframework.security.access.AccessDecisionManager;
6. import org.springframework.security.access.AccessDeniedException;
7. import org.springframework.security.access.ConfigAttribute;
8. import org.springframework.security.access.SecurityConfig;
9. import org.springframework.security.authentication.InsufficientAuthenticationException;
10. import org.springframework.security.core.Authentication;
11. import org.springframework.security.core.GrantedAuthority;
12.
13.
14. public class MyAccessDecisionManager implements AccessDecisionManager {
15.
16. //In this method, need to compare authentication with configAttributes.
17. // 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here.
18. // 2, Check authentication has attribute in permission configuration (configAttributes)
19. // 3, If not match corresponding authentication, throw a AccessDeniedException.
20. public void decide(Authentication authentication, Object object,
21. Collection<ConfigAttribute> configAttributes)
22. throws AccessDeniedException, InsufficientAuthenticationException {
23. if(configAttributes == null){
24. return ;
25. }
26. System.out.println(object.toString()); //object is a URL.
27. Iterator<ConfigAttribute> ite=configAttributes.iterator();
28. while(ite.hasNext()){
29. ConfigAttribute ca=ite.next();
30. String needRole=((SecurityConfig)ca).getAttribute();
31. for(GrantedAuthority ga:authentication.getAuthorities()){
32. if(needRole.equals(ga.getAuthority())){ //ga is user's role.
33. return;
34. }
35. }
36. }
37. throw new AccessDeniedException("no right");
38. }
39.
40. @Override
41. public boolean supports(ConfigAttribute attribute) {
42. // TODO Auto-generated method stub
43. return true;
44. }
45.
46. @Override
47. public boolean supports(Class<?> clazz) {
48. return true;
49. }
50. }

package com.robin.erp.fwk.security;
import java.util.Collection;
import java.util.Iterator;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;


public class MyAccessDecisionManager implements AccessDecisionManager {

//In this method, need to compare authentication with configAttributes.
// 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here.
// 2, Check authentication has attribute in permission configuration (configAttributes)
// 3, If not match corresponding authentication, throw a AccessDeniedException.
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(configAttributes == null){
return ;
}
System.out.println(object.toString()); //object is a URL.
Iterator<ConfigAttribute> ite=configAttributes.iterator();
while(ite.hasNext()){
ConfigAttribute ca=ite.next();
String needRole=((SecurityConfig)ca).getAttribute();
for(GrantedAuthority ga:authentication.getAuthorities()){
if(needRole.equals(ga.getAuthority())){ //ga is user's role.
return;
}
}
}
throw new AccessDeniedException("no right");
}

@Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean supports(Class<?> clazz) {
return true;
}
}


在这个类中,最重要的是decide方法,如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则 throw new AccessDeniedException("no right");这样,就会进入上面提到的403.jsp页面。

参考资料:
1,Spring官方网站:http://www.springframework.org
2,文章所用的代码,MyEclipse工程,去掉了lib,请自行下载Spring Security 3.x的包,并copy至对应目录。工程源代码
3,根据网络上的资料,制作的CHM版的 Spring Security 3.x 参考手册中文版
4,2009年3月,我在“IBM WebSphere技术专家沙龙(华南区广州站)”演讲时的PPT:《Spring Security--Protect your web application》,当时是Spring Security 2.x,很多原理是一样,可作参考。

教程中为了尽可能不跟其它框架关联上,所以去掉了访问数据库的部分,比如用户信息和资源配置信息的读取,直接写死在代码中了,大家可以根据自己的实际情况补充完整。

如有任何疑问,欢迎大家以评论的方式提问,也欢迎大家讨论!

------------------------------------

学习spring security 3一篇很好的文章,忍不住分享之。网上好多转载,已经分不清原作者的文章了,若有作者有反感转载请告之,立马处理掉。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值