Spring 3之MVC & Security简单整合开发(三)

来源:互联网 发布:榕基软件股票行情 编辑:程序博客网 时间:2024/06/09 18:20
Security对数据库验证用户有两种方式,上文提到的是它默认支持的数据库表结构,但基本上用于实际是不现实的,因为我们的数据库都有自己的业务逻辑,所以现在来看看怎么在我们自己的数据库上进行Security框架的用户验证整合,这里给出一个比较通用的数据库权限设计结构: 
 
    假设我们的数据表名称为b_user和b_userrole,它们的结构如下: 
Sql代码  收藏代码
  1. CREATE TABLE `b_user` (  
  2.   `ID` int(11) NOT NULL AUTO_INCREMENT,  
  3.   `USERNAME` varchar(20) NOT NULL,  
  4.   `PASSWORDvarchar(32) NOT NULL,  
  5.   PRIMARY KEY (`ID`)  
  6. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8  
  7. CREATE TABLE `b_userrole` (  
  8.   `ID` int(11) NOT NULL,  
  9.   `USERID` int(11) NOT NULL,  
  10.   `ROLE` varchar(15) NOT NULL,  
  11.   PRIMARY KEY (`ID`),  
  12.   KEY `FK_USERID_USERROLE` (`USERID`),  
  13.   CONSTRAINT `FK_USERID_USERROLE` FOREIGN KEY (`USERID`) REFERENCES `b_user` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION  
  14. ) ENGINE=InnoDB DEFAULT CHARSET=utf8  

    那么表名,字段名和结构都和Security框架默认的不匹配,只好通过SQL语句来让Security框架识别了,在配置文件的数据库验证部分,我们可以这么来写: 
Xml代码  收藏代码
  1. <security:authentication-manager>  
  2.     <security:authentication-provider>  
  3.         <security:password-encoder ref="md5Encoder" />  
  4.         <security:jdbc-user-service  
  5.             data-source-ref="dataSource"  
  6.             users-by-username-query="select USERNAME,PASSWORD,'true' as ENABLED from b_user where USERNAME=?"  
  7.             authorities-by-username-query="select u.USERNAME,ur.ROLE as AUTHORITIES from b_user u,b_userrole ur where u.USERNAME=? and u.ID=ur.USERID" />  
  8.     </security:authentication-provider>  
  9. </security:authentication-manager>  

    在jdbc-user-service中,我们启用了两个属性,其中放置的是SQL语句,就是我们自定义的用户验证方式,将我们的数据库设计和Security框架相匹配,这里的角色一定是在拦截url标签中配置过的,否则Security框架不能识别用户身份。 
    启动应用程序,发现这和原来的验证效果是一样的,这就是自定义的数据库验证方式了。也非常简单,就是用SQL语句查询结果匹配Security框架,不过这可能要和自己应用的数据库设计做出调整,尽量做到最小调整。 
    这里补充一点用户验证方式的配置,我们已经使用了在配置文件里配置用户和数据库验证,一种是支持为数不多的用户,一种是支持数据库大量查询的。对于前者,将配置信息写在XML文件中,和Security框架的配置信息粘在一起,不利于维护。其实Security框架也支持属性文件的配置,我们可以这么来写: 
Xml代码  收藏代码
  1. <security:authentication-manager>  
  2.     <security:authentication-provider>  
  3.         <security:user-service properties="/WEB-INF/users.properties"/>  
  4.     </security:authentication-provider>  
  5. </security:authentication-manager>  

    这里把用户信息都写在了users.properties里,我们来看这个属性配置文件: 
Java代码  收藏代码
  1. admin=123,ROLE_ADMIN,ROLE_USER  
  2. user1=123,ROLE_USER  
  3. user2=123,enabled,ROLE_USER  

    这里面名/值对的形式排列的,值的字段比较多,我们来逐个解释。admin/user1是用户名,不用多说,等号后面第一位是密码,这里没有加密。第二位是状态,这是可选的,默认是enabled,第三位以后就是用户所拥有的角色了,这么使用和前面的效果也是相同的。 
    用户验证部分基本就这么多内容,这里没有涉及到LDAP相关部分。下面是访问控制的说明,访问控制是Security框架的另一大特性,可以对其进行自定义的扩展,设计符合我们业务逻辑的控制。这比URL拦截又深入了一步,可以过滤的东西又多了。 
    设计到访问控制,要引入一个概念,谁来决定能否访问,从而进行控制。Security框架中的访问控制管理有三种方案:至少有一个同意访问,全部同意访问,全部弃权或都同意访问(也就是没有拒绝的)。如何同意?投票产生!Security框架一个可配置的元素就出来了,那就是投票器了。和现实的投票一样,分同意,反对和弃权三类。 
    下面我们应用第一类访问控制管理:至少有一个投票器同意访问,在配置文件中这么来设置: 
Xml代码  收藏代码
  1. <bean id="accessDecisionManager"  
  2.     class="org.springframework.security.access.vote.AffirmativeBased">  
  3.     <property name="decisionVoters">  
  4.         <list>  
  5.             <bean class="org.springframework.security.access.vote.RoleVoter" />  
  6.             <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />  
  7.         </list>  
  8.     </property>  
  9. </bean>  

    上面是默认需要的认证投票,下面就是我们定制的内容了,用来满足特定需要。在消息发布应用中,有这样一个需求,在服务器上登录的用户,给可以删除消息的权限,也就是说不用管理员账户登录,也能删除。那么我们就需要对访问进行控制。在服务器本身登录的用户的IP应该是本地地址127.0.0.1,那么只要IP是它的允许删除,我们来自定义一个投票器来进行投票: 
Java代码  收藏代码
  1. package org.ourpioneer.board.security;  
  2. import java.util.Collection;  
  3. import org.springframework.security.access.AccessDecisionVoter;  
  4. import org.springframework.security.access.ConfigAttribute;  
  5. import org.springframework.security.core.Authentication;  
  6. import org.springframework.security.web.authentication.WebAuthenticationDetails;  
  7. public class IPAddressVoter implements AccessDecisionVoter {  
  8.     public static final String IP_PREFIX = "IP_";  
  9.     public static final String IP_LOCAL_HOST = "IP_LOCAL_HOST";  
  10.     public boolean supports(ConfigAttribute attribute) {  
  11.         return attribute.getAttribute() != null  
  12.                 && attribute.getAttribute().startsWith(IP_PREFIX);  
  13.     }  
  14.     public boolean supports(Class<?> clazz) {  
  15.         return true;  
  16.     }  
  17.     public int vote(Authentication authentication, Object object,  
  18.             Collection<ConfigAttribute> attributes) {  
  19.         if (!(authentication.getDetails() instanceof WebAuthenticationDetails)) {  
  20.             return ACCESS_DENIED;  
  21.         }  
  22.         WebAuthenticationDetails details = (WebAuthenticationDetails) authentication  
  23.                 .getDetails();  
  24.         String address = details.getRemoteAddress();  
  25.         int result = ACCESS_ABSTAIN;  
  26.         for (ConfigAttribute config : attributes) {  
  27.             result = ACCESS_DENIED;  
  28.             if (IP_LOCAL_HOST.equals(config.getAttribute())) {  
  29.                 if (address.equals("127.0.0.1")  
  30.                         || address.equals("0:0:0:0:0:0:0:1")) {  
  31.                     return ACCESS_GRANTED;  
  32.                 }  
  33.             }  
  34.         }  
  35.         return result;  
  36.     }  
  37. }  

    IP地址投票器仅处理属性开头是IP的访问,而支持访问的只能是IP_LOCAL_HOST访问属性。如果访问者的IP是127.0.0.1或者0:0:0:0:0:0:0:1的可以访问,其余的拒绝访问。在配置文件的投票器list中再加入这个类: 
Xml代码  收藏代码
  1. <bean class="org.ourpioneer.board.security.IPAddressVoter" />  

    之后还要在URL拦截属性中修改配置IP_LOCAL_HOST属性的访问权限: 
Xml代码  收藏代码
  1. <security:intercept-url pattern="/messageDelete.htm" access="ROLE_ADMIN,IP_LOCAL_HOST" />  

    而且在http中还要配置访问决定管理器,否则是不能识别到IP_LOCAL_HOST的: 
Xml代码  收藏代码
  1. <security:http access-decision-manager-ref="accessDecisionManager">  

    此时,在本地用user1用户登录,也具有了删除权限,可以删除文章了。这就是投票器的简单应用了。下面是方法调用安全,这是非常细粒度的安全控制,可以作用于类的方法,那么也就是说,对一块业务逻辑有权限的用户组,可能允许你能添加而不能删除,他能修改而不能添加和删除,这都是可以实现的,因为这已经细化到了方法之上了,一个类的某一个方法给你授权访问,其余方法就访问不到,细化到一个功能点上的访问,安全性有很大的提升。先看看对控制器方法的安全访问,这个配置相对简单,在配置文件中,把安全配置文件和controller的声明放在一起: 
Xml代码  收藏代码
  1. <context:component-scan base-package="org.ourpioneer.board.web" />  
  2. <security:global-method-security  
  3.         jsr250-annotations="enabled" secured-annotations="enabled" />  

    这样才能对controller的方法进行控制。不过对方法实行安全控制之后,就没有必要对URL进行拦截了,http配置中的url拦截就都可以去掉了,仅留下登录和退出的就可以了: 
Xml代码  收藏代码
  1. <security:http access-decision-manager-ref="accessDecisionManager">  
  2.     <security:form-login login-page="/login.jsp"  
  3.         login-processing-url="/login" default-target-url="/messageList.htm"  
  4.             authentication-failure-url="/login.jsp?error=true" />  
  5.     <security:logout logout-success-url="/login.jsp" />  
  6. </security:http>  

    虽然这里加入了access-decision-manager-ref="accessDecisionManager",但是对方法的安全不是这里做的,所以这样的话使用user1登录就没有对消息的删除权限了,那么怎么能恢复呢?很简单,在global-method-security中加入它就可以了。这就完成了对控制器方法的配置,那么因为前面都是注解实现的,所以在方法上配置注解就行了,前面代码很全,这里给出一个示例: 
Java代码  收藏代码
  1. @RequestMapping(method = RequestMethod.GET)  
  2. @Secured( { "ROLE_ADMIN""IP_LOCAL_HOST" })  
  3. public String messageDelete(  
  4.     @RequestParam(required = true, value = "messageId") Long messageId,  
  5.             Model model) {  
  6.     Message message = messageBoardService.findMessageById(messageId);  
  7.     messageBoardService.deleteMeesage(message);  
  8.     model.addAttribute("messages", messageBoardService.listMessages());  
  9.     return "redirect:messageList.htm";  
  10. }  

    只要在注解方法上表明可访问的权限就能实现拦截了。当然在Service上实现拦截同理可得,只是需要注意一下注解声明的所在配置文件,否则可能无效,这确实有点不爽。方法拦截除了注解,还有嵌入配置方式和切点配置方式,这两种都是常规做法,参考官方文档就可以了。 
    最后一点是V层的拦截,这在前面已经提到了,使用的是Security框架的标签库实现的。可用的标签和属性,直接参考官方文档即可,使用也很方便,这里就不过多说明了。 
0 0
原创粉丝点击