zoukankan      html  css  js  c++  java
  • Spring 3之MVC & Security简单整合开发(二)

    引用:http://sarin.iteye.com/blog/830831

    本文接上一篇继续研究Spring MVC和Security的简单整合开发。但文本会略详细介绍Security的基本用法。 
        现在来说Security部分。Spring Security框架是Acegi Security的升级,这个框架就是利用了多重过滤的机制对请求进行处理,将符合要求的请求放行,不符合要求的请求阻止下来,这是最大的原理。下面先来看看简单的url过滤吧。 
        先写一个用于验证身份的登录页面: 

    Html代码  收藏代码
    1. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>  
    2. <html>  
    3. <head>  
    4. <title>Login</title>  
    5. </head>  
    6. <body>  
    7. <c:if test="${not empty param.error}">  
    8.     <font color="red">Login error.<br />  
    9.     </font>  
    10.     Reason:${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}  
    11. </c:if>  
    12. <form method="POST" action="<c:url value="/login"/>">  
    13. <table>  
    14.     <tr>  
    15.         <td align="right">Username</td>  
    16.         <td><input type="text" name="j_username" /></td>  
    17.     </tr>  
    18.     <tr>  
    19.         <td align="right">Password</td>  
    20.         <td><input type="password" name="j_password" /></td>  
    21.     </tr>  
    22.     <tr>  
    23.         <td colspan="2" align="right"><input type="submit" value="Login" />  
    24.         <input type="reset" value="Reset" /></td>  
    25.     </tr>  
    26. </table>  
    27. </form>  
    28. </body>  
    29. </html>  


        做一些说明,使用Spring Security时,默认的登录验证地址是j_spring_security_check,验证的用户名是j_username,密码是j_password,对于用户名和密码我们不需要修改,使用其默认值即可,而验证路径通常我们想使用自定义地址,这就需要在security中进行配置,后面会看到,这里还会看到如果验证失败,会把失败信息打印出来,就是JSTL的c:if段的作用。下面来看最基本的Security框架作用,拦截URL请求。在board-security.xml配置如下: 

    Xml代码  收藏代码
    1. <?xml version="1.0" encoding="UTF-8"?>  
    2. <beans xmlns="http://www.springframework.org/schema/beans"  
    3.     xmlns:security="http://www.springframework.org/schema/security"  
    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.     <security:http auto-config="true">  
    10. <security:intercept-url pattern="/messageList.htm"  
    11.                     access="ROLE_ADMIN,ROLE_USER,ROLE_GUEST" />  
    12.         <security:intercept-url pattern="/messagePost.htm"  
    13.                     access="ROLE_ADMIN,ROLE_USER" />  
    14.         <security:intercept-url pattern="/messageDelete.htm"  
    15.                     access="ROLE_ADMIN" />  
    16.         <security:anonymous username="guest"  
    17.             granted-authority="ROLE_GUEST" />  
    18.     </security:http>  
    19.   
    20.     <security:authentication-manager>  
    21.         <security:authentication-provider>  
    22.             <security:user-service>  
    23.                 <security:user name="admin" authorities="ROLE_ADMIN,ROLE_USER" password="secret" />  
    24.                 <security:user name="user1" authorities="ROLE_USER"  
    25.                                 password="1111" />  
    26. </security:user-service>  
    27.         </security:authentication-provider>  
    28.     </security:authentication-manager>  
    29. </beans>  


        配置文件中首先是对http请求的拦截,这里使用了自动配置auto-config,那么当请求到达时,Security框架会让我们进行身份验证,我们拦截的url模式已经在其中配置出来,三个请求分别对应不同的权限,而且messageList.htm还开放了匿名访问功能,要提供匿名访问,就要配置<anonymous>,这里我们配置匿名用户名为guest,角色是ROLE_GUEST,这里的角色都是ROLE_开头,是Spring Security框架默认使用的,我们不用去更改,这也很清楚。首先我们启动应用,来访问唯一的匿名功能,之后我们看到如下效果: 
     
        可以看到,现在的角色是ROLE_GUEST,那么就直接看到,没有验证身份,若我们要发布消息呢,点击Post链接,看看效果: 
     
    要求身份验证了,这就说明对url的拦截起作用了,想发布消息,权限不够了,要验证身份了,注意这里这个页面并不是我们前面写的那个页面,而是Security框架的默认验证页面,为什么没有使用我们所写的页面呢?因为我们还没有配置它啊,当然不会被识别到了。我们来看看默认的页面源码是什么样子的: 

    Html代码  收藏代码
    1. <html><head><title>Login Page</title></head><body onload='document.f.j_username.focus();'>   
    2. <h3>Login with Username and Password</h3><form name='f' action='/j_spring_security_check' method='POST'>   
    3.  <table>   
    4.     <tr><td>User:</td><td><input type='text' name='j_username' value=''></td></tr>   
    5.     <tr><td>Password:</td><td><input type='password' name='j_password'/></td></tr>   
    6.     <tr><td colspan='2'><input name="submit" type="submit"/></td></tr>   
    7.     <tr><td colspan='2'><input name="reset" type="reset"/></td></tr>   
    8.   </table>   
    9. </form></body></html>  


        可以看到这里的默认请求路径就是/j_spring_security_check了,不过这里我们已经可以使用我们配置的用户来登录了,之前在配置文件中的admin和user1,它们拥有的权限不同,那么我们使用user1登录,来发布消息。验证通过,出现消息输入页面: 
     
        下面发布消息,之后能看到消息的列表了,这对ROLE_USER的角色都是可以查看的。 
     
        没有把Author的信息打印出来,为什么?我们在这里对这个自动进行了限制,来看一下页面是怎么写的: 

    Xml代码  收藏代码
    1. <security:authorize ifAllGranted="ROLE_ADMIN,ROLE_USER">  
    2.     <tr>  
    3.         <td>Author</td>  
    4.         <td>${message.author}</td>  
    5.     </tr>  
    6. </security:authorize>  


        这里说的是拥有ROLE_ADMIN和ROLE_USER两种角色才能显示author信息,显然我们权限不够了,当然把这里修改为ifAnyGranted=”ROLE_ADMIN,ROLE_USER”就可以显示出来了,All和Any的区别嘛,很容易理解。还有一个属性是ifNotGranted,不用说你也会明白它是什么意思了,我们现在修改为ifAnyGranted=”ROLE_ADMIN,ROLE_USER”,刷新页面,就会看到如下内容了: 
     
        其实这已经是在扩展Security框架的视图功能了,就是这么使用的,如果想了解security框架标签库其它标签,那么去参考官方文档吧,因为你已经知道该怎么去套用了。 
        该试试删除功能了,当前用户角色是ROLE_USER,想删除肯定是不可以的了,那么会是怎么样的效果呢,点击Delete链接,看一下吧: 
     
        非常不幸,被拦截下来了,HTTP 403表示没有权限,那么就对了,Security框架起作用了,这就是我们想要的效果了。 
        Security框架的基本URL拦截到此就说完了,是不是很简单?下面就来定制一些操作吧,我们既然编写了自定义登录页面,得用上吧,还有Logout退出功能没用呢。下面就对这基本的配置进行第一次扩展,我们这样做: 

    Xml代码  收藏代码
    1. <security:http>  
    2.     <security:intercept-url pattern="/messageList.htm"  
    3.                     access="ROLE_ADMIN,ROLE_USER,ROLE_GUEST" />  
    4.     <security:intercept-url pattern="/messagePost.htm"  
    5.                     access="ROLE_ADMIN,ROLE_USER" />  
    6.     <security:intercept-url pattern="/messageDelete.htm"  
    7.                     access="ROLE_ADMIN" />  
    8.     <security:form-login login-page="/login.jsp"  
    9. login-processing-url="/login" default-target-url="/messageList.htm"  
    10.             authentication-failure-url="/login.jsp?error=true" />  
    11.     <security:logout logout-success-url="/login.jsp" />  
    12. </security:http>  


        首先去掉auto-config,因为要定制,不让Security按它默认的执行。那么登录验证就配置吧,login-page属性配置的是登录页面,就是我们前面所写的,login-processing-url就是我们处理登录逻辑的请求地址,默认的是j_spring_security_check,前面也说过了,default_target_url就是默认的登录成功转向的目标地址,这里是消息列表页面。最后一个属性是authentication-failure-url,很明白了,就是验证失败转向的页面,这里我们附加一个参数error,页面里面也有体现,就是用它来控制失败信息的打印的。下面一个是配置退出,logout-success-url就是退出后转向的页面,这里是到登录页面,没错,退出后回到登录页面。下面来看看效果吧,修改完毕重启Jetty: 
     
        由于去掉了匿名访问,那么直接请求messageList.htm就会为我们跳转到登录页面了,进行身份验证,此时我们输入一个错误的信息,看看能捕捉到什么: 
     
        验证失败错误会出现Bad credentials,这里不判断是用户不存在还是密码错误,统一是登录凭据错误。输入正确的信息就可以重复上述操作了。使用admin登录成功,会出现: 
     
        至此基本的Security拦截操作已经说完了,是不是很简单呢。当然这是测试的,真实应用中我们的用户不可能这么配置,因为都是存放在数据库中的,那么Security能不能支持数据库用户验证呢?答案是肯定的。只是需要一些扩展配置,这里Security整合了一些数据库验证的操作,要符合Security的验证模式,那么要么我们重新设计数据库,要么在原有基础之上来修改一下数据库设计。这里我们先看一下Security框架默认支持的数据库设计吧,就是它默认SQL查询语句所支持的内容。 
     
        这两个表明是默认的,这么写Security会自己识别出来,不用我们书写SQL语句了。先来看看表设计吧,就这些信息就够Security进行验证了。 

    Sql代码  收藏代码
    1. CREATE TABLE `users` (  
    2.   `USERNAME` varchar(10) NOT NULL,  
    3.   `PASSWORDvarchar(32) NOT NULL,  
    4.   `ENABLED` tinyint(1) NOT NULL,  
    5.   PRIMARY KEY (`USERNAME`)  
    6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8  
    7.   
    8. CREATE TABLE `authorities` (  
    9.   `USERNAME` varchar(10) NOT NULL,  
    10.   `AUTHORITY` varchar(10) NOT NULL,  
    11.   KEY `FK_USERNAME_AUTHORITY` (`USERNAME`),  
    12.   CONSTRAINT `FK_USERNAME_AUTHORITY` FOREIGN KEY (`USERNAME`) REFERENCES `users` (`USERNAME`) ON DELETE NO ACTION ON UPDATE NO ACTION  
    13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8  


        两个表之间有一个外键的关联,是用户名关联,而且我们还进行了md5密码扩展,这也要在Security框架进行配置,在表中插入一些信息,就可以进行数据库验证了,此时Security框架的配置如下,修改认证管理器: 

    Xml代码  收藏代码
    1. <security:authentication-manager>  
    2.     <security:authentication-provider>  
    3.         <security:password-encoder ref="md5Encoder" />  
    4.         <security:jdbc-user-service data-source-ref="dataSource" />  
    5.     </security:authentication-provider>  
    6. </security:authentication-manager>  


        这里我们配置了jdbc数据源和密码编码器,因为连MD5加密方式也是我们自定义的,这样安全系数更高。要使用自定义的加密器,别忘了编写加密器的bean。 

    Xml代码  收藏代码
    1. <bean id="md5Encoder" class="org.ourpioneer.board.util.MD5Encoder" />  


        加密器类需要实现PasswordEncoder接口,然后编写我们自己的加密方案,加密器很简单,如下设计: 

    Java代码  收藏代码
    1. package org.ourpioneer.board.util;  
    2. import org.springframework.dao.DataAccessException;  
    3. import org.springframework.security.authentication.encoding.PasswordEncoder;  
    4. public class MD5Encoder implements PasswordEncoder {  
    5.     public String encodePassword(String origPwd, Object salt)  
    6.             throws DataAccessException {  
    7.         return MD5.getMD5ofStr(origPwd);  
    8.     }  
    9.     public boolean isPasswordValid(String encPwd, String origPwd, Object salt)  
    10.             throws DataAccessException {  
    11.         return encPwd.equals(encodePassword(origPwd, salt));  
    12.     }  
    13. }  


        其中使用到的MD5加密类为: 

    Java代码  收藏代码
    1. package org.ourpioneer.board.util;  
    2. import java.security.MessageDigest;  
    3. /** 
    4.  * 标准MD5加密方法,使用java类库的security包的MessageDigest类处理 <BR> 
    5.  * 也可变为非标准MD5,请修改下面的移位算法 
    6.  *  
    7.  * @author Nanlei 
    8.  *  
    9.  */  
    10. public class MD5 {  
    11.     /** 
    12.      * 获得MD5加密密码的方法 
    13.      */  
    14.     public static String getMD5ofStr(String origString) {  
    15.         String origMD5 = null;  
    16.         try {  
    17.             MessageDigest md5 = MessageDigest.getInstance("MD5");  
    18.             byte[] result = md5.digest(origString.getBytes());  
    19.             origMD5 = byteArray2HexStr(result);  
    20.         } catch (Exception e) {  
    21.             e.printStackTrace();  
    22.         }  
    23.         return origMD5;  
    24.     }  
    25.     /** 
    26.      * 处理字节数组得到MD5密码的方法 
    27.      */  
    28.     private static String byteArray2HexStr(byte[] bs) {  
    29.         StringBuffer sb = new StringBuffer();  
    30.         for (byte b : bs) {  
    31.             sb.append(byte2HexStr(b));  
    32.         }  
    33.         return sb.toString();  
    34.     }  
    35.     /** 
    36.      * 字节标准移位转十六进制方法 
    37.      */  
    38.     private static String byte2HexStr(byte b) {  
    39.         String hexStr = null;  
    40.         int n = b;  
    41.         if (n < 0) {  
    42.             // 若需要自定义加密,请修改这个移位算法即可  
    43.             n = b & 0x7F + 128;  
    44.         }  
    45.         hexStr = Integer.toHexString(n / 16) + Integer.toHexString(n % 16);  
    46.         return hexStr.toUpperCase();  
    47.     }  
    48.     /** 
    49.      * 提供一个MD5多次加密方法 
    50.      */  
    51.     public static String getMD5ofStr(String origString, int times) {  
    52.         String md5 = getMD5ofStr(origString);  
    53.         for (int i = 0; i < times - 1; i++) {  
    54.             md5 = getMD5ofStr(md5);  
    55.         }  
    56.         return getMD5ofStr(md5);  
    57.     }  
    58.     /** 
    59.      * 密码验证方法 
    60.      */  
    61.     public static boolean verifyPassword(String inputStr, String MD5Code) {  
    62.         return getMD5ofStr(inputStr).equals(MD5Code);  
    63.     }  
    64.     /** 
    65.      * 多次加密时的密码验证方法 
    66.      */  
    67.     public static boolean verifyPassword(String inputStr, String MD5Code,  
    68.             int times) {  
    69.         return getMD5ofStr(inputStr, times).equals(MD5Code);  
    70.     }  
    71.     /** 
    72.      * 提供一个测试的主函数 
    73.      */  
    74.     public static void main(String[] args) {  
    75.         System.out.println("123:" + getMD5ofStr("123"));  
    76.         System.out.println("123456789:" + getMD5ofStr("123456789"));  
    77.         System.out.println("pioneer:" + getMD5ofStr("pioneer"));  
    78.         System.out.println("123:" + getMD5ofStr("123"4));  
    79.     }  
    80. }  


        加密工作已经准备好,之前配置的数据源是: 

    Xml代码  收藏代码
    1. <bean id="dataSource"  
    2.     class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
    3.     <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
    4.         <property name="url"  
    5.             value="jdbc:mysql://localhost:3306/board" />  
    6.         <property name="username" value="root" />  
    7.         <property name="password" value="123" />  
    8.     </bean>  


        别忘了加入JDBC的驱动程序,之后我们就可以使用数据库用户验证了,剩下的步骤就和前面是一样的了。 
        至此我们已经了解了Security对标准设置的数据库验证的操作了,下一篇将从非标准的数据库验证开始继续介绍Security框架。欢迎交流,希望对使用者有用。

  • 相关阅读:
    数据库存储过程语法及实例
    springboot2配置JavaMelody与springMVC配置JavaMelody
    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'userId' in 'class java.lang.Integer'
    ajax表单提交执行成功但是没有执行回调函数,并且post变get了
    SpringMVC——重定向跳转传值
    thymeleaf中跳转地址的使用
    solr安装与配置
    redis集群redis-cluster搭建
    nginx安装手册
    Linux忘记root用户的密码
  • 原文地址:https://www.cnblogs.com/sode/p/2719994.html
Copyright © 2011-2022 走看看