1、配置web.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <!-- applicationContext*.xml的位置 --> 8 <context-param> 9 <param-name>contextConfigLocation</param-name> 10 <param-value>classpath:applicationContext*.xml</param-value> 11 </context-param> 12 <!-- 对spring进行监听 --> 13 <listener> 14 <listener-class> 15 org.springframework.web.context.ContextLoaderListener 16 </listener-class> 17 </listener> 18 <!-- springsecurity需要的filter --> 19 <filter> 20 <filter-name>springSecurityFilterChain</filter-name> 21 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 22 </filter> 23 <filter-mapping> 24 <filter-name>springSecurityFilterChain</filter-name> 25 <url-pattern>/*</url-pattern> 26 </filter-mapping> 27 </web-app>
2、配置applicationContext-security.xml(重要)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:security="http://www.springframework.org/schema/security" 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" access-denied-page="/403.jsp"> <!-- 当访问拒绝(比如权限不够)时,会转到403.jsp --> 10 <security:intercept-url pattern="/login.jsp" filters="none"/> 11 <security:form-login login-page="/login.jsp" authentication-failure-url="/login?error=true" default-target-url="/index.jsp"/> 12 <security:logout logout-success-url="/login.jsp"/> 13 <security:http-basic/> 14 <!-- 增加一个filter,且不能修改默认的filter了,这个filter位于FILTER_SECURITY_INTERCEPTOR之前--> 15 <security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter"/> 16 </security:http>
17 18 <!-- 一个自定义的filter,必须包含authenticationManager,accessManager,securityMetadataSource三个属性 ,所有的控制都将在这三个类中实现--> 19 <bean id="myFilter" class="com.lwh.security.filter.MyFilterSecurityInterceptor"> 20 <property name="authenticationManager" ref="authenticationManager"/> 21 <property name="accessDecisionManager" ref="myAccessDecisionManagerBean"/> 22 <property name="securityMetadataSource" ref="securityMetadataSource"/> 23 </bean> 24 25 <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 --> 26 <security:authentication-manager alias="authenticationManager"> 27 <security:authentication-provider user-service-ref="myUserDetailService"> 28 <!-- <security:password-encoder hash="md5"/>--> 29 </security:authentication-provider> 30 </security:authentication-manager> 31 <bean id="myUserDetailService" class="com.lwh.secutity.service.MyUserDetailService"> 32 </bean> 33 <!-- 访问决策器,决定某个用户具有的角色是否有足够的权限去访问某个资源 --> 34 <bean id="myAccessDecisionManagerBean" class="com.lwh.secutity.service.MyAccessDecisionManager"> 35 </bean> 36 <!-- 资源源数据定义,即定义某一资源可以哪些角色访问 --> 37 <bean id="securityMetadataSource" class="com.lwh.secutity.service.MyInvocationSecurityMetadataSource"> 38 </bean> 39 </beans>
3、自定义的filter的实现
1 import java.io.IOException; 2 import javax.servlet.Filter; 3 import javax.servlet.FilterChain; 4 import javax.servlet.FilterConfig; 5 import javax.servlet.ServletException; 6 import javax.servlet.ServletRequest; 7 import javax.servlet.ServletResponse; 8 import org.springframework.security.access.SecurityMetadataSource; 9 import org.springframework.security.access.intercept.AbstractSecurityInterceptor; 10 import org.springframework.security.access.intercept.InterceptorStatusToken; 11 import org.springframework.security.web.FilterInvocation; 12 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 13 public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor 14 implements Filter { 15 private FilterInvocationSecurityMetadataSource securityMetadataSource; 16 /**@param request 17 * the servlet request 18 * @param response 19 * the servlet response 20 * @param chain 21 * the filter chain 22 * @throws IOException 23 * if the filter chain fails 24 * @throws ServletException 25 * if the filter chain fails 26 * 27 */ 28 public void doFilter(ServletRequest request, ServletResponse response, 29 FilterChain chain) throws IOException, ServletException { 30 FilterInvocation fi=new FilterInvocation(request, response, chain); 31 invoke(fi); 32 33 } 34 public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { 35 return securityMetadataSource; 36 } 37 @Override 38 public Class<? extends Object> getSecureObjectClass() { 39 // TODO Auto-generated method stub 40 return FilterInvocation.class; 41 } 42 public void invoke(FilterInvocation fi)throws IOException,ServletException{ 43 InterceptorStatusToken token=super.beforeInvocation(fi); 44 try { 45 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); 46 } catch (Exception e) { 47 // TODO: handle exception 48 }finally{ 49 super.afterInvocation(token, null); 50 } 51 } 52 @Override 53 public SecurityMetadataSource obtainSecurityMetadataSource() { 54 // TODO Auto-generated method stub 55 return this.securityMetadataSource; 56 } 57 public void setSecurityMetadataSource( 58 FilterInvocationSecurityMetadataSource securityMetadataSource) { 59 this.securityMetadataSource = securityMetadataSource; 60 } 61 public void destroy() { 62 // TODO Auto-generated method stub 63 64 } 65 public void init(FilterConfig arg0) throws ServletException { 66 // TODO Auto-generated method stub 67 68 } 69 70 }
最核心的代码就是invoke方法中的InterceptorStatusToken token=super.beforeInvocation(fi);这一句,即在执行doFilter之前,进行权限的检查,而具体的实现已经交给accessDecisionManager了
4、authentication-provider的实现
1 import java.util.ArrayList; 2 import java.util.Collection; 3 4 import org.springframework.dao.DataAccessException; 5 import org.springframework.security.core.GrantedAuthority; 6 import org.springframework.security.core.authority.GrantedAuthorityImpl; 7 import org.springframework.security.core.userdetails.UserDetails; 8 import org.springframework.security.core.userdetails.User; 9 import org.springframework.security.core.userdetails.UserDetailsService; 10 import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 //该类是用于从数据库中读入用户的密码、角色信息、是否锁定、账号是否过期等 12 public class MyUserDetailService implements UserDetailsService { 13 public UserDetails loadUserByUsername(String username) 14 throws UsernameNotFoundException, DataAccessException { 15 Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>(); 16 GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN"); 17 auths.add(auth2); 18 if(username.equals("louis")) 19 { 20 auths=new ArrayList<GrantedAuthority>(); 21 GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_ROBIN"); 22 auths.add(auth1); 23 } 24 User user=new User(username,"louis",true,true,true,true,auths); 25 return user; 26 }
27 }
这个类中,可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等
5、对于资源的访问权限定义,我们通过实现FilterInvocationSecutiryMetadataSource这个接口来初始化数据
1 import java.util.ArrayList; 2 import java.util.Collection; 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.Map; 6 7 import org.springframework.security.access.ConfigAttribute; 8 import org.springframework.security.access.SecurityConfig; 9 import org.springframework.security.web.FilterInvocation; 10 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 11 import org.springframework.security.web.util.AntUrlPathMatcher; 12 import org.springframework.security.web.util.UrlMatcher; 13 14 import com.sun.org.apache.bcel.internal.generic.NEW; 15 //对于资源的访问权限的定义,通过FilterInvocationSecurityMetadataSource这个接口来初始化数据 16 //假定index.jsp和i.jsp这两个页面需要Role_ADMIN角色的用户才能访问 17 //核心的地方就是提供某个资源对应的权限定义,即getAttributes方法返回的结果 18 //使用AntUrlPathMatcher这个pathmatcher来检查URL是否与资源定义匹配,也可以用正则表达式,或者实现自己在的一个matcher 19 public class MyInvocationSecurityMetadataSource implements 20 FilterInvocationSecurityMetadataSource { 21 /** 22 * 此类在初始化时,应该取得所有资源及其对应角色的定义 23 */ 24 private UrlMatcher urlMatcher=new AntUrlPathMatcher(); 25 private static Map<String,Collection<ConfigAttribute>> resouMap=null; 26 public MyInvocationSecurityMetadataSource(){ 27 loadResourceDefine(); 28 } 29 private void loadResourceDefine() 30 { 31 resouMap=new HashMap<String, Collection<ConfigAttribute>>(); 32 Collection<ConfigAttribute> atts=new ArrayList<ConfigAttribute>(); 33 ConfigAttribute ca=new SecurityConfig("ROLE_ADMIN"); 34 atts.add(ca); 35 resouMap.put("/index.jsp", atts); 36 resouMap.put("/info.jsp", atts); 37 } 38 //According to a URL,find out permission configuration of this URL 39 public Collection<ConfigAttribute> getAttributes(Object object) 40 throws IllegalArgumentException { 41 //guess object is a URL 42 String url=((FilterInvocation)object).getRequestUrl(); 43 Iterator<String> iterator=resouMap.keySet().iterator(); 44 while(iterator.hasNext()) 45 { 46 String resURL=iterator.next(); 47 if(urlMatcher.pathMatchesUrl(resURL,url)){ 48 return resouMap.get(resURL); 49 } 50 } 51 return null; 52 } 53 public Collection<ConfigAttribute> getAllConfigAttributes() { 54 // TODO Auto-generated method stub 55 return null; 56 } 57 58 public boolean supports(Class<?> clazz) { 59 // TODO Auto-generated method stub 60 return true; 61 } 62 63 }
loadResourceDefine方法,假定index.jsp和info.jsp这两个资源需要ROLE_ADMIN角色的用户才能访问。
这个类中,还有一个最核心的地方,就是提供某个资源对应的权限定义,即getAttributes方法返回的结果。
注意:例子中使用的 AntUrlPathMatcher这个path matcher来检查URL是否与资源定义匹配,事实上还要用正则表达式匹配或者自己实现一个matcher
6、最后的决策
1 import java.util.Collection; 2 import java.util.Iterator; 3 4 import org.springframework.security.access.AccessDecisionManager; 5 import org.springframework.security.access.AccessDeniedException; 6 import org.springframework.security.access.ConfigAttribute; 7 import org.springframework.security.access.SecurityConfig; 8 import org.springframework.security.authentication.InsufficientAuthenticationException; 9 import org.springframework.security.core.Authentication; 10 import org.springframework.security.core.GrantedAuthority; 11 //最终的决策 12 //In this method,need to compare authentication with configAttributes. 13 //1、A object is a URL, a filter finds permisssion configuration by this URL,and pass to here 14 //2、Check authentication has attribute in permission configuration(configAttributes) 15 //3、If not match corresponding authentication,throw a AccessDeniedException 16 //这个类中最重要的是decide方法,如果不存在对该资源的定义,直接放行,否则,如果找到正确的角色,即认为拥有权限,否则抛出异常,这样就会进入403页面 17 public class MyAccessDecisionManager implements AccessDecisionManager { 18 19 public void decide(Authentication authentication, Object object, 20 Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, 21 InsufficientAuthenticationException { 22 if(configAttributes==null) 23 { 24 return; 25 } 26 System.out.println(object.toString());//object is a URL 27 Iterator<ConfigAttribute> iterator=configAttributes.iterator(); 28 while(iterator.hasNext()) 29 { 30 ConfigAttribute ca=iterator.next(); 31 String needRoleString=((SecurityConfig)ca).getAttribute(); 32 for(GrantedAuthority ga:authentication.getAuthorities()) 33 { 34 if(needRoleString.equals(ga.getAuthority())) 35 { 36 //ga is user's role 37 return; 38 } 39 } 40 } 41 throw new AccessDeniedException("you have no right"); 42 43 } 44 45 public boolean supports(ConfigAttribute attribute) { 46 // TODO Auto-generated method stub 47 return true; 48 } 49 50 public boolean supports(Class<?> clazz) { 51 // TODO Auto-generated method stub 52 return true; 53 } 54 55 }
这个类中最重要的是decide方法,如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则
throw new AccessDeniedException("you have no right");即会进入403.jsp页面。