Spring Security 是 Spring Framework 的一个子项目. 之前也叫做 Acegi Secruty.
Spring Security 能以声明的方式来保护 Web 应用程序的 URL 访问. 只需简单的配置即可实现.
Spring Security 通过一系列 Servlet 过滤器为 Web 应用程序提供了多种安全服务.
Spring Security 使用认证过程过滤器(AuthenticationProcessingFilter) 来处理表单认证,
如果要读源码,从这个类读起。
权限判断流程 :FilterSecurityInterceptor(过滤器安全拦截器), 读源码,从这读起。
配置示例:
web.xml
view plain copy to clipboard print ?
1. <!-- 加入spring应用 -->
2. <listener>
3. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
4. </listener>
5. <context-param>
6. <param-name>contextConfigLocation</param-name>
7. <param-value>/WEB-INF/applicationContext.xml</param-value>
8. </context-param>
9. <!-- 加入spring-security-->
10. <filter>
11. <filter-name>springSecurityFilterChain</filter-name>
12. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
13. </filter>
14. <filter-mapping>
15. <filter-name>springSecurityFilterChain</filter-name>
16. <url-pattern>/*</url-pattern>
17. </filter-mapping>
<!-- 加入spring应用 --><listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value></context-param><!-- 加入spring-security--> <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>
applicationContext.xml
view plain copy to clipboard print ?
1. <!-- 头信息 -->
2. <beans xmlns="http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xmlns:sec="http://www.springframework.org/schema/security"
5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
6. http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.2.xsd">
7. <!-- 配置 http 的安全信息 -->
8. <sec:http auto-config="true">
9. <!-- 1. 需要保护哪些页面, 这些页面哪些角色可以访问 -->
10. <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <!-- 表示不需要任何权限(不需要登陆)即可以访问 -->
11. <sec:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/>
12. <sec:intercept-url pattern="/index.jsp" access="ROLE_USER"/> <!-- 可以逗号分隔多个角色 -->
13. </sec:http>
14. <sec:authentication-provider>
15. <sec:user-service>
16. <!-- 2. 配置用户信息: 用户名, 密码; 以及该用户是什么样的角色 -->
17. <sec:user password="admin" name="admin" authorities="ROLE_ADMIN, ROLE_USER"/>
18. <sec:user password="user" name="user" authorities="ROLE_USER"/>
19. </sec:user-service>
20. </sec:authentication-provider>
<!-- 头信息 --><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.2.xsd"><!-- 配置 http 的安全信息 --><sec:http auto-config="true"> <!-- 1. 需要保护哪些页面, 这些页面哪些角色可以访问 --> <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <!-- 表示不需要任何权限(不需要登陆)即可以访问 --> <sec:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/> <sec:intercept-url pattern="/index.jsp" access="ROLE_USER"/> <!-- 可以逗号分隔多个角色 --></sec:http><sec:authentication-provider> <sec:user-service> <!-- 2. 配置用户信息: 用户名, 密码; 以及该用户是什么样的角色 --> <sec:user password="admin" name="admin" authorities="ROLE_ADMIN, ROLE_USER"/> <sec:user password="user" name="user" authorities="ROLE_USER"/> </sec:user-service></sec:authentication-provider>
以上配置完成后,当访问index.jsp或/admin.jsp会进入到一个登陆页面(spring自动生成的,我们也可以自己指定)
当用user用户登陆后,访问admin.jsp时会提示403,无法访问,没有权限。
使用admin时,可以风雨无阻。
在<sec:http>配置:
<sec:form-login login-page="/login.jsp"/>
表示用logon.jsp页面登陆。默认spring会自己生成一个登陆页面。但是,我们也可以通过这种方式自己指定。
但是 , 页面中元素的命名等需要遵循某些规范。例如form的action,User项对应的文本框必须name为j_username等等。
这些可以通过查看spring生成的登陆页面源码来探知详细。
其它属性:
login-processing-url,例如:
login-processing-url="/loginTo" ---> 表示当访问"/loginTo"这个url模式时,跳转到此登陆页面
default-target-url,例如:
default-target-url="/success.jsp" ---> 表示当登陆成功后,跳转到/success.jsp页面。注意,如果这个页面也是受保护的话,可能导致某些用户登录后提示错误。
默认情况:默认情况,也就是说如果没有配置这个属性的话。有两种情况:
1. 当在未登录的情况下,去访问一个受保护的页面。这个时候,理所当然,他会被强制登录。
而,这个时候,用户如果登录成功,页面将重新回到他要访问的那个页面。
例如,当用户未登录访问admin.jsp ---> 页面跳转到login.jsp ---> 以admin账号登录成功 ---> 页面回到admin.jsp
虽然如此,但用户是否访问页面成功,依然视其权限而定。
2. 当未登录而直接访问登录页面时,登录成功后,会跳转到WEB的根目录,如果目录下游index.jsp、index.html等主页时,将被访问。
always-use-default-target , 例:
always-use-default-target="true"
表示,登录成功后,始终使用“login-processing-url”的页面作为登录成功页面。
<sec:logout logout-url="/logout"/>
表示,当访问“/logout”的时候,登出当前登陆。
默认情况:也就是logout-url缺省时:
默认被映射到 /j_spring_security_logout
其它:
invalidate-session="true" 表示登出时销毁session
logout-success-url="/success.jsp" 表示登出成功显示的页面
**************
JSP页面 根据权限动态显示(登录用户有权限时则显示、否则不显示):
<security:authorize ifAnyGranted="ROLE_DELETE, ROLE_UPDATE">
<th>操作</th>
</security:authorize>
---- 当用户具有 ROLE_DELETE, ROLE_UPDATE 之间任意一个权限,则显示 <th>操作</th>
<security:authorize ifAllGranted="ROLE_UPDATE">
<a href="${cp }/role-update-ui.do?roleid=${role.id}">修改</a>
</security:authorize>
---- 当用户具有ROLE_UPDATE权限时,才显示标签内的内容。(还可以逗号隔开多个权限,此时登录用户必须满足所有权限,才满足显示条件)
***************
web.xml配置
view plain copy to clipboard print ?
- <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>
<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>
***************
程序员可以自定义 UserDetailsService 接口的实现类, 将该类配置到 Spring 的 IOC 容器中:
在 <authentication-provider user-service-ref=“”> 节点的 user-service-ref 属性中引用该 Bean. 即可实现自定义的登录.
例:
=====applicationContext-security.xml ---
<http auto-config="true">
<intercept-url pattern="/index.jsp*" access="ROLE_USER" />
<intercept-url pattern="/admin.jsp*" access="ROLE_ADMIN" />
<!-- 登出 -->
<logout logout-url="/logout"/>
</http>
<authentication-provider user-service-ref="detailsService" />
<b:bean id="detailsService" class="security.service.UserDetailsServiceImpl" />
类:
=====security.service.UserDetailsServiceImpl
view plain copy to clipboard print ?
- public class UserDetailsServiceImpl implements UserDetailsService {
- @Override
- public UserDetails loadUserByUsername(String arg0)
- throws UsernameNotFoundException, DataAccessException {
- GrantedAuthority[] auths = new GrantedAuthority[2];
- auths[0] = new GrantedAuthorityImpl("ROLE_USER");
- auths[1] = new GrantedAuthorityImpl("ROLE_ADMIN");
- User user = new User("wjh", "422899", true, auths );
- return user;
- }
- }
public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String arg0) throws UsernameNotFoundException, DataAccessException { GrantedAuthority[] auths = new GrantedAuthority[2]; auths[0] = new GrantedAuthorityImpl("ROLE_USER"); auths[1] = new GrantedAuthorityImpl("ROLE_ADMIN"); User user = new User("wjh", "422899", true, auths ); return user; } }
以上代码等价于如下配置:
view plain copy to clipboard print ?
- <http auto-config="true">
- <intercept-url pattern="/index.jsp*" access="ROLE_USER" />
- <intercept-url pattern="/admin.jsp*" access="ROLE_ADMIN" />
- <!-- 登出 -->
- <logout logout-url="/logout"/>
- </http>
- <authentication-provider>
- <user-service>
- <user password="admin" name="admin" authorities="ROLE_ADMIN, ROLE_USER" />
- <user password="user" name="user" authorities="ROLE_USER" />
- </user-service>
- </authentication-provider>
<http auto-config="true"> <intercept-url pattern="/index.jsp*" access="ROLE_USER" /> <intercept-url pattern="/admin.jsp*" access="ROLE_ADMIN" /> <!-- 登出 --> <logout logout-url="/logout"/> </http><authentication-provider> <user-service> <user password="admin" name="admin" authorities="ROLE_ADMIN, ROLE_USER" /> <user password="user" name="user" authorities="ROLE_USER" /> </user-service></authentication-provider>
=======它的优势在于
我们可以在自实现的UserDetailsService 类中访问数据库,以此获取权限信息(登录名、密码总算来自数据库了)。
***********************
使用数据库管理用户权限
数据结构:
create table user(
id bigint,
username varchar(50),
password varchar(50),
status integer,
descn varchar(200)
);
create table user_role(
user_id bigint,
role_id bigint
);
create table role(
id bigint,
name varchar(50),
descn varchar(200)
);
类:
=====security.service.UserDetailsServiceImpl
view plain copy to clipboard print ?
- public class UserDetailsServiceImpl implements UserDetailsService {
- //通过依赖注入的方式,获取dbcTemplate
- private SimpleJdbcTemplate jdbcTemplate;
- public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- @SuppressWarnings("deprecation")
- @Override
- public UserDetails loadUserByUsername(String username)
- throws UsernameNotFoundException, DataAccessException {
- String sql = "select username, password, status, name " +
- "from user u " +
- "join user_role ur " +
- "on u.id = ur.user_id " +
- "join role r " +
- "on r.id = ur.role_id where u.username = ?";
- //查询出将要用到的信息
- List<Map<String, Object>> roleList = jdbcTemplate.queryForList(sql, username);
- if(roleList.size() <= 0) {
- throw new UsernameNotFoundException("用户不存在,或密码错误!");
- }
- String password = (String) roleList.get(0).get("password");
- boolean enabled = ((Integer) roleList.get(0).get("status") == 1);
- List<GrantedAuthority> anthoritieList = new ArrayList<GrantedAuthority>();
- for (Map<String, Object> map : roleList) {
- String role = (String)map.get("name");
- anthoritieList.add(new GrantedAuthorityImpl(role));
- }
- return new User(username,password,enabled,anthoritieList.toArray(new GrantedAuthority[0]));
- }
- }
public class UserDetailsServiceImpl implements UserDetailsService { //通过依赖注入的方式,获取dbcTemplate private SimpleJdbcTemplate jdbcTemplate; public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @SuppressWarnings("deprecation") @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { String sql = "select username, password, status, name " + "from user u " + "join user_role ur " + "on u.id = ur.user_id " + "join role r " + "on r.id = ur.role_id where u.username = ?"; //查询出将要用到的信息 List<Map<String, Object>> roleList = jdbcTemplate.queryForList(sql, username); if(roleList.size() <= 0) { throw new UsernameNotFoundException("用户不存在,或密码错误!"); } String password = (String) roleList.get(0).get("password"); boolean enabled = ((Integer) roleList.get(0).get("status") == 1); List<GrantedAuthority> anthoritieList = new ArrayList<GrantedAuthority>(); for (Map<String, Object> map : roleList) { String role = (String)map.get("name"); anthoritieList.add(new GrantedAuthorityImpl(role)); } return new User(username,password,enabled,anthoritieList.toArray(new GrantedAuthority[0])); } }
****************************************
如何动态加入受保护资源信息:
方式:
程序员可以自定义 ObjectDefinitionSource 接口的实现类,
将该类的实例装配给 FilterSecurityInterceptor 的 objectDefinitionSource 的属性.
即可实现自定义的资源获取.
分析过程:
通过读源码:
FilterSecurityInterceptor --> doFilter --> invoke --> super.beforeInvocation(fi) -->|
ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
最后一句代码:this.obtainObjectDefinitionSource().getAttributes(object) 的 obtainObjectDefinitionSource()
这是 FilterSecurityInterceptor 的一个方法,同时此类还有一个 setObjectDefinitionSource 方法, 给了我们用依赖注入的方式改变其值的可能。
方法返回一个ObjectDefinitionSource的实例
通过 Debug 看到,实际上返回的是 DefaultFilterInvocationDefinitionSource 的实例
查看其包含的元素得知 这个类就是用来存放资源信息的。
因此,得出结论,我们只需要定义自己的DefaultFilterInvocationDefinitionSource ,并装配给FilterSecurityInterceptor 便可。
而,通过查看源码得知,原来的DefaultFilterInvocationDefinitionSource 是通过构造器的两个参数来获取到资源信息的:
public DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher, LinkedHashMap requestMap);
其中,requestMap 是 重中之重。
因此,我们需要继承DefaultFilterInvocationDefinitionSource 类,并要安排在某个位置调用它的上述构造器。
只有采取这种方式:
//在继承类的构造器里调用父类构造器。
//而继承规则规定:子类在明确的调用父类的构造器,其参数必须明确——即,值为静态变量值或者为静态方法的返回值。
public ObejctDefinitionSourceImpl(UrlMatcher urlMatcher,
LinkedHashMap requestMap) {
super(urlMatcher, requestMap);
}
因此,我现在采取下面方式来完成功能的实现
(具体实现可以参看spring-security源码----HttpSecurityBeanDefinitionParser类:parseInterceptUrlsForFilterInvocationRequestMap方法):
=*==*==*==*==*==*==*==*==*==*==**==*==*==*==**==*==*==*==**==*==*==*==**==*==*==*==*=
view plain copy to clipboard print ?
- package security.service;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import org.springframework.security.ConfigAttributeEditor;
- import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
- import org.springframework.security.intercept.web.RequestKey;
- import org.springframework.security.util.AntUrlPathMatcher;
- import org.springframework.security.util.UrlMatcher;
- @SuppressWarnings("unchecked")
- public class ObejctDefinitionSourceImpl extends
- DefaultFilterInvocationDefinitionSource {
- private static UrlMatcher urlMatcher;
- private static LinkedHashMap requestMap = new LinkedHashMap();
- //采用静态初始化的方式,来准备父类构造器的参数值
- static {
- urlMatcher = new AntUrlPathMatcher();
- ConfigAttributeEditor editor = new ConfigAttributeEditor();
- editor.setAsText("ROLE_USER");
- Object key = new RequestKey("/index.jsp*", null);
- //editor.getValue() 不能省略为:"ROLE_ADMIN",并需要经过editor这一道中转站
- requestMap.put(key, editor.getValue());
- editor.setAsText("ROLE_ADMIN");
- key = new RequestKey("/admin*.jsp", null);
- requestMap.put(key, editor.getValue());
- }
- public ObejctDefinitionSourceImpl(UrlMatcher urlMatcher,
- LinkedHashMap requestMap) {
- super(urlMatcher, requestMap);
- }
- //采用静态工厂方法来创建本实例————因为这样才能使用带参的构造器来实例化bean。
- public static ObejctDefinitionSourceImpl createObejctDefinitionSource() {
- return new ObejctDefinitionSourceImpl(urlMatcher, requestMap);
- }
- }
package security.service; import java.util.HashMap;import java.util.LinkedHashMap; import org.springframework.security.ConfigAttributeEditor;import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;import org.springframework.security.intercept.web.RequestKey;import org.springframework.security.util.AntUrlPathMatcher;import org.springframework.security.util.UrlMatcher; @SuppressWarnings("unchecked")public class ObejctDefinitionSourceImpl extends DefaultFilterInvocationDefinitionSource { private static UrlMatcher urlMatcher; private static LinkedHashMap requestMap = new LinkedHashMap(); //采用静态初始化的方式,来准备父类构造器的参数值 static { urlMatcher = new AntUrlPathMatcher(); ConfigAttributeEditor editor = new ConfigAttributeEditor(); editor.setAsText("ROLE_USER"); Object key = new RequestKey("/index.jsp*", null); //editor.getValue() 不能省略为:"ROLE_ADMIN",并需要经过editor这一道中转站 requestMap.put(key, editor.getValue()); editor.setAsText("ROLE_ADMIN"); key = new RequestKey("/admin*.jsp", null); requestMap.put(key, editor.getValue()); } public ObejctDefinitionSourceImpl(UrlMatcher urlMatcher, LinkedHashMap requestMap) { super(urlMatcher, requestMap); } //采用静态工厂方法来创建本实例————因为这样才能使用带参的构造器来实例化bean。 public static ObejctDefinitionSourceImpl createObejctDefinitionSource() { return new ObejctDefinitionSourceImpl(urlMatcher, requestMap); } }
=*==*==*==*==*==*==*==*==*==*==**==*==*==*==**==*==*==*==**==*==*==*==**==*==*==*==*=
接下来要解决两个问题:
①. 将ObejctDefinitionSourceImpl 装备给FilterSecurityInterceptor,覆盖其早先的值。
②. 因为要为FilterSecurityInterceptor装配新值,那么势必要重新配置它,相当于是一个新的bean。要如何取代流程中以往的FilterSecurityInterceptor实例,而将现在的新配的这个bean实例插入到spring-security流程中呢?
方案为:
<b:bean id="obejctDefinitionSource" class="security.service.ObejctDefinitionSourceImpl"
factory-method="createObejctDefinitionSource" />
<b:bean class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
autowire="byType">
<!-- 使 NEXT 指向原来那个实例的 Filter,指向现在新定义的 FilterSecurityInterceptor 实例,
而这个实例自然会指向下一个Filter,原先的实例相当于被架空-->
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
<!-- 装配自定义的资源信息容器类 -->
<b:property name="objectDefinitionSource" ref="obejctDefinitionSource" />
</b:bean>
特别注意:配置 FilterSecurityInterceptor 时的 autowire="byType" 不能少。因为它还有其它很多必要属性,索性ByType让它自己装配得了。
=*==*==*==*==*==*==*==*==*==*==*==*==*==*==**==*==*==*==**==*==*==*==**==*==*==*==*=
另外还可以使用BeanFactory
Spring 中有两种类型的 Bean, 一种是普通Bean, 另一种是工厂 Bean, 即 FactoryBean.
工厂 Bean 跟普通Bean不同, 其返回的对象不是指定类的一个实例, 其返回的是该工厂 Bean 的 getObject 方法所返回的对象。
例:
public class CustomerFacroty implements FactoryBean {
// 此方法返回的对象,为bean实例化成功后spring容器最终存放的对象
@Override
public Object getObject() throws Exception {
return new Customer("wjh", 21);
}
// 创建的对象的类型
@Override
public Class getObjectType() {
return Customer.class;
}
// 是否采用单例模式来管理bean
@Override
public boolean isSingleton() {
return false;
}
}
<b:bean id="customerFacroty" class="junit.test.CustomerFacroty" />
这样配置后,spring存放的标识为customerFacroty的bean实际上是 new Customer("wjh", 21) 这个对象, 而不是 CustomerFacroty 。
代码:
=*==*==*==*==*==*==*==*==*==*==*==
view plain copy to clipboard print ?
- package security.service;
- import java.util.LinkedHashMap;
- import org.springframework.beans.factory.FactoryBean;
- import org.springframework.security.ConfigAttributeEditor;
- import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
- import org.springframework.security.intercept.web.RequestKey;
- import org.springframework.security.util.AntUrlPathMatcher;
- import org.springframework.security.util.UrlMatcher;
- @SuppressWarnings("unchecked")
- public class ObejctDefinitionSourceImpl2 implements FactoryBean {
- // 一目了然,之所以采用这种方式,只是为了能调用非静态方法来获取 DefaultFilterInvocationDefinitionSource 的两个参数
- @Override
- public Object getObject() throws Exception {
- UrlMatcher urlMatcher = new AntUrlPathMatcher();
- LinkedHashMap requestMap = getRequestMap();
- return new DefaultFilterInvocationDefinitionSource(urlMatcher, requestMap);
- }
- private LinkedHashMap getRequestMap() {
- LinkedHashMap requestMap = new LinkedHashMap();
- ConfigAttributeEditor editor = new ConfigAttributeEditor();
- editor.setAsText("ROLE_USER");
- Object key = new RequestKey("/index.jsp*", null);
- requestMap.put(key, editor.getValue());
- editor.setAsText("ROLE_ADMIN");
- key = new RequestKey("/admin*.jsp", null);
- requestMap.put(key, editor.getValue());
- return requestMap;
- }
- @Override
- public Class getObjectType() {
- return DefaultFilterInvocationDefinitionSource.class;
- }
- @Override
- public boolean isSingleton() {
- return true;
- }
- }
package security.service; import java.util.LinkedHashMap; import org.springframework.beans.factory.FactoryBean;import org.springframework.security.ConfigAttributeEditor;import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;import org.springframework.security.intercept.web.RequestKey;import org.springframework.security.util.AntUrlPathMatcher;import org.springframework.security.util.UrlMatcher; @SuppressWarnings("unchecked")public class ObejctDefinitionSourceImpl2 implements FactoryBean { // 一目了然,之所以采用这种方式,只是为了能调用非静态方法来获取 DefaultFilterInvocationDefinitionSource 的两个参数 @Override public Object getObject() throws Exception { UrlMatcher urlMatcher = new AntUrlPathMatcher(); LinkedHashMap requestMap = getRequestMap(); return new DefaultFilterInvocationDefinitionSource(urlMatcher, requestMap); } private LinkedHashMap getRequestMap() { LinkedHashMap requestMap = new LinkedHashMap(); ConfigAttributeEditor editor = new ConfigAttributeEditor(); editor.setAsText("ROLE_USER"); Object key = new RequestKey("/index.jsp*", null); requestMap.put(key, editor.getValue()); editor.setAsText("ROLE_ADMIN"); key = new RequestKey("/admin*.jsp", null); requestMap.put(key, editor.getValue()); return requestMap; } @Override public Class getObjectType() { return DefaultFilterInvocationDefinitionSource.class; } @Override public boolean isSingleton() { return true; } }
=*==*==*==*==*==*==*==*==*==*==*==
装配方式相同:
<b:bean id="obejctDefinitionSource2" class="security.service.ObejctDefinitionSourceImpl2" />
<b:bean class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
autowire="byType">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
<b:property name="objectDefinitionSource" ref="obejctDefinitionSource2" />
</b:bean>
=*==*==*==*==*==*==*==*==*==*==*==
个人觉得第一种更好。
第二种没有必要。
如果真的只是为了调用方法来获取构造器参数,可以写成这样子。
前提:继承
public ObejctDefinitionSourceImpl(UrlMatcher urlMatcher,
LinkedHashMap requestMap) {
super(getMatcher(), getRequestMap());
}
public static UrlMatcher getMatcher(){
return null;
}
public static LinkedHashMap getRequestMap() {
return null;
}
********************************
在上例的基础上,从数据库获取资源信息
数据库表结构 :
CREATE TABLE `resource` (
`Id` int(11) ,
`value` varchar(50)
)
CREATE TABLE `resource_role` (
`resource_id` int(11) ,
`role_id` int(11),
)
create table role(
id bigint,
name varchar(50),
descn varchar(200)
);
---------- 与上例结合的数据库表结构
这种情况下,其实上例中的第一种方式不太好使:
因为要使用到数据库连接对象,而一般情况下,是通过注入DAO来完成数据库间接访问的。
本例中通过注入SimpleJdbcTemplate来对数据库进行访问。
而,因为第一种方式需要在初始化类时,便需要调用(最后一种方式,也需要在构造器中调用静态方法)。但,此时没有办法在之前就注入SimpleJdbcTemplate这个bean。
使用<property>方式注入的对象,需要在对象实例化成功之后(构造器调用之后)才开始注入。
构造器方式的注入此例中更不适合,因为,对于此对象,我们已经被限定了一种构造器的使用。。
我晕 , 突然想到可以这样。
在第一种方式的基础至少这样写:
static {
ApplicationContext acx = new ClassPathXmlApplicationContext("applicationContext*.xml");
SimpleJdbcTemplate jdbcTemplate = (SimpleJdbcTemplate) acx.getBean("jdbcTemplate");
}
这样不就可以获取到数据库访问对象了么,我一直纠结于以注入的方式来获取它。却忘了还可以直接getBean。
所以我傻傻的采用了实例化工厂bean方法的方式来完成需求 ,代码:
=*==*==*==*==*==*==*==*==*==*==*==*==*==*==**==*==*==*==**==*==*==*==**==*==*==*==*=
view plain copy to clipboard print ?
- package security.service;
- import java.util.LinkedHashMap;
- import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
- import org.springframework.security.util.UrlMatcher;
- @SuppressWarnings("unchecked")
- public class ObejctDefinitionSourceImpl extends
- DefaultFilterInvocationDefinitionSource {
- public ObejctDefinitionSourceImpl(UrlMatcher urlMatcher,
- LinkedHashMap requestMap) {
- super(urlMatcher, requestMap);
- }
- }
package security.service; import java.util.LinkedHashMap;import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;import org.springframework.security.util.UrlMatcher; @SuppressWarnings("unchecked")public class ObejctDefinitionSourceImpl extends DefaultFilterInvocationDefinitionSource { public ObejctDefinitionSourceImpl(UrlMatcher urlMatcher, LinkedHashMap requestMap) { super(urlMatcher, requestMap); }}
除此之外,为了能成功注入数据库访问对象,我编写了一个FactoryBean :
view plain copy to clipboard print ?
- package security.service;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
- import org.springframework.security.ConfigAttributeEditor;
- import org.springframework.security.intercept.web.RequestKey;
- import org.springframework.security.util.AntUrlPathMatcher;
- import org.springframework.security.util.UrlMatcher;
- @SuppressWarnings("unchecked")
- public class ObjectDefinitionSourceFactory {
- private static SimpleJdbcTemplate jdbcTemplate;
- private static ObejctDefinitionSourceImpl obejctDefinitionSource = null;
- public ObjectDefinitionSourceFactory(SimpleJdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- public ObejctDefinitionSourceImpl createObejctDefinitionSource() {
- return getObejctDefinitionSource();
- }
- public static ObejctDefinitionSourceImpl getObejctDefinitionSource() {
- if(obejctDefinitionSource != null) {
- return obejctDefinitionSource;
- }
- LinkedHashMap requestMap = getRequestMap();
- UrlMatcher urlMatcher = new AntUrlPathMatcher();
- obejctDefinitionSource = new ObejctDefinitionSourceImpl(urlMatcher, requestMap);
- return obejctDefinitionSource;
- }
- private static LinkedHashMap getRequestMap() {
- LinkedHashMap requestMap = new LinkedHashMap();
- Map<String, String> resources = getResourcesForQueryMap();
- ConfigAttributeEditor editor = new ConfigAttributeEditor();
- Set<Map.Entry<String, String>> set = resources.entrySet();
- for (Map.Entry<String, String> resource : set) {
- // 如果权限为空,则使用默认值,使所有人都可以访问。
- String role = "IS_AUTHENTICATED_ANONYMOUSLY";
- if(resource.getValue() != null) {
- role = resource.getValue();
- }
- editor.setAsText(role);
- Object key = new RequestKey(resource.getKey(), null);
- requestMap.put(key, editor.getValue());
- // 如果权限为空,任何人都不能访问
- /*Object key = new RequestKey(resource.getKey(), null);
- String role = resource.getValue();
- if(role == null) {
- requestMap.put(key, ConfigAttributeDefinition.NO_ATTRIBUTES);
- } else {
- editor.setAsText(role);
- requestMap.put(key, editor.getValue());
- }*/
- }
- return requestMap;
- }
- //注:sql一般都要写成左链接(left join),因为,我们不希望如果一个资源没有对应任何权限的话,便抛弃它。
- // 而是希望,如果一个资源部对应任何权限的时候,任何人都可以访问它或者任何人都不能访问它。
- private static Map<String, String> getResourcesForQueryMap() {
- String sql = "select value, name " + "from resource r "
- + "left join resource_role rr " + "on r.id = rr.resource_id "
- + "left join role ro " + "on ro.id = rr.role_id";
- List<Map<String, Object>> resourcesArole = jdbcTemplate.queryForList(sql);
- Map<String, String> resources = new HashMap<String, String>();
- for (Map<String, Object> map : resourcesArole) {
- String path = (String) map.get("value");
- String role = (String) map.get("name");
- //以逗号隔开多个权限对应一个URL资源
- if (resources.containsKey(path)) {
- role = resources.get(path) + "," + role;
- }
- resources.put(path, role);
- }
- return resources;
- }
- }
package security.service; import java.util.HashMap;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.Set; import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;import org.springframework.security.ConfigAttributeEditor;import org.springframework.security.intercept.web.RequestKey;import org.springframework.security.util.AntUrlPathMatcher;import org.springframework.security.util.UrlMatcher; @SuppressWarnings("unchecked")public class ObjectDefinitionSourceFactory { private static SimpleJdbcTemplate jdbcTemplate; private static ObejctDefinitionSourceImpl obejctDefinitionSource = null; public ObjectDefinitionSourceFactory(SimpleJdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public ObejctDefinitionSourceImpl createObejctDefinitionSource() { return getObejctDefinitionSource(); } public static ObejctDefinitionSourceImpl getObejctDefinitionSource() { if(obejctDefinitionSource != null) { return obejctDefinitionSource; } LinkedHashMap requestMap = getRequestMap(); UrlMatcher urlMatcher = new AntUrlPathMatcher(); obejctDefinitionSource = new ObejctDefinitionSourceImpl(urlMatcher, requestMap); return obejctDefinitionSource; } private static LinkedHashMap getRequestMap() { LinkedHashMap requestMap = new LinkedHashMap(); Map<String, String> resources = getResourcesForQueryMap(); ConfigAttributeEditor editor = new ConfigAttributeEditor(); Set<Map.Entry<String, String>> set = resources.entrySet(); for (Map.Entry<String, String> resource : set) { // 如果权限为空,则使用默认值,使所有人都可以访问。 String role = "IS_AUTHENTICATED_ANONYMOUSLY"; if(resource.getValue() != null) { role = resource.getValue(); } editor.setAsText(role); Object key = new RequestKey(resource.getKey(), null); requestMap.put(key, editor.getValue()); // 如果权限为空,任何人都不能访问 /*Object key = new RequestKey(resource.getKey(), null); String role = resource.getValue(); if(role == null) { requestMap.put(key, ConfigAttributeDefinition.NO_ATTRIBUTES); } else { editor.setAsText(role); requestMap.put(key, editor.getValue()); }*/ } return requestMap; } //注:sql一般都要写成左链接(left join),因为,我们不希望如果一个资源没有对应任何权限的话,便抛弃它。 // 而是希望,如果一个资源部对应任何权限的时候,任何人都可以访问它或者任何人都不能访问它。 private static Map<String, String> getResourcesForQueryMap() { String sql = "select value, name " + "from resource r " + "left join resource_role rr " + "on r.id = rr.resource_id " + "left join role ro " + "on ro.id = rr.role_id"; List<Map<String, Object>> resourcesArole = jdbcTemplate.queryForList(sql); Map<String, String> resources = new HashMap<String, String>(); for (Map<String, Object> map : resourcesArole) { String path = (String) map.get("value"); String role = (String) map.get("name"); //以逗号隔开多个权限对应一个URL资源 if (resources.containsKey(path)) { role = resources.get(path) + "," + role; } resources.put(path, role); } return resources; } }
=*==*==*==*==*==*==*==*=
view plain copy to clipboard print ?
- <b:bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
- <b:constructor-arg ref="dataSource" />
- </b:bean>
- <b:bean id="objectDefinitionSourceFactory" class="security.service.ObjectDefinitionSourceFactory">
- <!-- 构造器的方式加入数据库访问对象 -->
- <b:constructor-arg ref="jdbcTemplate" />
- </b:bean>
- <b:bean id="obejctDefinitionSource" class="security.service.ObejctDefinitionSourceImpl"
- factory-method="createObejctDefinitionSource" factory-bean="objectDefinitionSourceFactory"/>
- <b:bean class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
- autowire="byType">
- <custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
- <b:property name="objectDefinitionSource" ref="obejctDefinitionSource" />
- </b:bean>
<b:bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate"> <b:constructor-arg ref="dataSource" /></b:bean> <b:bean id="objectDefinitionSourceFactory" class="security.service.ObjectDefinitionSourceFactory"> <!-- 构造器的方式加入数据库访问对象 --> <b:constructor-arg ref="jdbcTemplate" /></b:bean> <b:bean id="obejctDefinitionSource" class="security.service.ObejctDefinitionSourceImpl" factory-method="createObejctDefinitionSource" factory-bean="objectDefinitionSourceFactory"/> <b:bean class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType"> <custom-filter before="FILTER_SECURITY_INTERCEPTOR" /> <b:property name="objectDefinitionSource" ref="obejctDefinitionSource" /></b:bean>
=*==*==*==*==*==*==*==*=
另外以 FactoryBean 较之上面这种更简单,清晰。不做记录。
********************
MD5加密
<sec:authentication-provider user-service-ref="userDetailsService">
<!-- 启用密码的 md5 加密 -->
<sec:password-encoder hash="md5">
<sec:salt-source user-property="username"/>
</sec:password-encoder>
</sec:authentication-provider>
配置之后,它会将登陆时输入的密码进行MD5加密之后,再进行验证。
*********************
盐值加密,因为如果密码设置得过于简单,虽然进行了md5加密,但还是有可能被反推出原密码。虽然很难~~~
于是想出一个办法,对密码进行盐值。并且盐值也有一定的迷惑性。
盐值的原理非常简单,就是先把密码和盐值指定的内容合并在一起,再使用md5对合并后的内容进行演算" 中的 "先把密码和盐值指定的内容合并在一起" 时什么意思, 及密码和盐值怎样放在一起?
spring-security中使用盐值的方式进行MD5加密的配置方式为:
<sec:password-encoder hash="md5">
<sec:salt-source user-property="username"/> <!-- 表示以用户名做为盐值 -->
</sec:password-encoder>
但有个问题文档中没有予以说明,就是 spring-security 中盐值的方式是采用哪种方式:是加在密码的前边?后边?中间?或者打散了随机加?
通过阅读 org.springframework.security.providers.encoding.BasePasswordEncoder 类的 mergePasswordAndSalt 方法得知 "先把密码和盐值指定的内容合并在一起" 是通过以下代码完成的: return password + "{" + salt.toString() + "}";
因此,在录入新密码的时候,进行md5算法加密的时候,需要以同样的方式进行盐值加密。否则会出现用户输入的密码永远无法匹配上数据库密码的
现象。
********************
页面上访问用户信息。
<security:authentication property="name"></security:authentication>
Authentication接口里面定义的所有getXXX方法的返回值对象及其嵌套值,都可以这样访问到
例:
<security:authentication property="details"></security:authentication>
****************
管理session
同一个账号只能登陆一次
先在web.xml中配置:
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
然后在spring配置文件配置:
<sec:concurrent-session-control/>
默认的是后登陆的将前面的踢出
也可以改变:
<sec:concurrent-session-control exception-if-maximum-exceeded="true"/>
这时候是前面登陆了,后面不允许登上去。