Spring Security初探

来源:互联网 发布:文本加密软件 编辑:程序博客网 时间:2024/06/11 10:04

Spring Security这个框架是为基于权限的安全提供解决方案的,它能够基于权限来判断是否让你请求某url,也能够基于权限来渲染视图(具体来说就是同样的一个页面,根据不同用户的不同权限显示不一样的东西),还能够对请求方法(RMI)进行权限验证。个人感觉对于一般的建站使用这个框架是够用了的。

一般来说,一切的验证都将从HttpServletRequest开始,很多人都会想到了使用Filter或者Servlet来实现对request对象的处理,事实上Spring Security也是这样子做的。不过,它通过一个委托来让Filter其作用,Filter的具体定义不在web.xml文件中,而是定义在了Spring IOC容器中。为什么要说这个呢?因为在你部署的时候出现了NoSuchBeanDefinition异常时,估计就跟它有关了。

导入Spring Security相关jar包

这里我使用的是Myecplise,直接引入相关的spring security 3.0相关的包。

在web.xml中配置filter代理

  <!-- Security Filter -->  <!-- BEGIN -->  <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>  <dispatcher>FORWARD</dispatcher>  <dispatcher>REQUEST</dispatcher>  </filter-mapping>  <!-- END -->

需要注意的是,spring security 依赖于 spring的IOC容器,在web.xml中,你还需要配置一个监听器,确保在ServletContext创建好时创建IOC容器
  <listener>  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  </listener>  <context-param>  <param-name>contextConfigLocation</param-name>  <param-value>classpath*:applicationContext.xml</param-value>  </context-param>

默认情况下,在创建IOC容器,会applicationContext.xml文件是在项目的根目录下,因为用IDE来导入spring额支持都是把appliactionContext.xml文件创建在classpath路径下的,所以需要加上上下文参数。

引入spring security命名空间

引入命名空间是为了使用对应的security标签。
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:security="http://www.springframework.org/schema/security"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security-3.0.xsd">
其中带有security这个词的就是需要引入的命名空间。

最小化spring security配置

<security:jdbc-user-service id="jdbcUserService" data-source-ref="dataSource"authorities-by-username-query="select users.username, authority from users, authorities where username=?"/><security:authentication-manager><security:authentication-provider user-service-ref="jdbcUserService"></security:authentication-provider></security:authentication-manager><security:http access-denied-page="/DeniedPage.jsp"><security:form-login/><security:http-basic/><security:logout/><!-- 不过滤css文件 --><security:intercept-url pattern="/**.css" filters="none"/><!-- 不过滤js文件 --><security:intercept-url pattern="/**.js" filters="none"/><!-- 不过滤图标图片 --><security:intercept-url pattern="/**.png" filters="none"/><security:intercept-url pattern="/**.gif" filters="none"/><security:intercept-url pattern="/**.jpg" filters="none"/><security:intercept-url pattern="/background/**" access="ROLE_ADMIN"/><security:intercept-url pattern="/**" access="ROLE_USER"/></security:http>

上述是一个spring security的最小化配置。
如果你的配置中没有<security: form-login/>元素,那么会抛出异常。
如果你的配置中没有<security:authorities-manager>元素,那么会抛出异常。

下面来解析一下这个最小化配置都做了什么?
首先,你需要知道,一般的安全处理需要做这么两件事情:用户认证和权限认证。用户认证是验证这个用户是否合法用户,更简单通俗点就是你能登陆成功,就是合法用户,登陆失败,就是非法用户。权限验证,就是验证你要做的这件事情,实际上你到底有没有权限。
<security:form-login/>元素是用来做用户认证的,指定用户的登录页面,登录表单的处理路径和登录失败的处理路径,可选的属性有
login-processing-url,用来指定登录表单的处理路径,默认为j_spring_security_check
default-target-url,
login-page,指定登录页所在的url路径,如果没有指定那么将使用/spring_security_login
authentication-failure-url,当登录失败时,将会跳转到指定的url路径,如果没有指定,那么将使用/spring_security_login?login_error
authentication-success-handler-ref,用来处理成功认证请求的一个AuthenticationSuccessHandler bean。 
authentication-failure-handler-ref,用来处理认证失败请求的一个AuthenticationFailureHandler bean。
<security:http-basic/>,用来指定使用http来进行认证。
<security:logout/>,用来配置登出的一个filter,可供配置的属性有:
logout-url,指定的登出url路径,如果没有指定,将使用/j_spring_security_logout。
logout-success-url,登出成功后,跳转到的url路径,如果没有指定,将使用/.。
invalidate-session,指定登出动作发生后,是否将对应的HttpSession会话销毁,默认值为true。
success-handler-ref,一个实现了LogoutSuccessHandler的bean。

细心的读者肯定会发现,security在哪里记录合法用户?事实上,常用的两种有使用内存来进行合法用户记录和使用数据库。如果某用户数量非常少,可以使用内存来进行记录,但是大多数情况下还是使用数据库来进行合法用户记录。

使用内存记录合法用户和权限

使用<security:user-service/>,
<security:user-service id="memoryUserService"><security:user name="admin" password="admin" authorities="ROLE_ADMIN"/></security:user-service>

上述创建了一个基于内存的合法用户,用户名是admin,密码是admin,权限是ROLE_ADMIN。你可以在authorities中加入多个权限,用逗号隔开既可。
此外,你还需要配置一个authentication-manager,来做认证管理
<security:authentication-manager><security:authentication-provider user-service-ref="memoryUserService"></security:authentication-provider></security:authentication-manager>

这种基于内存来进行用户、权限存储、认证的方式,在实际的项目中基本上是不会用到的。为什么呢?个人觉得,这种配置方式,导致了角色和权限成了一对一的关联了,而事实上,角色和权限是多对多的,这样子才能更灵活地为用户去分配角色。而且一个项目中的用户成百上千,是不可能写在内存中的。

使用JDBC来记录合法用户和权限

security默认的简单配置,使用JDBC,还是没有将角色和权限细分开来,不过,对于小型的应用,或许不需要分得这么细。使用<securiy:jdbc-user-service/>,
<security:jdbc-user-service id="jdbcUserService" data-source-ref="dataSource"authorities-by-username-query="select users.username, authority from users, authorities where username=?"/>

使用jdbc-user-service,需要提供一个数据源来供security进行数据库的使用。可供配置的属性如下:
data-source-ref,这个属性是必须的,因为security要使用到数据库连接。
users-by-username-query,检索用户名所用的sql语句,如果没有指定,将采用默认"select username, password from users where username="。
authorities-by-username-query,检索指定用户名的权限列表所用的sql语句,如果没有指定,将采用"select username, authority from authorities where username="。
role-prefix,用来指定角色对应的前缀,有什么用呢?比如说你常用ROLE_ADMIN,在配置后就可以使用ADMIN。
group-authorities-by-username-query,
cache-ref
此外,还是需要配置认证管理器authentication-manager
<security:authentication-manager><security:authentication-provider user-service-ref="jdbcUserService"></security:authentication-provider></security:authentication-manager>

细心的读者肯定能根据默认的sql语句,来推敲一个数据库表的最小组成列。当初我也是这么推敲的,需要提供两张表,表名分别为users和authorities,我使用的是mysql数据库,下面给我自己设计的这两张表的DDL。
users表
CREATE TABLE `users` (  `id` int(11) NOT NULL auto_increment,  `username` char(20) character set utf8 collate utf8_bin NOT NULL default '',  `password` char(20) character set utf8 collate utf8_bin default NULL,  `enabled` binary(1) default '1',  PRIMARY KEY  (`id`),  UNIQUE KEY `username` (`username`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

authorities表
CREATE TABLE `authorities` (  `id` int(11) NOT NULL auto_increment,  `authority` varchar(100) default NULL,  `userId` int(11) default NULL,  PRIMARY KEY  (`id`),  KEY `userId` (`userId`),  CONSTRAINT `authorities_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这样的话,默认的在配置jdbc-user-service时,就要去改变默认的权限列表查询语句了authorities-by-username-query,要更改成以下语句
select users.username, authorities.authority from users, authorities where users.username=?

由于两张表之间的联系是通过外键,而mysql5.0版本的外键只支持整型,如果你采用的是oracle数据库,可以不用改变默认表的结构。

配置拦截<security: intercept-url/>

只有配置了该元素,security才能真正显示出它的强大和灵活。首先,一些资源是没有必要被拦截的,比如说css文件、js文件和png格式图片等等。配置intercept-url时,需要把范围小的先声明在前面,因为security会优先匹配声明靠前的intercept-url元素,一旦匹配了一个,就不会匹配下一个了。
<!-- 不过滤css文件 --><security:intercept-url pattern="/**.css" filters="none"/><!-- 不过滤js文件 --><security:intercept-url pattern="/**.js" filters="none"/><!-- 不过滤图标图片 --><security:intercept-url pattern="/**.png" filters="none"/><security:intercept-url pattern="/**.gif" filters="none"/><security:intercept-url pattern="/**.jpg" filters="none"/><security:intercept-url pattern="/background/**" access="ROLE_ADMIN"/><security:intercept-url pattern="/**" access="ROLE_USER"/>
filters属性配置为none表示不需要任何权限,pattern中,**表示可以跨越目录,而*表示不可以跨越目录,access表示可以访问该资源的权限列表。

好了,整体的一个最简单security的配置就完成了。

上述讲的都是没有细分角色和权限的security配置,你可以看到,角色跟权限是一一对应的,这样是做不到复杂的权限控制的,正常情况下,你需要七张表来进行复杂的权限控制。用户表、角色表、权限表、资源表,为什么是七张而不是八张,因为用户跟角色是多对多关联,会得到一张中间表;角色跟权限是多对多关联,又会得到一张中间表;权限跟资源又是多对多关联,又会得到一张中间表。

下文的内容我会写在spring security细分角色与权限中。

0 0