王健-吐血奉献
重要说明,由于本人用的是SpringSecurity3.1版本,3.1版本与3.0版本在配置上发生了一些变化,在配置时,本人都已经全部注明区别和使用方法。
1、SpringSecurity的体系结构
SpringSecurity由一系列的过虑器组成,核心过虑器为为:org.springframework.web.filter.DelegatingFilterProxy,它代理其他所 有的过虑器,此类必须要配置到web.xml中,且名称必须取名是springSecurityFilterChain。与spring的bean配置文件中的
DelegatingFilterProxy将顺序让以下过虑器工作: Security filter chain: [
SecurityContextPersistenceFilter LogoutFilter
UsernamePasswordAuthenticationFilter DefaultLoginPageGeneratingFilter BasicAuthenticationFilter RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor ]
说明:1:以上信息,是通过spring-security-3.1.xsd的命名空间,
2:关于上面过虑器DelegatingFilterProxy的说明请见spring-security3.1.pdf文档第8.2节的具体讲解。 对二上面的过虑都系统都给出来别名,见spring-security-3.1.pdf文档的第20页: 以下截图来即来自于spring-security3.1.pdf
2、准备开发的资源包
本人使用spring3.1和spring security3.1进行示例。
注意里面包含有aopalliance.jar和aspectj两个外部jar包。这也是spring依赖的包。还有就是log4j和logging.jar.
3、以下配置非数据库方式下安全保护
准备好所有的jar文件。创建一个完整的web项目。建议使用utf-8编码。
第一步:在web.xml中添加以下配置
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"> classpath:spring-beans.xml, classpath:spring-security.xml
第二步:完善项目结构
添加完成jar文件,及配置好web.xml文件后的项目结构如下:
第三步:在spring-secrity.xml中配置安全
注意:
上面的配置项目中,安全所使用的命名空间为spring-security-3.1.xsd。它与3.0.xsd有一些区别,由于项目用的是3.1的jar包,所以必须要使用3.1的命名空间。同时,3.0与3.1在配置上存在一些差别,请多加注意。
1、 上面的配置中 以下是来自于spring-security.pdf第12页的说明: 2、access=”hasRole(‘ROLE_ADMIN’)是什么意思? hasRole方法及permitAll()方法都来自于类:org.springframework.security.web.access.expression.WebSecurityExpressionRoot。此类中及其父类中定义了若干的方法,提供验证功能。它的类层次结构为: 在类SecurityExpressionRoot中提供了若干的验证方法如下: 关于更多请查看它的源代码。 3、 此元素提供用户登录认证,及提供角色列表。可以在配置文件中直接配置用户名和密码,也可以配置从数据库加载用户和。这是我们以后动大手术的地方。 此元素定义后,默认的id值为:org.springframework.security.authenticationManager且不建议修改,因为 4、 此元素是专门提供用户名和密码的地方。目前我们并没有给用户名进行加密。以后我们可选的可以给密码MD5加密。这是以后我们要修改的地方。只要提供一个能可以从数据库读取用户名和密码的userDetailService即可以从数据库加载用户。 注意,在上面的配置中,我们让admin用户,拥有了ROLE_ADMIN,ROLE_USER两个角色,所以admin用户可以访问所有用户的资源。而jack不可以访问admin的资源。一旦访问,将转到访问被拒绝页面上去。 5、一些默认的配置 在 6、access-denied-page选项 上面配置了访问被拒绝时的转发页面。 需要说明的是,在ie6浏览器上,错误页面字节数量必须要大于1024bytes才可以被正常转向。 第四步:运行测试 配置完成上面的程序后,就可以运行测试了。 以下是运行的截图: Springsecurity提供的默认登录页面: 1、登录成功以后,如何获取用户信息 在登录成功以后,SpringSecurity会向session中存放一个key值为SPRING_SECURITY_CONTEXT的对象。此对象为SecurityContext类型,实现类为SecurityContextImpl。这里面包含了安全认证及安全验证的所有信息。 且Spring默认为我们实现了一个User类。此类是UserDetails的子类。此类在登录成功后,被封装成User对象放到SecurityContext中,然后将SecurityContext对象以SPRING_SECURITY_CONTEXT为key值放到session中。 以下是User类的部分代码:可见,此类封装了用户名和密码,并没有提供我们最为关心的id。这也是我们以后要重要扩展的对象。 public class User implements UserDetails, CredentialsContainer { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; //~ Instance fields ================================================================================================ private String password; private final String username; private final Set } 所以,我们可以按以下方式获取User类的实例: SecurityContext ctx = (SecurityContext)//从session中获取SPRING_SECURITY_CONTEXT对象并从中获取Principal session().getAttribute(\"SPRING_SECURITY_CONTEXT\"); User user=(User) ctx.getAuthentication().getPrincipal();//注意是如何获取最后用户对象的 按Spring的习惯,总会给我们提供一个工具类,以方便获取SecurityContext对象。(如Spring中的WebApplicationContextUtils). 此处,SpringSecurity所提供的工具类为:SecurityContextHolder 以下是用SecurityContextHolder类获取SecurityContext对象的示例: Object o = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); System.err.println(\"o>:\"+o); User u = (User) o;//强制类型转换成User对象即可 //SecurityContextHolder的好处是在任何地方,你都可以获取用户的信息,因为它是通过静态方法获取SecurityContext对象的。 2、登录成功以后,如何获取用户的信息-2 虽然我们可以通过SecurityContextHolder来获取用户的信息,或是通过session.getAttribute(“SPRING_SECURITY_CONTEXT”),但这样做会让程序员感觉有些蹩脚。直接将User对象放到Session中,这是我们通常的做法。即然,我们已经知道Spring帮助我们登录成功以后,即会向Session中添加key为SPRING_SECURITY_CONTEXT的key对象。则我们可以书写一个HttpSessionAttributeListener的: 示例代码:以下必须要配置到web.xml中: package cn.listener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import org.springframework.security.core.context.SecurityContext; import cn.domain.MyUser; /** * 此类用于监控是否向session中放入了SPRING_Security_Context的key值。 * 此值只有当用户通过spring的security通过安全认证时,由spring的过虑器向session中放数据 * @version 2012-4-29 */ public class MySessionListener implements HttpSessionAttributeListener{ } public void attributeAdded(HttpSessionBindingEvent e) { } /** * 也同时删除 */ public void attributeRemoved(HttpSessionBindingEvent e) { } /** * 记得还要同时替换呀 */ public void attributeReplaced(HttpSessionBindingEvent e) { } if(e.getName().equals(\"SPRING_SECURITY_CONTEXT\")){ } e.getSession().removeAttribute(\"user\"); if(e.getName().equals(\"SPRING_SECURITY_CONTEXT\")){//判断是否放入了SPRING_SECURITY_CONTEXT的key值 } System.err.println(\"认证通过。。。。。\"); SecurityContext ctx = (SecurityContext)//从session中获取SPRING_SECURITY_CONTEXT对象并从中获取Principal e.getSession().getAttribute(\"SPRING_SECURITY_CONTEXT\"); MyUser user=(MyUser) ctx.getAuthentication().getPrincipal(); e.getSession().setAttribute(\"user\",user); 第五步:编写退出请求 Spring Security默认提供的退出请求为:/j_spring_security_logout <%@ page language=\"java\" import=\"java.util.*\" pageEncoding=\"UTF-8\"%> <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%> This is my JSP page. 第六步:配置自己的登录页面,同时配置了退出和cookie信息 修改spring-security.xml配置文件。添加 authentication-failure-url=\"/jsps/login.jsp?error=1\" /> 请注意登录验证不成功返回的页面,只是在后面多了一个参数而已。 第七步:书写登录页面 Spring security的登录请求url为/j_spring_security_check: <%@ page language=\"java\" import=\"java.util.*\" pageEncoding=\"UTF-8\"%> <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%> 这是登录页面 用户名: 记住我: 第八步:小总结 经过上面的配置,我们已经通过硬编码实现了用户的管理,安全路径资源的管理。且认识了以下几个工具类和配置: SecurityContextHolder类。 以上源代码位于:security3项目中。 4、将用户的信息保存到数据库中-暂只使用用户和角色表 本示例,只使用用户和角色表,我将在后面的课程中再讲解如何使用资源表。这一个慢慢学习的过程。 按照我个人的习惯,定义用户-角色-资源三个表。并能过两个中间表建立多对多多的关系,本人采用MySql数据库。 第一步:定义数据结构 E-R图如下: 完整的DDL如下: create database security3 character set UTF8; use security3; /*用户列表*/ create table users( user_id varchar(32) primary key, user_name varchar(50), user_pwd varchar(32), user_status char(1) ); /*角色列表*/ create table roles( role_id varchar(32) primary key, role_name varchar(30), role_desc varchar(50) ); /*用户角色对应表*/ create table roleuser( ru_userid varchar(32), ru_roleid varchar(32), constraint ru_pk primary key(ru_userid,ru_roleid), constraint ru_fk1 foreign key(ru_userid) references users(user_id), constraint ru_fk2 foreign key(ru_roleid) references roles(role_id) ); /*资源列表*/ create table resources( ); /*资源角色对应表*/ create table func( func_roleid varchar(32), func_resid varchar(32), constraint func_pk primary key(func_roleid,func_resid), constraint func_fk1 foreign key(func_roleid) references roles(role_id), constraint func_fk2 foreign key(func_resid) references resources(res_id) ); /*写入一些初始化数据,暂时先不加密*/ insert into users values('U1','admin','1234','1'); insert into users values('U2','guest','1234','1'); /*写入角色信息,默认情况下,角色都以ROLE_开头*/ insert into roles values('R1','ROLE_ADMIN','管理员'); insert into roles values('R2','ROLE_USER','普通用户'); /*用户角色对应*/ insert into roleuser values('U1','R1'); insert into roleuser values('U1','R2'); insert into roleuser values('U2','R2'); res_id varchar(32) primary key, res_name varchar(50), res_url varchar(100) /*暂时先不定义资源信息*/ /*现在开始定义资源信息,并设置资源对应的角色*/ insert into resources values('S1','超级管理','/jsps/secu/secu.jsp'); insert into resources values('S2','普通应用','/jsps/user/user.jsp'); /*定义角色与资源的对应关系*/ insert into func(func_roleid,func_resid) values('R1','S1'); insert into func(func_roleid,func_resid) values('R1','S2'); insert into func(func_roleid,func_resid) values('R2','S2'); /*查询某人拥有某资源*/ SELECT u.user_name,s.res_name,s.res_url FROM users u INNER JOIN roleuser ru ON u.user_id=ru.ru_userid INNER JOIN roles r ON ru.ru_roleid=r.role_id INNER JOIN func f ON r.role_id=f.func_roleid INNER JOIN resources s ON f.func_resid=s.res_id; /*使用distinct关键字*/ SELECT DISTINCT u.user_name,s.res_name,s.res_url FROM users u INNER JOIN roleuser ru ON u.user_id=ru.ru_userid INNER JOIN roles r ON ru.ru_roleid=r.role_id INNER JOIN func f ON r.role_id=f.func_roleid INNER JOIN resources s ON f.func_resid=s.res_id; 第二步:在配置文件中连接数据库 修改spring-beans.xml文件,在这儿连接数据库: 注意,为了区别,我在这儿默认使用bean为默认命名空间,而在spring-security.xml中默认使用spring-security为默认命名空间。 第三步:修改 这一步,最为关键,我们通过查看InMemoryDaoImpl的源代码可知,此类是UserDetailsService的子类。InMemoryDaoImpl负责从配置文件中读取硬编码的用户名和密码。 1、分析spring-security的源代码1 通过查看UserDetailService的继承关系我们也可以知道,它还有一个子类JdbcDaoImpl,它负责从数据库中加载用户的信息。但此类要求使用Spring给我们定义好数据结构。此数据结构并不是很灵活,不能满足我们的要求。 以下是UserDetailsService的继承关系: 虽然JdbcDaoImpl不能满足我们的要求,但我们可以从中学习到Spring是如何加载用户认证的,从而去实现自己的UserDetailsService。以下是JdbcDaoImpl的部分源代码: public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService { //~ Static fields/initializers ===================================================================================== public static final String DEF_USERS_BY_USERNAME_QUERY = \"select username,password,enabled \" + \"from users \" + \"where username = ?\"; //请自己去查看更多源代码。 //其中就是定义了很多查询语句而已 } 关于JdbcDaoImpl使用什么样的表结构,在SpringSecurity的pdf文档中已经给出,请参考spring-security.pdf第126页的建表语句。 2、分析spring-security的源代码2 UserDetailsService接口的源代码如下: public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; } 可见,只要我们实现此类,并提供一个此方法的实现,并返回UserDetails即可。 又通过查看User类的源代码我们知道。它实现了UserDetails接口。并封装了一系列的用户信息: public class User implements UserDetails, CredentialsContainer { private String password; private final String username; private final Set } 通过这些,我们就知道了。自己完全可以继承User类,或是直接实现UserDetails接口。在loadUserByUsername方法中根据用户名查询用户是否存在。 3、User类实现UserDetails接口 实现UserDetails接口以后,有一系列的方法让我们实现,先来看一个添加了所有空实现的实现: package cn.itcast.domain; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class User implements UserDetails { } private static final long serialVersionUID = 1L; public Collection extends GrantedAuthority> getAuthorities() { } public String getPassword() { } public String getUsername() { } //以下方法必须返回真 public boolean isAccountNonExpired() { } public boolean isAccountNonLocked() { } public boolean isCredentialsNonExpired() { } public boolean isEnabled() { } return true; return true; return true; return true; return null; return null; return null; 4、完善自己开发的User类 package cn.itcast.domain; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class User implements UserDetails { private static final long serialVersionUID = 1L; private String id; private String username; private String password; private List * 返回认证的角色列表,但返的一不是我们的Role对象, * 其实它只需要一个名称。所以,我们可以将Role中的name放到GrantedAuthority中返回 * 即可。 */ public Collection extends GrantedAuthority> getAuthorities() { } //定义两个构造方法 public User() {} public User(String id, String username, String password, List this.id = id; this.username = username; this.password = password; this.roles = roles; List return list; //以下是getter/setter略 } 上面用到了Role类,也同时给出Role类的源代码: package cn.itcast.domain; /** * 定义角色类的Bean * @author 王健 * @version 2012-4-29 */ public class Role { private String id; private String name; private String desc; //以下是getter/setter略 } 5、实现UserDetailsService接口 实现UserDetailsService接口的原理就是先根据用户名查询此用户是否存在。如果存在则查询此用户所拥有的所有角色。 强烈建议查询代码应该在DaoJdbc中实现,你懂的! 代码: package cn.itcast.security; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import cn.itcast.domain.Role; import cn.itcast.domain.User; /** * 就实现一个方法即可 * 因为需要操作数据库,所以要注入 datasource * 应该注入一个dao,此处我就略了 * @author 王健 * @version 2012-4-29 */ public class DbUserDetailsService extends JdbcDaoSupport implements UserDetailsService { //本人强烈建议在dao中查询 /** * 根据用户名查询此用户是否存在 */ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = null; //查询此用户 String sql = \"select * from users where user_name=?\"; System.err.println(\"查询语句为:\"+sql+\+username); user = getJdbcTemplate().queryForObject(sql, new RowMapper public User mapRow(ResultSet rs, int row) throws SQLException { } User user = new User(rs.getString(\"user_id\"), rs.getString(\"user_name\"), rs.getString(\"user_pwd\"), null);//先设置角色为null System.err.println(\"有没有:\"+user); return user; },username); System.err.println(\"user对象为\"+user); if(user!=null){//说明此用户存在,则查询此用户所属性于的所有角色,根据id查即可 sql = \"select role_id,role_name,role_desc \" +//只关联角色和中间表即可 \"from roles inner join roleuser on roles.role_id=roleuser.ru_roleid\" + } } } \" where roleuser.ru_userid=?\"; System.err.println(\"角色查询为:\"+sql+\+user.getId()); List public Role mapRow(ResultSet rs, int row) } throws SQLException { Role role = new Role(); role.setId(rs.getString(\"role_id\")); role.setName(rs.getString(\"role_name\")); role.setDesc(rs.getString(\"role_desc\")); return role; },user.getId()); user.setRoles(roles); return user; 6、小小的修改以下配置文件与数据库的角色对应 由于本人的角色表已经设置成了以下信息:,即不是以ROLE_xx开头的名称了,的所以,要做一点小小的修改: 表数据: 修改spring-security.xml如下:注意里面的对应关系,即为admin 第四步:小总结 本部分我们主要学习了以下两个类: UserDetailsService接口,及如何实现它里面的方法。 UserDetails接口,即用户,及如何实现一个自己的User对象。 配置中的access=”hasRole(„ROLE_ADMIN)”与角色表的对应关系。 5、实现资源也从数据库进行验证 目前情况下,实现到上面的代码,就已经非常好了。如果再向下实现,将会是更加细粒度的控制。 继承吧!。。。。 如果前面的配置你都已经看明白了,那我恭喜你。后面的配置将比上面的所有配置复杂的多。 第一步:分析存在的问题 – 在配置文件中资源硬编码问题 第上例中,我们已经实现不同的资源只能某些用户访问如: 上面两个定义了,secu下的资源必须要拥有admin角色才可以访问,suer下的资源,必须要具有user角色才可以访问。而上面的配置全部是是通过硬编码方式实现的。如果也能将资源配置转移到数据库中岂不是更好吗! 第二步:在resources表中保存资源信息-越详细越好 同样使用之前的表结构: /*现在开始定义资源信息,并设置资源对应的角色*/ insert into resources values('S1','超级管理','/jsps/secu/secu.jsp'); insert into resources values('S2','普通应用','/jsps/user/user.jsp'); /*定义角色与资源的对应关系*/ insert into func(func_roleid,func_resid) values('R1','S1'); insert into func(func_roleid,func_resid) values('R1','S2'); insert into func(func_roleid,func_resid) values('R2','S2'); 现在让我们做一个查询,查询出某人拥有某资源: /*查询某人拥有某资源*/ SELECT u.user_name,s.res_name,s.res_url FROM users u INNER JOIN roleuser ru ON u.user_id=ru.ru_userid INNER JOIN roles r ON ru.ru_roleid=r.role_id INNER JOIN func f ON r.role_id=f.func_roleid INNER JOIN resources s ON f.func_resid=s.res_id; 查询的结果: 不难发现,里面有重复的数据。 为此我们使用distinct关键字: /*使用distinct关键字*/ SELECT DISTINCT u.user_name,s.res_name,s.res_url FROM users u INNER JOIN roleuser ru ON u.user_id=ru.ru_userid INNER JOIN roles r ON ru.ru_roleid=r.role_id INNER JOIN func f ON r.role_id=f.func_roleid INNER JOIN resources s ON f.func_resid=s.res_id; 查询的结果: 可见,重复的数据已经没有了。 上例的查询操作,一般用于用户登录时,显示用户的菜单。 第三步:配置自己的核心过虑器-核心工作 前面的配置 Security filter chain: [ SecurityContextPersistenceFilter LogoutFilter UsernamePasswordAuthenticationFilter BasicAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter RememberMeAuthenticationFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor ] 最后一个过虑器FilterSecurityInterceptor则是我们真正核心的安全过虑器。要想真正的使用自己的验证规则,则必须要覆盖此类的实现。此类的源代码及继承关系图如下: 1、三个重要的属性-必不可少 要想重写FilterSecurityInterceptor必须要为其注入三个必不可少的属性,它们是: 1、authenticationManager认证管理器,从内存或是从数据库获取用户的信息。此属性我们已经配置好了: 2、accessDecisionManager访问判断管理器,它使用投票机制决定用户是否具体访问某资源的权限。系统已经帮我们做好了默认的实现。但需要我们给出定义。 3、securityMatadataSourcer所有被保护的资源信息,一般为一个url,也可以是一个类或是一个方法。如 此类需要我们自己的去实现,即读取所有资源并缓存起来。 为了更加清楚安全认证的过程,我们再次分析FilterSecurityInterceptor的源代码。 通过分析源码,我们得到如下信息: 它实现了javax.servlet.Filter接口,是一个标准的Servlet 过滤器从AbstractSecurityInterceptor 继承而来,定义了FilterInvocationSecurityMetadataSource(接口)属性,属性名为securityMetadataSource,并提供了getter/setter方法。而AbstractSecurityInterceptor 中定义了accessDecisionManager属性,并提供getter/setter方法,类型为AccessDecisionManager(接口),还有authenticationManager属性,并提供了getter/setter方法,类型为AuthenticationManager(接口). 过滤器中三个主要属性都找到了。 accessDecisionManager属性:此属性的类型为AccessDecisionManager接口类型,它的子类如下: 可见。它拥有一个抽象子类和三个具体子类,AccessDecisionManager接口的源代码如下: public interface AccessDecisionManager { //决定是否允许访问某个资源,资源可以是一个url,也可以是一个类或是方法。 //如果没有访问权限则会直接抛出一个AccessDeniedException异常 void decide(Authentication authentication, Object object, Collection * @return true if this boolean supports(ConfigAttribute attribute); /* * @return boolean supports(Class> clazz); } 2、三种不同的验证方式-投票 上例中AccessDecisionManager接口的三个实现类分别为三种不同的验证方式: AffrimativeBased类,验证用户是否具体所需要的角色中的一个。如访问/user/user.jsp资源,需要具有ROLE_ADMIN或是ROLE_USER角色,只要用户具有其中一个角色,即可以访问资源。应该是or的关系。也可以理解成,只要有一个人投同意票就算是通过。 ConsensusBased类,当用户拥有的角色大于等于所需要的角色数量/2,即可访问资源。即有一半以上的角色时,即可以访问资源。也可以理解成,当有一半以上的人投同意票时才通过。 UnanimousBased类,这也是最为严格的验证。必须要所有的投票者都投同意票时才通过。而每一个AccessDecisionVoter的子类只能是一个投票者。 都通过AccessDecisionVoter接口的子类来实现投票: RoleVoter类是我们所需要的一个类。 3、启动过程和访问过程分析 4、先完成Resource领域模型 public class Resource { private String id; private String name; private String url; private List } 5、书写securityMetadataSource的实现类继承 package cn.itcast.security; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; 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 cn.itcast.domain.Resource; import cn.itcast.domain.Role; /** * 从数据库中加载需要认证的资源 * 本人强烈建议在Dao中实现从数据库中的查询,但为了让你看明白我还在这儿实现 * @author 王健 * @version 2012-4-30 */ public class DBSecurityMetadataSource extends JdbcDaoSupport implements FilterInvocationSecurityMetadataSource{ private Map public void init(){ String sql = \"select res_id,res_name,res_url from resources\";//查询所有受保护的资源 JdbcTemplate jt = getJdbcTemplate(); List public Resource mapRow(ResultSet rs, int row) } throws SQLException { Resource res = new Resource(); res.setId(rs.getString(\"res_id\")); res.setName(rs.getString(\"res_name\")); res.setUrl(rs.getString(\"res_url\")); return res; Collection }); //查询每一资源所对应的角色 sql = \"select role_id,role_name,role_desc \" + } //将List //即一个资源所对应的角色是什么即:{\"/secu.jsp\":[\"ROLE_ADMIN\ for(Resource rs:ress){ } String key = rs.getUrl();//以受保护的资源为key Set resourcesMap.put(key,config); ConfigAttribute att = new SecurityConfig(r.getName());//直接将角色信息放到config中即可 config.add(att); \"from roles inner join func on role_id=func_roleid\" + \" where func_resid=?\"; List public Role mapRow(ResultSet rs, int arg1) } throws SQLException { Role role = new Role(); role.setId(rs.getString(\"role_id\")); role.setName(rs.getString(\"role_name\")); role.setDesc(rs.getString(\"role_desc\")); return role; for(Resource res:ress){ },res.getId()); res.setRoles(roles); System.err.println(\"启动完成:\"+resourcesMap); } /** * 查询所有的资源 */ public Collection } } Collection //遍历获取所有的资源 for(Map.Entry System.err.println(\"获取所有可用的角色信息:\"+all); return all; /** * 根据给定的资源获取某一个资源的角色信息 */ public Collection public boolean supports(Class> arg0) { } return true; throws IllegalArgumentException { System.err.println(\"所有资源为:\"+resourcesMap); FilterInvocation invo =(FilterInvocation) obj; String url = invo.getRequestUrl(); System.err.println(\"用spring的方式获取url为:\"+url); url = invo.getRequest().getRequestURI();//获取项目名和请求的路径 System.err.println(\"自己的方式获取:\"+url); String contextPath = invo.getRequest().getContextPath(); url = url.replace(contextPath,\"\"); System.err.println(\"自己处理以后:\"+url); if(url.indexOf(\";\")!=-1){//说明里面有jsessionid } Collection System.err.println(\"去掉jsessionid:\"); url = url.substring(0,url.indexOf(\";\")); 6、修改spring-security.xml文件的配置 authentication-failure-url=\"/jsps/login.jsp?error=1\" /> 7、运行测试 大功告成。 第四步:小总结 修改SpringSecurityInterceptor核心过虑器。提供自己的实现。 三种投票方式:一个人同意即放行,一半以上同意才放行,全部同意才放行。 自己实现securityMetadataSource。提供查询语句,并最终将数据封装成Spring security需要的数据类型Map
* 因此我们完全可以拦截此key值,并从session中获取已经放入的SecurityContext对象。
* 并从中获取用户信息
* @author 王健
密码:AccessDecisionManager
can support the passed configuration attribute */true
if the implementation can process the indicated class */
* 以后再做优化
Copyright © 2019- igat.cn 版权所有 赣ICP备2024042791号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务