Spring Security3的使用方法有4种

kunhua 贡献于2012-05-11

作者 kuka  创建于2012-05-04 10:55:00   修改者kuka  修改于2012-05-04 10:56:00字数15957

文档摘要:一种是全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中。 二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置。 三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器,并分别实现AccessDecisionManager、 InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。
关键词:

Spring Security3的使用方法有4种: 一种是全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中。 二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置。 三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器,并分别实现AccessDecisionManager、 InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。 四是修改spring security的源代码,主要是修改InvocationSecurityMetadataSourceService和UserDetailsService两个类。前者是将配置文件或数据库中存储的资源(url)提取出来加工成为url和权限列表的Map供Security使用,后者提取用户名和权限组成一个完整的 (UserDetails)User对象,该对象可以提供用户的详细信息供AuthentationManager进行认证与授权使用。该方法理论上可行,但是比较暴力,不推荐使用。 本文有两个例子,我在简单例子章节实现了第一种方法。在复杂例子章节实现了第二种和第三种方法组合使用的例子。简单例子通俗易懂,不再赘述。复杂例子及其使用和扩展,我将穿插详细的配置注释和讲解,包括整个程序的执行过程。 简单例子 创建web工程如下图所示: 配置如下图所示: 单击Finish即可。 把从spring网站下载的spring-security-3.1.0.RELEASE解压,并将其中的spring-security-samples-contacts-3.1.0.RELEASE.war解压,把WEB-INF\lib中的jar包拷贝到如下图所示: 修改配置web.xml如下: contextConfigLocation classpath:securityConfig.xml springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /* org.springframework.web.context.ContextLoaderListener index.jsp 在src中创建securityConfig.xml内容如下: 在WebRoot中创建login.jsp内容如下: <%@page language="java" import="java.util.*" pageEncoding="UTF-8"%> 登录
用户:
密码:
在WebRoot中创建accessDenied.jsp,内容如下: <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 访问拒绝 您的访问被拒绝,无权访问该资源!
在WebRoot中创建admin.jsp内容如下: <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> My JSP 'admin.jsp' starting page 欢迎来到管理员页面.
修改index.jsp内容如下: <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> My JSP 'index.jsp' starting page 这是首页,欢迎!
进入admin页面 进入其它页面 添加工程如下图所示: 点击Finish即可,然后运行工程如下图所示: 测试页面如下: 输入用户:cyu密码:sap123,然后回车: 点击“进入admin页面”超链,得到如下图所示: 复杂例子 修改securityConfig.xml内容如下: 编写UrlMatcher接口,内容如下: package com.aostarit.spring.security.tool; public abstract interface UrlMatcher { public abstract Object compile(String paramString); public abstract boolean pathMatchesUrl(Object paramObject, String paramString); public abstract String getUniversalMatchPattern(); public abstract boolean requiresLowerCaseUrl(); } 这个接口是以前spring版本中的,现在的spring版本中不存在,由于项目需要且使用方便,故加入到项目当中。 编写AntUrlPathMatcher类,内容如下: package com.aostarit.spring.security.tool; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; public class AntUrlPathMatcher implements UrlMatcher { private boolean requiresLowerCaseUrl; private PathMatcher pathMatcher; public AntUrlPathMatcher() { this(true); } public AntUrlPathMatcher(boolean requiresLowerCaseUrl) { this.requiresLowerCaseUrl = true; this.pathMatcher = new AntPathMatcher(); this.requiresLowerCaseUrl = requiresLowerCaseUrl; } public Object compile(String path) { if (this.requiresLowerCaseUrl) { return path.toLowerCase(); } return path; } public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl) { this.requiresLowerCaseUrl = requiresLowerCaseUrl; } public boolean pathMatchesUrl(Object path, String url) { if (("/**".equals(path)) || ("**".equals(path))) { return true; } return this.pathMatcher.match((String)path, url); } public String getUniversalMatchPattern() { return "/**"; } public boolean requiresLowerCaseUrl() { return this.requiresLowerCaseUrl; } public String toString() { return super.getClass().getName() + "[requiresLowerCase='" + this.requiresLowerCaseUrl + "']"; } } 这个类是以前spring版本中的工具类,现在的spring版本中不存在,由于项目需要且使用方便,故加入到项目当中。 编写MyFilterSecurityInterceptor类,内容如下: package com.aostarit.spring.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; /** * Method that is actually called by the filter chain. Simply delegates to * the {@link #invoke(FilterInvocation)} method. * * @param request * the servlet request * @param response * the servlet response * @param chain * the filter chain * * @throws IOException * if the filter chain fails * @throws ServletException * if the filter chain fails */ 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 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); } } public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource newSource) { this.securityMetadataSource = newSource; } public void destroy() { } public void init(FilterConfig arg0) throws ServletException { } } 核心的是InterceptorStatusToken token = super.beforeInvocation(fi);会调用我们定义的accessDecisionManager:decide(Object object)和securityMetadataSource:getAttributes(Object object)方法。 编写MyInvocationSecurityMetadataSource类,内容如下: package com.aostarit.spring.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; 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 com.aostarit.spring.security.tool.AntUrlPathMatcher; import com.aostarit.spring.security.tool.UrlMatcher; /** * * 此类在初始化时,应该取到所有资源及其对应角色的定义 * */ public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private UrlMatcher urlMatcher = new AntUrlPathMatcher(); private static Map> resourceMap = null; public MyInvocationSecurityMetadataSource() { loadResourceDefine(); } private void loadResourceDefine() { resourceMap = new HashMap>(); Collection atts = new ArrayList(); ConfigAttribute ca = new SecurityConfig("ROLE_USER"); atts.add(ca); resourceMap.put("/index.jsp", atts); Collection attsno = new ArrayList(); ConfigAttribute cano = new SecurityConfig("ROLE_NO"); attsno.add(cano); resourceMap.put("/other.jsp", attsno); } // According to a URL, Find out permission configuration of this URL. public Collection getAttributes(Object object) throws IllegalArgumentException { // guess object is a URL. String url = ((FilterInvocation)object).getRequestUrl(); Iterator ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (urlMatcher.pathMatchesUrl(resURL, url)) { return resourceMap.get(resURL); } } return null; } public boolean supports(Class clazz) { return true; } public Collection getAllConfigAttributes() { return null; } } 对于资源的访问权限的定义,我们通过实现FilterInvocationSecurityMetadataSource这个接口来初始化数据。看看loadResourceDefine方法,我在这里,假定index.jsp这个资源,需要ROLE_USER角色的用户才能访问, other.jsp这个资源,需要ROLE_NO角色的用户才能访问。这个类中,还有一个最核心的地方,就是提供某个资源对应的权限定义,即getAttributes方法返回的结果。注意,我例子中使用的是 AntUrlPathMatcher这个path matcher来检查URL是否与资源定义匹配,事实上你还要用正则的方式来匹配,或者自己实现一个matcher。 这里的角色和资源都可以从数据库中获取,建议通过我们封装的平台级持久层管理类获取和管理。 编写MyAccessDecisionManager类,内容如下: package com.aostarit.spring.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; public class MyAccessDecisionManager implements AccessDecisionManager { //In this method, need to compare authentication with configAttributes. // 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here. // 2, Check authentication has attribute in permission configuration (configAttributes) // 3, If not match corresponding authentication, throw a AccessDeniedException. public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(configAttributes == null){ return ; } System.out.println(object.toString()); //object is a URL. Iterator ite=configAttributes.iterator(); while(ite.hasNext()){ ConfigAttribute ca=ite.next(); String needRole=((SecurityConfig)ca).getAttribute(); for(GrantedAuthority ga:authentication.getAuthorities()){ if(needRole.equals(ga.getAuthority())){ //ga is user's role. return; } } } throw new AccessDeniedException("no right"); } public boolean supports(ConfigAttribute attribute) { // TODO Auto-generated method stub return true; } public boolean supports(Class clazz) { return true; } } 在这个类中,最重要的是decide方法,如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则throw new AccessDeniedException("no right")。所有的异常建议平台统一进行封装并管理。 编写MyUserDetailService类,内容如下: package com.aostarit.spring.security; import java.util.ArrayList; import java.util.Collection; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; 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; public class MyUserDetailService implements UserDetailsService { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { Collection auths=new ArrayList(); GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN"); // auths.add(auth2); if(username.equals("cyu")){ auths=new ArrayList(); GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER"); auths.add(auth1); auths.add(auth2); } // User(String username, String password, boolean enabled, boolean accountNonExpired, // boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) { User user = new User(username, "sap123", true, true, true, true, auths); return user; } } 在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等。建议通过我们封装的平台级持久层管理类获取和管理。 1. 整个程序执行的过程如下: 1、容器启动(MyInvocationSecurityMetadataSource:loadResourceDefine加载系统资源与权限列表) 2、用户发出请求 3、过滤器拦截(MyFilterSecurityInterceptor:doFilter) 4、取得请求资源所需权限(MyInvocationSecurityMetadataSource:getAttributes) 5、匹配用户拥有权限和请求权限(MyAccessDecisionManager:decide),如果用户没有相应的权限,执行第6步,否则执行第7步 6、登录 7、验证并授权(MyUserDetailService:loadUserByUsername) 8、重复4,5 发布,测试页面如下: 输入用户:cyu密码:sap123,然后回车: 点击“进入admin页面”超链,得到如下图所示: 点击“进入其它页面”超链,得到如下图所示:

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 4 人已下载

下载文档