本文项目参照http://www.blogjava.net/SpartaYew/archive/2011/05/19/SpingSecurity3.html的第三种方法实现,并简写博客中一部分内容。
本文项目采用spring security3.1 + Mybatis3.2 + oracle10
Spring Security3提供了灵活的扩展方法。具体应该扩展哪些类呢? 或者到底Spring Security3工作的流程如何,你不妨参看下面一篇文章,就会获得
一些启示,网址为:http://www.blogjava.net/youxia/archive/2008/12/07/244883.html , 哈哈,谢谢分享。
还有一个地址很有价值, http://wenku.baidu.com/view/4ec7e324ccbff121dd368364.html ,我就参考着上面的介绍扩展了4个类。
不过我得提一下,原文的作者为了考验你的耐性和自信心,故意在代码里面卖了几点小小的关子,因此若是完全按照作者的原文代码装配起来的权限系统,是不会那么顺利地工作的,天下似乎真是没有不花费力气的午餐!在装配完成后,我也是经过九九八十一难的折磨,在用户、角色、权限、资源的
“天下黄河九曲十八弯”里面盘旋迂回,终于到达了成功的彼岸。至此才对Spring Security有了更深层次的理解,更加佩服作者的良苦用心。 哈哈。
并扩展了User类以增加其相关的各类其他信息(如Email,职务,所在单位id等)。
相关的代码如下(包含5个关键类)(修改过作者原先代码):
package com.springmvc.security; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public Class<? extends Object> getSecureObjectClass() { return FilterInvocation.class; } public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } public void destroy() { } public void init(FilterConfig filterconfig) throws ServletException { } }
package com.springmvc.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.Resource; 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 org.springframework.stereotype.Service; import com.springmvc.dao.TestDao; public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { // @Resource(name = "testDao") private TestDao testDao; private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public MyInvocationSecurityMetadataSourceService(TestDao testDao) { //使用注解方式的话,只能在构造函数执行完成后才能获得实例 this.testDao = testDao; loadResourceDefine(); } // 在Web服务器启动时,提取系统中的所有权限 private void loadResourceDefine() { List<String> query = this.testDao.getAllAuthorityName(); /* * 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。 * sparta */ resourceMap = new HashMap<String, Collection<ConfigAttribute>>(); for (String auth : query) { ConfigAttribute ca = new SecurityConfig(auth); List<String> query1 = this.testDao.getResource(auth); 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); } } } } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } // 根据URL,找到相关的权限配置。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // object 是一个URL,被用户请求的url。 String url = ((FilterInvocation) object).getRequestUrl(); System.out.println("url" + url); 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 (url.equals(resURL)) { return resourceMap.get(resURL); } } return null; } @Override public boolean supports(Class<?> arg0) { return true; } }
package com.springmvc.security; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Resource; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.springmvc.dao.TestDao; public class MyUserDetailsService implements UserDetailsService { @Resource(name="testDao") private TestDao testDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { System.out.println("username" + username); List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>(); List<String> authorityName = this.testDao.getAuthorityName(username); for(String roleName : authorityName) { System.out.println(roleName); SimpleGrantedAuthority authority = new SimpleGrantedAuthority(roleName); auths.add(authority); } String pwd = this.testDao.getPWD(username); return new User(username,pwd,true,true,true,true,auths); } }
package com.springmvc.security; import java.util.Collection; import java.util.Iterator; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; /** * AccessdecisionManager在Spring security中是很重要的。 * * 在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。 这就是赋予给主体的权限。 * GrantedAuthority对象通过AuthenticationManager 保存到 * Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。 * * Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。 * 一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。 这个 AccessDecisionManager * 被AbstractSecurityInterceptor调用, 它用来作最终访问控制的决定。 * 这个AccessDecisionManager接口包含三个方法: * * void decide(Authentication authentication, Object secureObject, * List<ConfigAttributeDefinition> config) throws AccessDeniedException; boolean * supports(ConfigAttribute attribute); boolean supports(Class clazz); * * 从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。 * 特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。 比如,让我们假设安全对象是一个MethodInvocation。 * 很容易为任何Customer参数查询MethodInvocation, * 然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。 * 如果访问被拒绝,实现将抛出一个AccessDeniedException异常。 * * 这个 supports(ConfigAttribute) 方法在启动的时候被 * AbstractSecurityInterceptor调用,来决定AccessDecisionManager * 是否可以执行传递ConfigAttribute。 supports(Class)方法被安全拦截器实现调用, * 包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。 */ public class MyAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (configAttributes == null) { return; } Iterator<ConfigAttribute> ite = configAttributes.iterator(); while (ite.hasNext()) { ConfigAttribute ca = ite.next(); String needRole = ((SecurityConfig) ca).getAttribute(); System.out.println("authentication size" + authentication.getAuthorities().size()); // ga 为用户所被赋予的权限。 needRole 为访问相应的资源应该具有的权限。 for (GrantedAuthority ga : authentication.getAuthorities()) { if (needRole.trim().equals(ga.getAuthority().trim())) { return; } } } throw new AccessDeniedException(""); } public boolean supports(ConfigAttribute attribute) { return true; } public boolean supports(Class<?> clazz) { return true; } }
数据库的SQL及预置数据(简化了数据):
create table SYS_AUTHORITIES ( AUTHORITY_ID VARCHAR2(32) not null, AUTHORITY_NAME VARCHAR2(40), AUTHORITY_DESC VARCHAR2(100), ENABLED NUMBER(1), ISSYS NUMBER(1), MODULE VARCHAR2(4) ) create table SYS_AUTHORITIES_RESOURCES ( ID NUMBER(13) not null, AUTHORITY_ID VARCHAR2(32), RESOURCE_ID VARCHAR2(32), ENABLED NUMBER(1) ) create table SYS_RESOURCES ( RESOURCE_ID VARCHAR2(32) not null, RESOURCE_NAME VARCHAR2(100), RESOURCE_DESC VARCHAR2(100), RESOURCE_TYPE VARCHAR2(40), RESOURCE_STRING VARCHAR2(200), PRIORITY NUMBER(1), ENABLED NUMBER(1), ISSYS NUMBER(1), MODULE VARCHAR2(4) ) create table SYS_ROLES ( ROLE_ID VARCHAR2(32) not null, ROLE_NAME VARCHAR2(40), ROLE_DESC VARCHAR2(100), ENABLED NUMBER(1), ISSYS NUMBER(1), MODULE VARCHAR2(4) ) create table SYS_ROLES_AUTHORITIES ( ID NUMBER(13) not null, ROLE_ID VARCHAR2(32), AUTHORITY_ID VARCHAR2(32), ENABLED NUMBER(1) ) create table SYS_USERS ( USER_ID VARCHAR2(32) not null, USER_ACCOUNT VARCHAR2(30), USER_NAME VARCHAR2(40), USER_PASSWORD VARCHAR2(100), USER_DESC VARCHAR2(100), ENABLED NUMBER(1), ISSYS NUMBER(1), USER_DEPT VARCHAR2(20), USER_DUTY VARCHAR2(10), SUB_SYSTEM VARCHAR2(30) ) create table SYS_USERS_ROLES ( ID NUMBER(13) not null, USER_ID VARCHAR2(32), ROLE_ID VARCHAR2(32), ENABLED NUMBER(1) ) insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE) values ('auth_admin', 'auth_admin', null, null, null, null); insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE) values ('auth_lxb', 'auth_lxb', null, null, null, null); insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE) values ('auth_user', 'auth_user', null, null, null, null); insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED) values (1, 'auth_admin', 'admin', 1); insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED) values (2, 'auth_lxb', 'lxb', null); insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED) values (3, 'auth_user', 'user', null); insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE) values ('admin', 'admin', null, null, '/admin/admin.jsp', null, null, null, null); insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE) values ('lxb', 'lxb', null, null, '/lxb.lxb.jsp', null, null, null, null); insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE) values ('user', 'user', null, null, '/user/user.jsp', null, null, null, null); insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE) values ('role_admin', 'role_admin', null, null, null, null); insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE) values ('role_lxb', 'role_lxb', null, null, null, null); insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE) values ('role_user', 'role_user', null, null, null, null); insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED) values (1, 'role_admin', 'auth_admin', 1); insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED) values (2, 'role_lxb', 'auth_lxb', 1); insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED) values (3, 'role_user', 'auth_user', 1); insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM) values ('admin', 'admin', 'admin', 'admin', null, null, null, null, null, null); insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM) values ('lxb', 'lxb', 'lxb', 'lxb', null, null, null, null, null, null); insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM) values ('user', 'user', 'user', 'user', null, null, null, null, null, null); insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED) values (1, 'admin', 'role_admin', 1); insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED) values (2, 'lxb', 'role_lxb', 1); insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED) values (3, 'user', 'role_user', 1);
web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>springsecurity</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml classpath:applicationContext-security.xml </param-value> </context-param> <!-- 定义spring security代理Filter --> <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> </filter-mapping> <!-- - Loads the root application context of this web app at startup. --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd" default-lazy-init="true" default-autowire="byName"> <context:component-scan base-package="com.springmvc"/> <context:annotation-config/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:ORCL"/> <property name="username" value="scott"/> <property name="password" value="scott"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:myBatis.xml"/> <property name="mapperLocations"> <list> <value>classpath:mapper/*.xml</value> </list> </property> <property name="transactionFactory"> <bean class="org.mybatis.spring.transaction.SpringManagedTransactionFactory"/> </property> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <bean id="sqlSessionForBatch" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> <constructor-arg index="1" value="BATCH"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="transactionProxy" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="insert*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="update*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="delete*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="clone*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="execute*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean> </beans>
applicationContext-security.xml配置
<?xml version="1.0" encoding="UTF-8"?> <b:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <!-- 不要过滤图片等静态资源,其中**代表可以跨越目录,*不可以跨越目录。 --> <!-- <http pattern="/**/*.jpg" security="none" /> <http pattern="/**/*.png" security="none" /> <http pattern="/**/*.gif" security="none" /> <http pattern="/**/*.css" security="none" /> <http pattern="/**/*.js" security="none" /> --> <http pattern="/login.jsp" security="none" /> <!-- <http pattern="index.html" security="none"/> --> <http pattern="/jsp/forgotpassword.jsp" security="none" /> <http auto-config="true" access-denied-page="/accessDenied.jsp"> <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" /> <!-- "记住我"功能,采用持久化策略(将用户的登录信息存放在数据库表中) --> <remember-me data-source-ref="dataSource" /> <!-- 检测失效的sessionId,超时时定位到另外一个URL --> <session-management invalid-session-url="/sessionTimeout.jsp" /> <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </http> <!-- 一个自定义的filter,必须包含authenticationManager, accessDecisionManager,securityMetadataSource三个属性。 --> <b:bean id="myFilter" class="com.springmvc.security.MyFilterSecurityInterceptor"> <b:property name="authenticationManager" ref="authenticationManager"/> <b:property name="accessDecisionManager" ref="myAccessDecisionManager"/> <b:property name="securityMetadataSource" ref="mySecurityMetadataSource"/> </b:bean> <!-- 注意能够为authentication-manager 设置alias别名 --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsManager"> <!-- <password-encoder ref="passwordEncoder"> <salt-source user-property="username" /> </password-encoder> --> </authentication-provider> </authentication-manager> <!-- 事件监听:实现了 ApplicationListener监听接口,包括AuthenticationCredentialsNotFoundEvent 事件, AuthorizationFailureEvent事件,AuthorizedEvent事件, PublicInvocationEvent事件 --> <b:bean class="org.springframework.security.authentication.event.LoggerListener" /> <!-- 用户详细信息管理:数据源、用户缓存(通过数据库管理用户、角色、权限、资源)。 --> <b:bean id="userDetailsManager" class="com.springmvc.security.MyUserDetailsService"/> <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源。 --> <b:bean id="myAccessDecisionManager" class="com.springmvc.security.MyAccessDecisionManager"> </b:bean> <!-- 资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色去访问。 --> <b:bean id="mySecurityMetadataSource" class="com.springmvc.security.MyInvocationSecurityMetadataSourceService"> <b:constructor-arg ref="testDao"/> </b:bean> </b:beans>
主要内容大概是这些,剩下的参考上传的项目,添加缺省的内容,
启动服务,打开浏览器,输入http://localhost:8080/ThirdSpringSecurity/user/user.jsp,由于有权限限制,会跳转到登录页面,输入user账号和密码(没有采用密文)登录后就可以到user.jsp页面,如果将url改为http://localhost:8080/ThirdSpringSecurity/admin/admin.jsp,会跳转到权限不足的界面。
,验证及授权的过程如下:
1、当Web服务器启动时,通过Web.xml中对于Spring Security的配置,加载过滤器链,那么在加载MyFilterSecurityInterceptor类时,会注入MyInvocationSecurityMetadataSourceService、MyUserDetailsService、MyAccessDecisionManager类。
2、该MyInvocationSecurityMetadataSourceService类在执行时会提取数据库中所有的用户权限,形成权限列表;
并循环该权限列表,通过每个权限再从数据库中提取出该权限所对应的资源列表,并将资源(URL)作为key,权限列表作为value,形成Map结构的数据。
3、当用户登录时,AuthenticationManager进行响应,通过用户输入的用户名和密码,然后再根据用户定义的密码算法和盐值等进行计算并和数据库比对,
当正确时通过验证。此时MyUserDetailsService进行响应,根据用户名从数据库中提取该用户的权限列表,组合成UserDetails供Spring Security使用。
4、当用户点击某个功能时,触发MyAccessDecisionManager类,该类通过decide方法对用户的资源访问进行拦截。
用户点击某个功能时,实际上是请求某个URL或Action, 无论.jsp也好,.action或.do也好,在请求时无一例外的表现为URL。
还记得第2步时那个Map结构的数据吗? 若用户点击了"login.action"这个URL之后,那么这个URL就跟那个Map结构的数据中的key对比,若两者相同,
则根据该url提取出Map结构的数据中的value来,这说明:若要请求这个URL,必须具有跟这个URL相对应的权限值。这个权限有可能是一个单独的权限,
也有可能是一个权限列表,也就是说,一个URL有可能被多种权限访问。
那好,我们在MyAccessDecisionManager类的decide这个方法里,将通过URL取得的权限列表进行循环,然后跟第3步中登录的用户所具有的权限进行比对,若相同,则表明该用户具有访问该资源的权利。 不大明白吧? 简单地说, 在数据库中我们定义了访问“LOGIN”这个URL必须是具有ROLE_ADMIN权限的人来访问,那么,登录用户恰恰具有该ROLE_ADMIN权限,两者的比对过程中,就能够返回TRUE,可以允许该用户进行访问。就这么简单!
不过在第2步的时候,一定要注意,MyInvocationSecurityMetadataSoruceService类的loadResourceDefine()方法中,形成以URL为key,权限列表为value的Map时,
要注意key和Value的对应性,避免Value的不正确对应形成重复,这样会导致没有权限的人也能访问到不该访问到的资源。
还有getAttributes()方法,要有 url.indexOf("?")这样的判断,要通过判断对URL特别是Action问号之前的部分进行匹配,防止用户请求的带参数的URL与你数据库中定义的URL不匹配,造成访问拒绝!