上一篇记录了自定义验证的第一步,替换登陆验证。so easy~
第二步,开始获取资源文件和权限的对应关系,上面一篇忘了说
一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性。
authenticationManager=用户权限获得(就是上一篇讲的)
securityMetadataSource= 资源和权限列表(这篇要讲的)
accessDecisionManager=访问决策器(下篇要讲的)
还有就是我会把具体实现先说明,最后讲怎么去配置,将这几个配合到一起应为配置很简单。
上一篇已经说到登陆。
登陆成功之后,用户就应该开始去访问一些页面,既然security就是做安全,权限的,那当用户访问的某个页面的时候,就应该去进行权限的验证,判
断这个用户是否具有这个权限。right?
security进行权限验证的时候首先交给访问决策器,访问决策器会取出你的请求的url,并且从资源和权限列表中找到这个url所对应的权限。然后决策器
根据你的用户所属角色判断有没有访问这个url的权限。并且做出相应处理。
访问决策器要去运行必须要有资源和权限列表。所以先说怎么取得资源权限列表。
第一步:建一个资源权限列表的类 实现FilterInvocationSecurityMetadataSource接口,如下
package cn.com.xinma.frame.service; import java.util.Collection; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class aaa implements FilterInvocationSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAllConfigAttributes() { // TODO Auto-generated method stub return null; } @Override public Collection<ConfigAttribute> getAttributes(Object arg0) throws IllegalArgumentException { // TODO Auto-generated method stub return null; } @Override public boolean supports(Class<?> arg0) { // TODO Auto-generated method stub return false; } }
getAttributes(Object obj)重写的这个方法的参数obj 就是用户请求的url地址,你们也可以看到他的返回值是一个Collection<ConfigAttribute>,也就是说,这个方法的意思就是根据用户的请求地址,获得这个地址所应该被赋予的权限集合。你也应该在你的数据库中有这样的对应关系,参考一下我的数据库。
sys_resource z 资源表
DROP TABLE IF EXISTS `sys_resource`; CREATE TABLE `sys_resource` ( `resc_rid` varchar(20) NOT NULL COMMENT '资源标识', `applicat_rid` varchar(20) DEFAULT NULL COMMENT '应用系统标识', `resc_prid` varchar(20) DEFAULT NULL COMMENT '资源父标识', `resc_type` varchar(6) DEFAULT NULL COMMENT '资源类型 url method', `resc_name` varchar(60) NOT NULL COMMENT '资源名称', `resc_code` varchar(60) NOT NULL COMMENT '资源代码', `resc_url` varchar(200) NOT NULL COMMENT '资源URL', `resc_description` varchar(200) DEFAULT NULL COMMENT '资源描述', `sort` varchar(6) DEFAULT NULL COMMENT '资源优先级 排序', `resc_is_valid` varchar(6) NOT NULL DEFAULT '1' COMMENT '资源是否有效', PRIMARY KEY (`resc_rid`), KEY `FK_resource_application` (`applicat_rid`), CONSTRAINT `FK_resource_application` FOREIGN KEY (`applicat_rid`) REFERENCES `sys_application` (`applicat_rid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
sys_reso_auth 资源和权限的对应表
DROP TABLE IF EXISTS `sys_reso_auth`; CREATE TABLE `sys_reso_auth` ( `reso_auth_id` varchar(20) NOT NULL COMMENT '角色与资源关系标识', `resc_rid` varchar(20) DEFAULT NULL COMMENT '资源标识', `authorities_id` varchar(20) DEFAULT NULL COMMENT '权限标识', PRIMARY KEY (`reso_auth_id`), KEY `FK_auth_reso` (`resc_rid`), KEY `FK_reso_auth` (`authorities_id`), CONSTRAINT `FK_auth_reso` FOREIGN KEY (`resc_rid`) REFERENCES `sys_resource` (`resc_rid`), CONSTRAINT `FK_reso_auth` FOREIGN KEY (`authorities_id`) REFERENCES `sys_authorities` (`authorities_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
上面讲到getAttributes这个方法会根据你的请求路径去获取这个路径应该是有哪些权限才可以去访问。从哪里去获取呢?当然是从数据库中去取!访问一次就取一次?那不是很麻烦啊!
怎么解决呢?这好办。定义一个静态变量private static Map<String, Collection<ConfigAttribute>> resourceMap = null;(这个变量保存的是url地址和访问他所需要的权限集合)
和一个方法,这个方法就是 从数据库中获得url和访问权限之间的对应 关系 然后保存至resourceMap 。
这个过程每个项目都是不一样的,所以要你自己去实现,我给出我们项目里的实现。
public CustomInvocationSecurityMetadataSourceService() { loadResourceDefine(); } private static Map<String, Collection<ConfigAttribute>> resourceMap = null; private void loadResourceDefine() { String[] locations = { "classpath:applicationContext-dao.xml", "classpath:applicationContext-resources.xml" }; ApplicationContext context = new ClassPathXmlApplicationContext( locations); SessionFactory sessionFactory = (SessionFactory) context .getBean("sessionFactory"); Session session = sessionFactory.openSession(); String username = ""; String sql = ""; // 提取系统中的所有权限。 sql = "select au_name from sys_authorities"; List<String> auNames = session.createSQLQuery(sql).list(); /* * 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。 */ resourceMap = new HashMap<String, Collection<ConfigAttribute>>(); for (String auth : auNames) { ConfigAttribute ca = new SecurityConfig(auth); List<String> query1 = session .createSQLQuery( "select b.resc_url FROM" + " sys_reso_auth a,Sys_Resource b, Sys_authorities c " + "where a.resc_rid = b.resc_rid and a.authorities_id=c.authorities_id " + " and c.au_name ='" + auth + "'").list(); for (String res : query1) { String url = res; /* * 判断资源文件和权限的对应关系,如果已经存在相关的资源url,则要通过该url为key提取出权限集合,将权限增加到权限集合中。 * sparta */ if (resourceMap.containsKey(url)) { Collection<ConfigAttribute> value = resourceMap.get(url); value.add(ca); resourceMap.put(url, value); } else { Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>(); atts.add(ca); resourceMap.put(url, atts); } } JSONObject jsonObject = null; System.out.println("resourse 的权限:"); jsonObject = JSONObject.fromObject(resourceMap); System.out.println(jsonObject); } }
这个是getAttributes方法的实现
// 根据URL,找到相关的权限配置。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // object 是一个URL,被用户请求的url。 String url = ((FilterInvocation) object).getRequestUrl(); int firstQuestionMarkIndex = url.indexOf("?"); if (firstQuestionMarkIndex != -1) { url = url.substring(0, firstQuestionMarkIndex); } Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (urlMatcher.pathMatchesUrl(url, resURL)) { return resourceMap.get(resURL); } } return null; }
基本上资源权限列表部分的code就这样。
另外的几点重要说明:
把权限列表做成static 有什么好处呢?你不用没访问一个url就去查询数据库,尤其是数据库很大的情况下这样效率是很好的。
但是如果管理员修改了一个资源的权限,不做另外的操作只改数据库中的数据时起不到效果的,应为getAttributes这个方法访问的是内存中的map对象,
所以要实现动态授权需要执行 loadResourceDefine();这个方法,重新把数据库中的数据load到内存中给getAttributes访问。
这就是所谓的动态授权。