【Spring】12、Spring Security 四种使用方式
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所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。 虽然讲得好像好复杂,读者们可能有点晕,不过不打紧,真正通过代码的讲解在后面,读者可以看完后面的代码实现,再返回看这个简单的原理,可能会有不错的收获。
spring security使用实现(基于spring security3.1.4):
javaEE的入口:web.xml:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<!--?xml"1.0""UTF-8"?--><web-app"2.5""http://java.sun.com/xml/ns/javaee""http://www.w3.org/2001/XMLSchema-instance""http://java.sun.com/xml/ns/javaee> <!--加载Spring <context-param> <param-name>contextConfigLocation</param-name> <param-value> </context-param> <!--1的过滤器链配置 <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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<!--?xml"1.0""UTF-8"?--><b:beans"http://www.springframework.org/schema/security""http://www.springframework.org/schema/beans""http://www.w3.org/2001/XMLSchema-instance"3.0.xsd http://www.springframework.org/schema/security <!--登录页面不过滤 <http"/login.jsp""none"> <http"/accessDenied.jsp"> <form-login"/login.jsp"> <!--访问/http://blog.csdn.net/u012367513/article/details/admin.jsp资源的用户必须具有ROLE_ADMIN的权限 <!--"/http://blog.csdn.net/u012367513/article/details/admin.jsp""ROLE_ADMIN" <!--访问/**资源的用户必须具有ROLE_USER的权限 <!--"/**""ROLE_USER" <session-management> <concurrency-control"1"if-maximum-exceeded="false"> </concurrency-control></session-management> <!--增加一个filter,这点与 <custom-filter"myFilter""FILTER_SECURITY_INTERCEPTOR"> </custom-filter></form-login></http> <!--一个自定义的filter,必须包含 我们的所有控制将在这三个类中实现,解释详见具体配置 <b:bean"myFilter"="com.erdangjiade.spring.security.MyFilterSecurityInterceptor"> <b:property"authenticationManager""authenticationManager"> <b:property"accessDecisionManager""myAccessDecisionManagerBean"> <b:property"securityMetadataSource""securityMetadataSource"> </b:property></b:property></b:property></b:bean> <!--验证配置,认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 <!--如果用户的密码采用加密的话"md5" </authentication-provider> </authentication-manager> <!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 <b:bean"myUserDetailService"="com.erdangjiade.spring.security.MyUserDetailService"> <!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 <b:bean"myAccessDecisionManagerBean"="com.erdangjiade.spring.security.MyAccessDecisionManager"> </b:bean> <!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 <b:bean"securityMetadataSource"="com.erdangjiade.spring.security.MyInvocationSecurityMetadataSource"> </b:bean></b:bean></http></b:beans> |
其实所有配置都在里面,首先这个版本的spring security不支持了filter=none的配置了,改成了独立的,里面你可以配登陆页面、权限不足的返回页面、注销页面等,上面那些配置,我注销了一些资源和权限的对应关系,笔者这里不需要在这配死它,可以自己写拦截器来获得资源与权限的对应关系。 session-management是用来防止多个用户同时登陆一个账号的。
最重要的是笔者自己写的拦截器myFilter(终于讲到重点了),首先这个拦截器会加载在FILTER_SECURITY_INTERCEPTOR之前(配置文件上有说),最主要的是这个拦截器里面配了三个处理类,第一个是authenticationManager,这个是处理验证的,这里需要特别说明的是:这个类不单只这个拦截器用到,还有验证拦截器AuthenticationProcessingFilter也用到 了,而且实际上的登陆验证也是AuthenticationProcessingFilter拦截器调用authenticationManager来处理的,我们这个拦截器只是为了拿到验证用户信息而已(这里不太清楚,因为authenticationManager笔者设了断点,用户登陆后再也没调用这个类了,而且调用这个类时不是笔者自己写的那个拦截器调用的,看了spring技术内幕这本书才知道是AuthenticationProcessingFilter拦截器调用的)。 securityMetadataSource这个用来加载资源与权限的全部对应关系的,并提供一个通过资源获取所有权限的方法。
accessDecisionManager这个也称为授权器,通过登录用户的权限信息、资源、获取资源所需的权限来根据不同的授权策略来判断用户是否有权限访问资源。
authenticationManager类可以有许多provider(提供者)提供用户验证信息,这里笔者自己写了一个类myUserDetailService来获取用户信息。
MyUserDetailService:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
packageimportimportimportimportimportimportimportimportimportpublic implements //登陆验证时,通过username获取用户的所有权限信息, //并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用 public throws Collection<grantedauthority>new GrantedAuthorityImplnew"ROLE_ADMIN"); GrantedAuthorityImplnew"ROLE_USER"); if(username.equals("lcy")){ auths=new auths.add(auth1); auths.add(auth2); } Usernew"lcy",true,true,true,true, return } } |
其中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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
packageimportimportimportimportimportimportimportimportimportimportimportimportpublic extendsimplements //配置文件注入 private //登陆后,每次访问资源都通过这个拦截器拦截 public throws FilterInvocationnew invoke(fi); } public return.securityMetadataSource; } publicextends returnclass; } public throws //fi里面有一个被拦截的url //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够 InterceptorStatusTokensuper.beforeInvocation(fi); try //执行下一个拦截器 fi.getChain().doFilter(fi.getRequest(), }finally super.afterInvocation(token,null); } } public return.securityMetadataSource; } public FilterInvocationSecurityMetadataSource { this.securityMetadataSource } public } public throws } } |
继承AbstractSecurityInterceptor、实现Filter是必须的。 首先,登陆后,每次访问资源都会被这个拦截器拦截,会执行doFilter这个方法,这个方法调用了invoke方法,其中fi断点显示是一个url(可能重写了toString方法吧,但是里面还有一些方法的),最重要的是beforeInvocation这个方法,它首先会调用MyInvocationSecurityMetadataSource类的getAttributes方法获取被拦截url所需的权限,在调用MyAccessDecisionManager类decide方法判断用户是否够权限。弄完这一切就会执行下一个拦截器。
再看一下这个MyInvocationSecurityMetadataSource的实现:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
packageimportimportimportimportimportimportimportimportimportimportimportpublic implements privatenew private "">>null; //tomcat启动时实例化一次 public loadResourceDefine(); } //tomcat开启时加载一次,加载所有url和权限(或角色)的对应关系 private resourceMapnew"">>(); Collection<configattribute>new ConfigAttributenew"ROLE_USER"); atts.add(ca); resourceMap.put("/index.jsp", Collection<configattribute>new ConfigAttributenew"ROLE_NO"); attsno.add(cano); } //参数是要访问的url,返回这个url对于的所有权限(或角色) publicthrows // String Iterator<string>ite while String if return } } return; } public return; } public return; } }</configattribute></string></configattribute></configattribute></configattribute></configattribute></configattribute></string,></string,> |
实现FilterInvocationSecurityMetadataSource接口也是必须的。 首先,这里也是模拟了从数据库中获取信息。 其中loadResourceDefine方法不是必须的,这个只是加载所有的资源与权限的对应关系并缓存起来,避免每次获取权限都访问数据库(提高性能),然后getAttributes根据参数(被拦截url)返回权限集合。 这种缓存的实现其实有一个缺点,因为loadResourceDefine方法是放在构造器上调用的,而这个类的实例化只在web服务器启动时调用一次,那就是说loadResourceDefine方法只会调用一次,如果资源和权限的对应关系在启动后发生了改变,那么缓存起来的就是脏数据,而笔者这里使用的就是缓存数据,那就会授权错误了。但如果资源和权限对应关系是不会改变的,这种方法性能会好很多。 现在说回有数据库的灵活实现,读者看到这,可能会说,这还不简单,和上面MyUserDetailService类一样使用dao灵活获取数据就行啦。 如果读者这样想,那只想到了一半,想一下spring的机制(依赖注入),dao需要依赖注入吧,但这是在启动时候,那个dao可能都还没加载,所以这里需要读者自己写sessionFactory,自己写hql或sql,对,就在loadResourceDefine方法里面写(这个应该会写吧,基础来的)。那如果说想用第二种方法呢(就是允许资源和权限的对应关系改变的那个),那更加简单,根本不需要loadResourceDefine方法了,直接在getAttributes方法里面调用dao(这个是加载完,后来才会调用的,所以可以使用dao),通过被拦截url获取数据库中的所有权限,封装成Collection返回就行了。(灵活、简单) 注意:接口UrlMatcher和实现类AntUrlPathMatcher是笔者自己写的,这本来是spring以前版本有的,现在没有了,但是觉得好用就用会来了,直接上代码(读者也可以自己写正则表达式验证被拦截url和缓存或数据库的url是否匹配):
|
1
2
3
4
5
6
7
8
|
packagepublic Object boolean String boolean} |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
packageimportimport public implements private private public this(true); } publicboolean { this.requiresLowerCaseUrltrue; this.pathMatchernew this.requiresLowerCaseUrl } public ifthis.requiresLowerCaseUrl) return } return } public boolean this.requiresLowerCaseUrl } public if"/**".equals(path))"**".equals(path))) return; } return.pathMatcher.match((String)path, } public return"/**"; } public return.requiresLowerCaseUrl; } public return.getClass().getName()"[requiresLowerCase='" +this.requiresLowerCaseUrl"']"; } } |
然后MyAccessDecisionManager类的实现:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
packageimportimportimportimportimportimportimportimportimportpublic implements //检查用户是否够权限访问资源 //参数authentication是从spring的全局缓存SecurityContextHolder中拿到的,里面是用户的权限信息 //参数object是url //参数configAttributes所需的权限 public Collection<configattribute> throws if(configAttributesnull){ return; } Iterator<configattribute> while(ite.hasNext()){ ConfigAttribute String for(GrantedAuthority if(needRole.equals(ga.getAuthority())){ return; } } } //注意:执行这里,后台是会抛异常的,但是界面会跳转到所配的access-denied-page页面 throw "no); } public return; } public return; } }</configattribute></configattribute> |
接口AccessDecisionManager也是必须实现的。 decide方法里面写的就是授权策略了,笔者的实现是,没有明说需要权限的(即没有对应的权限的资源),可以访问,用户具有其中一个或多个以上的权限的可以访问。这个就看需求了,需要什么策略,读者可以自己写其中的策略逻辑。通过就返回,不通过抛异常就行了,spring security会自动跳到权限不足页面(配置文件上配的)。
就这样,整个流程过了一遍。
剩下的页面代码
本来想给这个demo的源码出来的,但是笔者觉得,通过这个教程一步一步读下来,并自己敲一遍代码,会比直接运行一遍demo印象更深刻,并且更容易理解里面的原理。 而且我的源码其实都公布出来了: login.jsp:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<%@page"java"="java.util.*""UTF-8"%><title>登录</title> <form"j_spring_security_check""POST"> <table> <tbody><tr> <td>用户:</td> <td><input"'text'""'j_username'"></td> </tr> <tr> <td>密码:</td> <td><input"'password'""'j_password'"></td> </tr> <tr> <td><input"reset""reset"></td> <td><input"submit""submit"></td> </tr> </tbody></table> </form> |
index.jsp:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<%@page"java"="java.util.*""UTF-8"%> <title>My JSP 'index.jsp' starting page</title> <h3>这是首页</h3>欢迎 <sec:authentication"name"> <br> 进入admin页面 进入其它页面 </sec:authentication> |
http://blog.csdn.net/u012367513/article/details/admin.jsp:
|
1
2
3
4
5
6
7
8
9
|
<%@page"java"="java.util.*""utf-8"%> 欢迎来到管理员页面. <br> |
accessDenied.jsp:
|
1
2
3
4
5
6
7
8
9
|
<%@page"java"="java.util.*""utf-8"%> 欢迎来到管理员页面. <br> |
http://blog.csdn.net/u012367513/article/details/other.jsp:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<%@"java"="java.util.*""UTF-8"%><%StringString"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%> <base"<%=basePath%>"> <meta"pragma""no-cache"> <meta"cache-control""no-cache"> <meta"expires""0"> <meta"keywords""keyword1,keyword2,keyword3"> <meta"description""This> <!-- <link"stylesheet""text/css""styles.css"> --> <h3>这里是Other页面</h3> |
项目图:
<img src="http://www.2cto.com/uploadfile/Collfiles/20140829/20140829091240286.png" alt="n峨n竩�漽j喎�" http:="" www.2cto.com="" ym"="" target="_blank" class="keylink" style="border-width: 0px; padding: 0px; margin: 0px; list-style: none; width: 322px; height: 464px;">源码和jar包都在这个教程里面,为什么不直接给?笔者的目的是让读者跟着教程敲一遍代码,使印象深刻(相信做这行的都知道,同样一段代码,看过和敲过的区别是多么的大),所以不惜如此来强迫大家了。
转自:http://blog.csdn.net/u013516966/article/details/46688765
【Spring】12、Spring Security 四种使用方式的更多相关文章
- spring security四种实现方式
spring security四种实现方式 spring(20) > 目录(?)[+] 最简单配置spring-securityxml实现1 实现UserDetailsService 实现动态过 ...
- Spring中bean的四种注入方式
一.前言 最近在复习Spring的相关内容,这篇博客就来记录一下Spring为bean的属性注入值的四种方式.这篇博客主要讲解在xml文件中,如何为bean的属性注入值,最后也会简单提一下使用注解 ...
- Spring Data Jpa的四种查询方式
一.调用接口的方式 1.基本介绍 通过调用接口里的方法查询,需要我们自定义的接口继承Spring Data Jpa规定的接口 public interface UserDao extends JpaR ...
- Spring事务管理的四种方式(以银行转账为例)
Spring事务管理的四种方式(以银行转账为例) 一.事务的作用 将若干的数据库操作作为一个整体控制,一起成功或一起失败. 原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不 ...
- 读书笔记——spring cloud 中 HystrixCommand的四种执行方式简述
读了<Spring Cloud 微服务实战>第151-154页, 总结如下: Hystrix存在两种Command,一种是HystrixCommand,另一种是HystrixObserva ...
- 【spring springmvc】这里有你想要的SpringMVC的REST风格的四种请求方式
概述 之前的文章springmvc使用注解声明控制器与请求映射有简单提到过控制器与请求映射,这一次就详细讲解一下SpringMVC的REST风格的四种请求方式及其使用方法. 你能get的知识点 1.什 ...
- JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解
在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...
- spring MVC 拦截有几种实现方式
spring MVC 拦截有几种实现方式 实现HandelInterceptor接口方式 继承HandelInterceptor 的方式.一般有这两种方式 spring 如何走单元测式 ...
- Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (三) —— SharePreferences
除了SQLite数据库外,SharedPreferences也是一种轻型的数据存储方式,它的本质是基于XML文件存储key-value键值对数据,通常用来存储一些简单的配置信息.其存储位置在/data ...
随机推荐
- PHP 文件处理----fopen(),fclose(),feof(),fgets(),fgetc()
fopen() 函数用于在 PHP 中打开文件. 打开文件 fopen() 函数用于在 PHP 中打开文件. 此函数的第一个参数含有要打开的文件的名称,第二个参数规定了使用哪种模式来打开文件: < ...
- POJ3422费用流
Description: 一个N * N的奖赏地图,你可以走k次这个地图,但是每一次你走过一个有分的节点,你获得得分,但这个节点的得分都要清零,问你走k次地图的最大得分 Solution: 把得分变成 ...
- Swift学习目录
本学习基于苹果官方Swift学习材料,保留了原版90%左右的内容(一些项目开发中基本不用的知识点没有整理),并根据理解进行整理.如对原版感兴趣,可以直接单击链接阅读和学习. 第一部分 基础篇 1.基本 ...
- 五花八门的CSS
一.颜色 rgba(0, 0, 0, 0.5) rgba括号中前3个数字代表着 red green blue三种颜色的rgb值,0-255,最后一个是设定这个颜色的透明度即alpha值.范围从0到1, ...
- Android精通:View与ViewGroup,LinearLayout线性布局,RelativeLayout相对布局,ListView列表组件
UI的描述 对于Android应用程序中,所有用户界面元素都是由View和ViewGroup对象构建的.View是绘制在屏幕上能与用户进行交互的一个对象.而对于ViewGroup来说,则是一个用于存放 ...
- docker系统学习之docker界面管理
docker可视化界面 dockerUI已废弃,转投Portainer项目 Portainer,轻量级管理界面,基本满足中小单位需求 官方Github https://github.com/porta ...
- 【Spark工作原理】Spark任务调度理解
Spark内部有若干术语(Executor.Job.Stage.Task.Driver.DAG等),需要理解并搞清其内部关系,因为这是性能调优的基石. 节点类型有: 1. Master 节点: 常 ...
- Spring boot MultipartResolver
[参考文章]:Required MultipartFile parameter 'file' is not present [参考文章]:Springboot2.0中WebMvcConfigurerA ...
- js设计模式小结
1 构造函数模式 var Person = function(name){ this.name = name; this.getName = function(){ console.log(this. ...
- SQL求出优秀、及格人数
首先看看班级的表的数据: 接下来,由于班级有分linux .Mysql.Java三门科目,因此,先求Linux科目的及格人数.不及格人数和优秀人数 做一个语句的分解: 1.首先查出每个班级的班级ID ...