spring security使用分类:
如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1、不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo;2、使用数据库,根据spring security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不灵活,而且那个数据库设计得很简陋,实用性差;3、spring security和Acegi不同,它不能修改默认filter了,但支持插入filter,所以根据这个,我们可以插入自己的filter来灵活使用;4、暴力手段,修改源码,前面说的修改默认filter只是修改配置文件以替换filter而已,这种是直接改了里面的源码,但是这种不符合OO设计原则,而且不实际,不可用。
本文面向读者:
因为本文准备介绍第三种方法,所以面向的读者是已经具备了spring security基础知识的。不过不要紧,读者可以先看一下
这个教程,看完应该可以使用第二种方法开发了。
spring security的简单原理:
使用众多的拦截器对url拦截,以此来管理权限。但是这么多拦截器,笔者不可能对其一一来讲,主要讲里面核心流程的两个。
首先,权限管理离不开登陆验证的,所以登陆验证拦截器AuthenticationProcessingFilter要讲;
还有就是对访问的资源管理吧,所以资源管理拦截器AbstractSecurityInterceptor要讲;
但拦截器里面的实现需要一些组件来实现,所以就有了AuthenticationManager、accessDecisionManager等组件来支撑。
? ? 现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
? ? 虽然讲得好像好复杂,读者们可能有点晕,不过不打紧,真正通过代码的讲解在后面,读者可以看完后面的代码实现,再返回看这个简单的原理,可能会有不错的收获。
?
javaEE的入口:web.xml:
- <?xml?version="1.0"?encoding="UTF-8"?>??
- <web-app?version="2.5"?xmlns="http://java.sun.com/xml/ns/javaee"??
- ????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"??
- ????xsi:schemaLocation="http://java.sun.com/xml/ns/javaee?http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">???
- ???????
- ????<context-param>??
- ????????<param-name>contextConfigLocation</param-name>??
- ????????<param-value>?classpath:securityConfig.xml???????????</param-value>??
- ????</context-param>???
- ????????
- ????<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>???
- ?????????
- ????<listener>??
- ????????<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>??
- ????</listener>?????
- ????????
- ????<welcome-file-list>??
- ????????<welcome-file>index.jsp</welcome-file>??
- ????</welcome-file-list>??
- </web-app>??
上面那个配置不用多说了吧
直接上spring security的配置文件securityConfig.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="/login.jsp"?security="none"?/>??
- ????<http?access-denied-page="/accessDenied.jsp">??
- ????????<form-login?login-page="/login.jsp"?/>??
- ??????????
- ??????????
- ??????????
- ??????????
- ????????<session-management>??
- ????????????<concurrency-control?max-sessions="1"??
- ????????????????error-if-maximum-exceeded="false"?/>??
- ????????</session-management>??
- ??????????
- ????????<custom-filter?ref="myFilter"?before="FILTER_SECURITY_INTERCEPTOR"?/>??
- ????</http>??
- ????<!--一个自定义的filter,必须包含?authenticationManager,accessDecisionManager,securityMetadataSource三个属性,???
- ????????我们的所有控制将在这三个类中实现,解释详见具体配置?-->??
- ????<b:bean?id="myFilter"??
- ????????class="com.erdangjiade.spring.security.MyFilterSecurityInterceptor">??
- ????????<b:property?name="authenticationManager"?ref="authenticationManager"?/>??
- ????????<b:property?name="accessDecisionManager"?ref="myAccessDecisionManagerBean"?/>??
- ????????<b:property?name="securityMetadataSource"?ref="securityMetadataSource"?/>??
- ????</b:bean>??
- ??????
- ????<authentication-manager?alias="authenticationManager">??
- ????????<authentication-provider?user-service-ref="myUserDetailService">??
- ??????????????
- ????????</authentication-provider>??
- ????</authentication-manager>??
- ??????
- ????<b:bean?id="myUserDetailService"?class="com.erdangjiade.spring.security.MyUserDetailService"?/>??
- ??????
- ????<b:bean?id="myAccessDecisionManagerBean"??
- ????????class="com.erdangjiade.spring.security.MyAccessDecisionManager">??
- ????</b:bean>??
- ??????
- ????<b:bean?id="securityMetadataSource"??
- ????????class="com.erdangjiade.spring.security.MyInvocationSecurityMetadataSource"?/>???
- ????????????
- ?</b:beans>??
其实所有配置都在<http></http>里面,首先这个版本的spring security不支持了filter=none的配置了,改成了独立的<http?pattern="/login.jsp" security="none"/>,里面你可以配登陆页面、权限不足的返回页面、注销页面等,上面那些配置,我
注销了一些资源和权限的对应关系,笔者这里不需要在这配死它,可以自己写拦截器来获得资源与权限的对应关系。
session-management是用来防止多个用户同时登陆一个账号的。
? ? 最重要的是笔者自己写的拦截器myFilter(终于讲到重点了),首先这个拦截器会加载在FILTER_SECURITY_INTERCEPTOR之前(配置文件上有说),最主要的是这个拦截器里面配了三个处理类,第一个是authenticationManager,这个是处理验证的,这里需要特别说明的是:这个类不单只这个拦截器用到,还有验证拦截器AuthenticationProcessingFilter也用到 了,而且实际上的登陆验证也是AuthenticationProcessingFilter拦截器调用authenticationManager来处理的,我们这个拦截器只是为了拿到验证用户信息而已(这里不太清楚,因为authenticationManager笔者设了断点,用户登陆后再也没调用这个类了,而且调用这个类时不是笔者自己写的那个拦截器调用的,看了spring技术内幕这本书才知道是AuthenticationProcessingFilter拦截器调用的)。
securityMetadataSource这个用来加载资源与权限的全部对应关系的,并提供一个通过资源获取所有权限的方法。
accessDecisionManager这个也称为授权器,通过登录用户的权限信息、资源、获取资源所需的权限来根据不同的授权策略来判断用户是否有权限访问资源。
?
authenticationManager类可以有许多provider(提供者)提供用户验证信息,这里笔者自己写了一个类myUserDetailService来获取用户信息。
MyUserDetailService:
- package?com.erdangjiade.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<GrantedAuthority>?auths=new?ArrayList<GrantedAuthority>();???
- ??????????
- ????????GrantedAuthorityImpl?auth2=new?GrantedAuthorityImpl("ROLE_ADMIN");???
- ????????GrantedAuthorityImpl?auth1=new?GrantedAuthorityImpl("ROLE_USER");???
- ??????????
- ????????if(username.equals("lcy")){???
- ????????????auths=new?ArrayList<GrantedAuthority>();???
- ????????????auths.add(auth1);??
- ????????????auths.add(auth2);????????
- ????????}???????
- ??????????
- ????????User?user?=?new?User(username,?"lcy",?true,?true,?true,?true,?auths);???
- ????????return?user;????
- ????????}???
- ????}???
其中UserDetailsService接口是spring提供的,必须实现的。别看这个类只有一个方法,而且这么简单,其中内涵玄机。
读者看到这里可能就大感疑惑了,不是说好的用数据库吗?对,但别急,等笔者慢慢给你们解析。
首先,笔者为什么不用数据库,还不是为了读者们测试方便,并简化spring security的流程,让读者抓住主线,而不是还要烦其他事(导入数据库,配置数据库,写dao等)。
这里笔者只是用几个数据模拟了从数据库中拿到的数据,也就是说ROLE_ADMIN、ROLE_USER、lcy(第一个是登陆账号)、lcy(第二个是密码)是从数据库拿出来的,这个不难实现吧,如果需要数据库时,读者可以用自己写的dao通过参数username来查询出这个用户的权限信息(或是角色信息,就是那个ROLE_*,对必须是ROLE_开头的,不然spring security不认账的,其实是spring security里面做了一个判断,必须要ROLE_开头,读者可以百度改一下),再返回spring自带的数据模型User即可。
这个写应该比较清晰、灵活吧,总之数据读者们通过什么方法获取都行,只要返回一个User对象就行了。(这也是笔者为什么要重写这个类的原因)
?
? ? 通过MyUserDetailService拿到用户信息后,authenticationManager对比用户的密码(即验证用户),然后这个AuthenticationProcessingFilter拦截器就过咯。
?
下面要说的是另外一个拦截器,就是笔者自己写的拦截器MyFilterSecurityInterceptor:
- package?com.erdangjiade.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;??
- ??????
- ??????
- ????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);????
- ????????????}?????
- ????????}????
- ????public?SecurityMetadataSource?obtainSecurityMetadataSource()?{???
- ????????return?this.securityMetadataSource;?????
- ????????}???
- ????public?void?setSecurityMetadataSource(??
- ????????????FilterInvocationSecurityMetadataSource?newSource)??
- ????{???
- ????????this.securityMetadataSource?=?newSource;???
- ????}???
- ????public?void?destroy()?{????
- ??????????
- ????}?????
- ????public?void?init(FilterConfig?arg0)?throws?ServletException?{????
- ??????????
- ????}????
- }??
继承AbstractSecurityInterceptor、实现Filter是必须的。
首先,登陆后,每次访问资源都会被这个拦截器拦截,会执行doFilter这个方法,这个方法调用了invoke方法,其中fi断点显示是一个url(可能重写了toString方法吧,但是里面还有一些方法的),最重要的是beforeInvocation这个方法,它首先会调用MyInvocationSecurityMetadataSource类的getAttributes方法获取被拦截url所需的权限,在调用MyAccessDecisionManager类decide方法判断用户是否够权限。弄完这一切就会执行下一个拦截器。
?
?
再看一下这个MyInvocationSecurityMetadataSource的实现:
- package?com.erdangjiade.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.erdangjiade.spring.security.tool.AntUrlPathMatcher;??
- import?com.erdangjiade.spring.security.tool.UrlMatcher;??
- ??
- public?class?MyInvocationSecurityMetadataSource?implements?FilterInvocationSecurityMetadataSource?{???
- ????private?UrlMatcher?urlMatcher?=?new?AntUrlPathMatcher();???
- ????private?static?Map<String,?Collection<ConfigAttribute>>?resourceMap?=?null;??
- ??????
- ??????
- ????public?MyInvocationSecurityMetadataSource()?{??
- ????????loadResourceDefine();????
- ????????}?????
- ??????
- ????private?void?loadResourceDefine()?{??
- ????????resourceMap?=?new?HashMap<String,?Collection<ConfigAttribute>>();???
- ????????Collection<ConfigAttribute>?atts?=?new?ArrayList<ConfigAttribute>();???
- ????????ConfigAttribute?ca?=?new?SecurityConfig("ROLE_USER");??
- ????????atts.add(ca);???
- ????????resourceMap.put("/index.jsp",?atts);????
- ????????Collection<ConfigAttribute>?attsno?=new?ArrayList<ConfigAttribute>();??
- ????????ConfigAttribute?cano?=?new?SecurityConfig("ROLE_NO");??
- ????????attsno.add(cano);??
- ????????resourceMap.put("/other.jsp",?attsno);?????
- ????????}????
- ??????
- ??????
- ????public?Collection<ConfigAttribute>?getAttributes(Object?object)?throws?IllegalArgumentException?{???
- ??????????
- ????????String?url?=?((FilterInvocation)object).getRequestUrl();?????
- ????????Iterator<String>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<ConfigAttribute>?getAllConfigAttributes()?{???
- ????????return?null;????
- ????????}??
- ????}??
实现FilterInvocationSecurityMetadataSource接口也是必须的。
首先,这里也是模拟了从数据库中获取信息。
其中loadResourceDefine方法不是必须的,这个只是加载所有的资源与权限的对应关系并缓存起来,避免每次获取权限都访问数据库(提高性能),然后getAttributes根据参数(被拦截url)返回权限集合。
这种缓存的实现其实有一个缺点,因为loadResourceDefine方法是放在构造器上调用的,而这个类的实例化只在web服务器启动时调用一次,那就是说loadResourceDefine方法只会调用一次,如果资源和权限的对应关系在启动后发生了改变,那么缓存起来的就是脏数据,而笔者这里使用的就是缓存数据,那就会授权错误了。但如果资源和权限对应关系是不会改变的,这种方法性能会好很多。
现在说回有数据库的灵活实现,读者看到这,可能会说,这还不简单,和上面MyUserDetailService类一样使用dao灵活获取数据就行啦。
如果读者这样想,那只想到了一半,想一下spring的机制(依赖注入),dao需要依赖注入吧,但这是在启动时候,那个dao可能都还没加载,所以这里需要读者自己写sessionFactory,自己写hql或sql,对,就在loadResourceDefine方法里面写(这个应该会写吧,基础来的)。那如果说想用第二种方法呢(就是允许资源和权限的对应关系改变的那个),那更加简单,根本不需要loadResourceDefine方法了,直接在getAttributes方法里面调用dao(这个是加载完,后来才会调用的,所以可以使用dao),通过被拦截url获取数据库中的所有权限,封装成Collection<ConfigAttribute>返回就行了。(灵活、简单)
注意:接口UrlMatcher和实现类AntUrlPathMatcher是笔者自己写的,这本来是spring以前版本有的,现在没有了,但是觉得好用就用会来了,直接上代码(读者也可以自己写正则表达式验证被拦截url和缓存或数据库的url是否匹配):
- package?com.erdangjiade.spring.security.tool;??
- ??
- public?interface?UrlMatcher{??
- ????Object?compile(String?paramString);??
- ????boolean?pathMatchesUrl(Object?paramObject,?String?paramString);??
- ????String?getUniversalMatchPattern();???
- ????boolean?requiresLowerCaseUrl();??
- }??
- package?com.erdangjiade.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?+?"‘]";????
- ??????}??
- ??}??
?
?
然后MyAccessDecisionManager类的实现:
- package?com.erdangjiade.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?{??
- ??????
- ??????
- ??????
- ??????
- ??????
- ????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();??
- ????????????for(GrantedAuthority?ga?:?authentication.getAuthorities()){???
- ????????????????if(needRole.equals(ga.getAuthority())){????
- ??????????????????????
- ????????????????????return;????????????????
- ????????}??????????????
- ????}????????
- }???
- ??????????
- ????????throw?new?AccessDeniedException("no?right");?????
- }?????
- ????public?boolean?supports(ConfigAttribute?attribute)?{???
- ????????return?true;??
- ????}????
- ????public?boolean?supports(Class<?>clazz)?{??
- ????????return?true;???
- ????????}???
- ????}??
接口AccessDecisionManager也是必须实现的。
decide方法里面写的就是授权策略了,笔者的实现是,没有明说需要权限的(即没有对应的权限的资源),可以访问,用户具有其中一个或多个以上的权限的可以访问。这个就看需求了,需要什么策略,读者可以自己写其中的策略逻辑。通过就返回,不通过抛异常就行了,spring security会自动跳到权限不足页面(配置文件上配的)。
?
就这样,整个流程过了一遍。
?
?
剩下的页面代码
本来想给这个demo的源码出来的,但是笔者觉得,通过这个教程一步一步读下来,并自己敲一遍代码,会比直接运行一遍demo印象更深刻,并且更容易理解里面的原理。
而且我的源码其实都公布出来了:
login.jsp:
- <%@page?language="java"?import="java.util.*"?pageEncoding="UTF-8"%>??
- <!DOCTYPEhtmlPUBLIC"-//W3C//DTD?HTML?4.01?Transitional//EN">??
- <html>??
- <head>??
- <title>登录</title>??
- </head>??
- <body>??
- ????<form?action?="j_spring_security_check"?method="POST">??
- ????<table>??
- ????????<tr>??
- ????????????<td>用户:</td>??
- ????????????<td><input?type?=‘text‘?name=‘j_username‘></td>??
- ????????</tr>??
- ????????<tr>??
- ????????????<td>密码:</td>??
- ????????????<td><input?type?=‘password‘?name=‘j_password‘></td>??
- ????????</tr>??
- ????????<tr>??
- ????????????<td><input?name?="reset"?type="reset"></td>??
- ????????????<td><input?name?="submit"?type="submit"></td>??
- ????????</tr>??
- ????</table>??
- ????</form>??
- </body>??
- </html>??
index.jsp:
- <%@page?language="java"?import="java.util.*"?pageEncoding="UTF-8"%>???
- <%@taglib?prefix="sec"?uri="http://www.springframework.org/security/tags"%>???
- <!DOCTYPEHTMLPUBLIC"-//W3C//DTD?HTML?4.01?Transitional//EN">??
- ???
- <html>??
- ???
- <head>??
- ???
- <title>My?JSP?‘index.jsp‘?starting?page</title>???
- </head>??
- ???
- <body>??
- ??????<h3>这是首页</h3>欢迎??
- ????<sec:authentication?property?="name"/>?!??
- ??????
- ????<br>???
- ????<a?href="admin.jsp">进入admin页面</a>???
- ????<a?href="other.jsp">进入其它页面</a>???
- </body>??
- ???
- ??
- </html>??
- ???
admin.jsp:
- <%@page?language="java"?import="java.util.*"?pageEncoding="utf-8"%>??
- <!DOCTYPEHTMLPUBLIC"-//W3C//DTD?HTML?4.01?Transitional//EN">??
- <html>??
- <head>??
- <title>My?JSP?‘admin.jsp‘?starting?page</title>??
- </head>??
- <body>??
- ????欢迎来到管理员页面.??
- ????<br>??
- </body>??
- </html>??
accessDenied.jsp:
- <%@page?language="java"?import="java.util.*"?pageEncoding="utf-8"%>??
- <!DOCTYPEHTMLPUBLIC"-//W3C//DTD?HTML?4.01?Transitional//EN">??
- <html>??
- <head>??
- <title>My?JSP?‘admin.jsp‘?starting?page</title>??
- </head>??
- <body>??
- ????欢迎来到管理员页面.??
- ????<br>??
- </body>??
- </html>??
other.jsp:
- <%@?page?language="java"?import="java.util.*"?pageEncoding="UTF-8"%>??
- <%??
- String?path?=?request.getContextPath();??
- String?basePath?=?request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";??
- %>??
- ??
- <!DOCTYPE?HTML?PUBLIC?"-//W3C//DTD?HTML?4.01?Transitional//EN">??
- <html>??
- ??<head>??
- ????<base?href="<%=basePath%>">??
- ??????
- ????<title>My?JSP?‘other.jsp‘?starting?page</title>??
- ??????
- ????<meta?http-equiv="pragma"?content="no-cache">??
- ????<meta?http-equiv="cache-control"?content="no-cache">??
- ????<meta?http-equiv="expires"?content="0">??????
- ????<meta?http-equiv="keywords"?content="keyword1,keyword2,keyword3">??
- ????<meta?http-equiv="description"?content="This?is?my?page">??
- ?????
- ?
- ??
- ??
- ??</head>??
- ????
- ??<body>??
- ????<h3>这里是Other页面</h3>??
- ??</body>??
- </html>??
?
?
项目图:
?
最后的话:
虽然笔者没给读者们demo,但是所有源码和jar包都在这个教程里面,为什么不直接给?笔者的目的是让读者跟着教程敲一遍代码,使印象深刻(相信做这行的都知道,同样一段代码,看过和敲过的区别是多么的大),所以不惜如此来强迫大家了。
由于笔者有经常上csdn博客的习惯,所以读者有什么不懂的(或者指教的),笔者尽力解答。
?
?
补充:
第一点:
? ??MyUserDetailService这个类负责的是只是获取登陆用户的详细信息(包括密码、角色等),不负责和前端传过来的密码对比,只需返回User对象,后会有其他类根据User对象对比密码的正确性(框架帮我们做)。
?
第二点:
? ? 记得MyInvocationSecurityMetadataSource这个类是负责的是获取角色与url资源的所有对应关系,并根据url查询对应的所有角色。
? ? 今天为一个项目搭安全架构时,第一,发现上面MyInvocationSecurityMetadataSource这个类的代码有个bug:
? ? ? ? ? ? 上面的代码中,将所有的对应关系缓存到resourceMap,key是url,value是这个url对应所有角色。?
? ? ? ? ? ? getAttributes方法中,只要匹配到一个url就返回这个url对应所有角色,不再匹配后面的url,问题来了,当url有交集时,就有可能漏掉一些角色了:如有两个 url ,第一个是 /** ,第二个是 /role1/index.jsp ,第一个当然需要很高的权限了(因为能匹配所有 url ,即可以访问所有 url ),假设它需要的角色是 ROLE_ADMIN (不是一般人拥有的),第二个所需的角色是 ROLE_1 。 ? ?当我用 ROLE_1 这个角色访问 /role1/index.jsp 时,在getAttributes方法中,当先迭代了?/** 这个url,它就能匹配 /role1/index.jsp 这个url,并直接返回 /** 这个url对应的所有角色(在这,也就ROLE_ADMIN)给MyAccessDecisionManager这个投票类, ?MyAccessDecisionManager这个类中再对比 用户的角色?ROLE_1 ,就会发现不匹配。 ? ?最后,明明可以有权访问的 url ,却不能访问了。
??
??
? 第二,之前不是说缓存所有对应关系,需要读者自己写sessionFactory(因为在实例化这个类时,配置的sessionFactory可能还没实例化或dao还没加载好),既然这样,那笔者可以不在构造方法中加载对应关系,可以在第一次调用getAttributes方法时再加载(用静态变量缓存起来,第二次就不用再加载了, ? ?
?注:其实这样不是很严谨,不过笔者这里的对应关系是不变的,单例性不需很强,更严谨的请参考笔者另一篇博文
设计模式之单件模式)。
?
? ? 修改过的MyInvocationSecurityMetadataSource类:
- package?com.lcy.bookcrossing.springSecurity;??
- ??
- import?java.util.ArrayList;??
- import?java.util.Collection;??
- import?java.util.HashMap;??
- import?java.util.HashSet;??
- import?java.util.Iterator;??
- import?java.util.List;??
- import?java.util.Map;??
- import?java.util.Set;??
- ??
- 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?com.lcy.bookcrossing.bean.RoleUrlResource;??
- import?com.lcy.bookcrossing.dao.IRoleUrlResourceDao;??
- import?com.lcy.bookcrossing.springSecurity.tool.AntUrlPathMatcher;??
- import?com.lcy.bookcrossing.springSecurity.tool.UrlMatcher;??
- ??
- public?class?MyInvocationSecurityMetadataSource?implements?FilterInvocationSecurityMetadataSource?{???
- ????private?UrlMatcher?urlMatcher?=?new?AntUrlPathMatcher();???
- ??
- ??????
- ??????
- ????private?static?List<RoleUrlResource>?rus?=?null;??
- ??????
- ????@Resource??
- ????private?IRoleUrlResourceDao?roleUrlDao;??
- ??????
- ??????
- ????public?MyInvocationSecurityMetadataSource()?{??
- ??
- ????????}?????
- ??????
- ?????
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ??
- ??????
- ??????
- ????public?Collection<ConfigAttribute>?getAttributes(Object?object)?throws?IllegalArgumentException?{???
- ??????????
- ????????String?url?=?((FilterInvocation)object).getRequestUrl();?????
- ??????????
- ??????????
- ????????if(rus?==?null){??
- ????????rus?=?roleUrlDao.findAll();??
- ????????}??
- ??????????
- ??????????
- ????????Set<String>?roles?=?new?HashSet<String>();??
- ????????for(RoleUrlResource?ru?:?rus){??
- ????????????if?(urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(),?url))?{???
- ????????????????????????roles.add(ru.getRole().getRoleName());??
- ????????????????}???????
- ????????}??
- ????????Collection<ConfigAttribute>?cas?=?new?ArrayList<ConfigAttribute>();???
- ????????for(String?role?:?roles){??
- ????????????ConfigAttribute?ca?=?new?SecurityConfig(role);??
- ????????????cas.add(ca);???
- ????????}??
- ????????return?cas;??
- ??????????
- ?????????
- ?
- ?
- ?
- ?
- ?
- ?
- ??
- ????????}????
- ????public?boolean?supports(Class<?>clazz)?{???
- ????????????return?true;????
- ????????????}???
- ????public?Collection<ConfigAttribute>?getAllConfigAttributes()?{???
- ????????return?null;????
- ????????}??
- ????}??
? ? 以上代码,在getAttributes方法中缓存起所有的对应关系(可以
使用依赖注入了),并匹配
所有?url ,对角色进行去重(因为多个url可能有重复的角色),这样就能修复那个bug了。
?
?
?
?
?
浅谈springSecurity
原文:http://zhaoxijun.iteye.com/blog/2261757