说了这么些,那我们到底应该如何来实现这个ObjectDefinitionSource接口呢?
首先还是说说Acegi Seucrity 1.x 版本,org.acegisecurity.intercept.web 和org.acegisecurity.intercept.method 包下AbstractFilterInvocationDefinitionSource 和AbstractMethodDefinitionSource 两个抽象类,这两个类分别实现了FilterInvocationDefinitionSource 和MethodDefinitionSource 接口,而这两个接口都继承自ObjectDefinitionSource接口并实现了其中的方法。两个抽象类都使用方法模板模式来实现,将具体的实现方法交给了子类。
提示: 两个抽象类实现了各自接口的 getAttributes(Object object) 方法并在此方法中调用lookupAttributes (Method method) 方法,而实际该方法在抽象类中并没有具体的实现,而是留给了子类去实现。 |
在Acegi Seucrity 1.x版本中,系统为我们提供了默认的实现,MethodDefinitionMap 类用于返回方法的权限信息,而PathBasedFilterInvocationDefinitionMap 类和RegExpBasedFilterInvocationDefinitionMap 类用于返回URL资源对应的权限信息,也就是ConfigAttributeDefinition对象,现在也许明白一点儿了吧,我们只要按照这三个类的实现方式(也就是”模仿”,从后面的代码中你可以看到)从数据库中获取用户信息和权限信息然后封装成一个ConfigAttributeDefinition 对象返回即可(其实就是一个List列表,前面已经介绍过了),相信通过Hibernate从数据库中获取一个列表应该是再容易不过的了。
回到Spring Security ,系统为我们提供的默认实现有些变化,DefaultFilterInvocationDefinitionSource 和DelegatingMethodDefinitionSource 两个类,从名字也可以看出来它们分别是干什么的了。这两个类分别实现了FilterInvocationDefinitionSource 和MethodDefinitionSource 接口,而这两个接口都继承自ObjectDefinitionSource接口并实现了其中的方法,这和1.x版本中一样。它们都是从配置文件中得到资源和相应权限的信息。
通过上面的介绍,你或许更名白了一些,那我们下面要做的就是实现系统的FilterInvocationDefinitionSource 和MethodDefinitionSource 接口,只是数据源不是从配置文件中读取配置信息是数据库而已。
我们这里对比着 Acegi Seucrity 1.x 版本中的实现,我个人认为它更好理解,还是请你好好看看源代码。
1 自定义FilterInvocationDefinitionSource
在2.0中,系统没有在系统抽象类,所以我们还是使用1.x中的实现方式,首先通过一个抽象类来实现 ObjectDefinitionSource 接口。代码如下:
2
3 throws IllegalArgumentException {
4
5 if (object == null || ! ( this .supports(object.getClass()))) {
6
7 thrownew IllegalArgumentException( " Object must be a FilterInvocation " );
8
9 }
10
11 String url = ((FilterInvocation)object).getRequestUrl();
12
13 returnthis.lookupAttributes(url);
14
15 }
16
17 publicabstract ConfigAttributeDefinition lookupAttributes(String url);
18
19 @SuppressWarnings( " unchecked " )
20
21 publicabstract Collection getConfigAttributeDefinitions();
22
23 @SuppressWarnings( " unchecked " )
24
25 publicboolean supports(Class clazz) {
26
27 return FilterInvocation. class .isAssignableFrom(clazz);
28
29 }
这段代码你也可以在1.0中找到,getAttributes 方法的入口参数是一个Object对象,这是由系统传给我们的,因为是URL资源的请求,所有可以将这个Object对象强制转换为 FilterInvocation 对象,并通过调用它的getRequestUrl() 方法来获取用户当前请求的URL地址,然后调用子类需要实现的lookupAttributes 方法并将该URL地址作为参数传给该方法,下面是具体的实现类DataBaseFilterInvocationDefinitionSource 类的代码,也就是我们需要实现抽象父类的lookupAttributes方法:
2
3 public ConfigAttributeDefinition lookupAttributes(String url) {
4
5 // TODO Auto-generated method stub
6
7 // 初始化数据,从数据库读取
8
9 cacheManager.initResourceInCache();
10
11 if (isUseAntPath()) {
12
13 int firstQuestionMarkIndex = url.lastIndexOf( " ? " );
14
15 if (firstQuestionMarkIndex != - 1 ) {
16
17 url = url.substring( 0 , firstQuestionMarkIndex);
18
19 }
20
21 }
22
23 // 将URL在比较前都转换为小写
24
25 if (isConvertUrlToLowercaseBeforeComprison()) {
26
27 url = url.toLowerCase();
28
29 }
30
31 // 获取所有的URL
32
33 List < String > urls = cacheManager.getUrlResources();
34
35 // 倒叙排序--如果不进行排序,如果用户使用浏览器的导航工具访问页面可能出现问题
36
37 // 例如:访问被拒绝后用户刷新页面
38
39 Collections.sort(urls);
40
41 Collections.reverse(urls);
42
43 GrantedAuthority[] authorities = new GrantedAuthority[ 0 ];
44
45 // 将请求的URL与配置的URL资源进行匹配,并将正确匹配的URL资源对应的权限
46
47 // 取出
48
49 for (String resourceName_url : urls) {
50
51 boolean matched = false ;
52
53 // 使用ant匹配URL
54
55 if (isUseAntPath()) {
56
57 matched = pathMatcher.match(resourceName_url, url);
58
59 } else { // perl5编译URL
60
61 Pattern compliedPattern = null ;
62
63 Perl5Compiler compiler = new Perl5Compiler();
64
65 try {
66
67 compliedPattern = compiler.compile(resourceName_url, Perl5Compiler.READ_ONLY_MASK);
68
69 } catch (MalformedPatternException e) {
70
71 e.printStackTrace();
72
73 }
74
75 matched = matcher.matches(url, compliedPattern);
76
77 }
78
79 // 匹配正确,获取响应权限
80
81 if (matched) {
82
83 // 获取正确匹配URL资源对应的权限
84
85 ResourcDetail detail = cacheManager.getResourcDetailFromCache(resourceName_url);
86
87 authorities = detail.getAuthorities();
88
89 break ;
90
91 }
92
93 }
94
95 // 将权限封装成ConfigAttributeDefinition对象返回(使用ConfigAttributeEditor)
96
97 if (authorities.length > 0 ) {
98
99 String authTemp = "" ;
100
101 for (GrantedAuthority grantedAuthority : authorities) {
102
103 authTemp += grantedAuthority.getAuthority() + " , " ;
104
105 }
106
107 String authority = authTemp.substring( 0 , (authTemp.length() - 1 ));
108
109 System.out.println(authority);
110
111 ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();
112
113 attributeEditor.setAsText(authority.trim());
114
115 return (ConfigAttributeDefinition)attributeEditor.getValue();
116
117 }
118
119 returnnull;
120
121 }
我们这里同样使用了缓存,它参考自系统的UseCache接口的实现,这里不在介绍,你可以查看本例的源代码和系统的实现和本例的配置文件。这里将用户请求的URL地址与从数据库中获取的受保护的URL资源使用 ant 和perl5匹配(这取决与你的配置),如果匹配成功则从缓存中获取访问该资源需要的权限信息,并将其封装成ConfigAttributeDefinition 对象返回,这里使用org.springframework.security.ConfigAttributeEditor 类,该类提供了一个setAsText(String s) ,该方法收取一个字符串作为参数,在该方法中创建ConfigAttributeDefinition 对象并将字符串参数传递给ConfigAttributeDefinition类的构造函数 来初始化该对象。详细的实现还是请你看源代码。现在我们在配置文件添加自己的实现,如下:
2 class ="org.security.intercept.web.DataBaseFilterInvocationDefinitionSource"
3 p:convertUrlToLowercaseBeforeComprison ="true"
4 p:useAntPath ="true"
5 p:cacheManager-ref ="securityCacheManager" />
convertUrlToLowercaseBeforeComprison 属性 定义了在匹配之前将URL都转换为小写,useAntPath属性 定义使用Ant方式匹配URL,cacheManager 属性定义了指向另一个Bean的引用,我们使用它从缓存中获取相应的信息。
2 自定义 MethodDefinitionSource
将方法资源存放在数据库中的实现与 URL资源类似,这里不在累述,下面是 DataBaseMethodInvocationDefinitionSource 的 源代码,读者可以参考注释进行阅读(该类也是继承自一个自定义的抽象类 AbstractMethodDefinitionSource ):
2
3 // TODO Auto-generated method stub
4
5 // 初始化资源并缓存
6
7 securityCacheManager.initResourceInCache();
8
9 // 获取所有方法资源
10
11 List < String > methods = securityCacheManager.getMethodResources();
12
13 // 权限集合
14
15 Set < GrantedAuthority > authSet = new HashSet < GrantedAuthority > ();
16
17 // 遍历方法资源,并获取匹配的资源名称,然后从缓存中获取匹配正确
18
19 // 的资源对应的权限(ResourcDetail对象的GrantedAuthority[]对象数据)
20
21 for (String resourceName_method : methods) {
22
23 if (isMatch(targetClass, method, resourceName_method)) {
24
25 ResourcDetail detail = securityCacheManager.getResourcDetailFromCache(resourceName_method);
26
27 if (detail == null ) {
28
29 break ;
30
31 }
32
33 GrantedAuthority[] authorities = detail.getAuthorities();
34
35 if (authorities == null || authorities.length == 0 ) {
36
37 break ;
38
39 }
40
41 authSet.addAll(Arrays.asList(authorities));
42
43 }
44
45 }
46
47 if (authSet.size() > 0 ) {
48
49 String authString = "" ;
50
51 for (GrantedAuthority grantedAuthority : authSet) {
52
53 authString += grantedAuthority.getAuthority() + " , " ;
54
55 }
56
57 String authority = authString.substring( 0 , (authString.length() - 1 ));
58
59 System.out.println( " >>>>>>>>>>>>>>> " + authority);
60
61 ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();
62
63 attributeEditor.setAsText(authority.trim());
64
65 return (ConfigAttributeDefinition)attributeEditor.getValue();
66
67 }
68
69 returnnull;
70
71 }
isMatch 方法用于 对用户当前调用的方法与受保护的方法进行匹配,与URL资源类似,请参考代码。下面是applicationContext-security.xml 文件中的配置,请查看该配置文件。
2 class ="org.security.intercept.method.DataBaseMethodInvocationDefinitionSource"
3 p:securityCacheManager-ref ="securityCacheManager" />
securityCacheManager 属性定义了指向另一个Bean的引用,我们使用它从缓存中获取相应的信息。这个Bean和前一节中介绍的一样。只是这里我们获取的是方法保护定义资源。