背景描述
項目中需要做細粒的權限控制,細微至url + httpmethod (滿足restful,例如: https://.../xxx/users/1, 某些角色只能查看(HTTP GET), 而無權進行增改刪(POST, PUT, DELETE))。
表設計
為避嫌,只列出要用到的關鍵字段,其余敬請自行腦補。
1.admin_user 管理員用戶表, 關鍵字段( id, role_id )。
2.t_role 角色表, 關鍵字段( id, privilege_id )。
3.t_privilege 權限表, 關鍵字段( id, url, method )
三個表的關聯關系就不用多說了吧,看字段一眼就能看出。
實現前分析
我們可以逆向思考:
要實現我們的需求,最關鍵的一步就是讓Spring Security的AccessDecisionManager來判斷所請求的url + httpmethod 是否符合我們數據庫中的配置。然而,AccessDecisionManager并沒有來判定類似需求的相關Voter, 因此,我們需要自定義一個Voter的實現(默認注冊的AffirmativeBased的策略是只要有Voter投出ACCESS_GRANTED票,則判定為通過,這也正符合我們的需求)。實現voter后,有一個關鍵參數(Collection
總結一下思路步驟:
1.自定義voter實現。
2.自定義ConfigAttribute實現。
3.自定義SecurityMetadataSource實現。
4.Authentication包含用戶實例(這個其實不用說,大家應該都已經這么做了)。
5.自定義GrantedAuthority實現。
項目實戰
1.自定義GrantedAuthority實現
UrlGrantedAuthority.java
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
|
public class UrlGrantedAuthority implements GrantedAuthority { private final String httpMethod; private final String url; public UrlGrantedAuthority(String httpMethod, String url) { this .httpMethod = httpMethod; this .url = url; } @Override public String getAuthority() { return url; } public String getHttpMethod() { return httpMethod; } public String getUrl() { return url; } @Override public boolean equals(Object o) { if ( this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; UrlGrantedAuthority target = (UrlGrantedAuthority) o; if (httpMethod.equals(target.getHttpMethod()) && url.equals(target.getUrl())) return true ; return false ; } @Override public int hashCode() { int result = httpMethod != null ? httpMethod.hashCode() : 0 ; result = 31 * result + (url != null ? url.hashCode() : 0 ); return result; } } |
2.自定義認證用戶實例
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
64
65
66
67
68
|
public class SystemUser implements UserDetails { private final Admin admin; private List<MenuOutput> menuOutputList; private final List<GrantedAuthority> grantedAuthorities; public SystemUser(Admin admin, List<AdminPrivilege> grantedPrivileges, List<MenuOutput> menuOutputList) { this .admin = admin; this .grantedAuthorities = grantedPrivileges.stream().map(it -> { String method = it.getMethod() != null ? it.getMethod().getLabel() : null ; return new UrlGrantedAuthority(method, it.getUrl()); }).collect(Collectors.toList()); this .menuOutputList = menuOutputList; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this .grantedAuthorities; } @Override public String getPassword() { return admin.getPassword(); } @Override public String getUsername() { return null ; } @Override public boolean isAccountNonExpired() { return true ; } @Override public boolean isAccountNonLocked() { return true ; } @Override public boolean isCredentialsNonExpired() { return true ; } @Override public boolean isEnabled() { return true ; } public Long getId() { return admin.getId(); } public Admin getAdmin() { return admin; } public List<MenuOutput> getMenuOutputList() { return menuOutputList; } public String getSalt() { return admin.getSalt(); } } |
3.自定義UrlConfigAttribute實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class UrlConfigAttribute implements ConfigAttribute { private final HttpServletRequest httpServletRequest; public UrlConfigAttribute(HttpServletRequest httpServletRequest) { this .httpServletRequest = httpServletRequest; } @Override public String getAttribute() { return null ; } public HttpServletRequest getHttpServletRequest() { return httpServletRequest; } } |
4.自定義SecurityMetadataSource實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { final HttpServletRequest request = ((FilterInvocation) object).getRequest(); Set<ConfigAttribute> allAttributes = new HashSet<>(); ConfigAttribute configAttribute = new UrlConfigAttribute(request); allAttributes.add(configAttribute); return allAttributes; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null ; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation. class .isAssignableFrom(clazz); } } |
5.自定義voter實現
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
|
public class UrlMatchVoter implements AccessDecisionVoter<Object> { @Override public boolean supports(ConfigAttribute attribute) { if (attribute instanceof UrlConfigAttribute) return true ; return false ; } @Override public boolean supports(Class<?> clazz) { return true ; } @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { if (authentication == null ) { return ACCESS_DENIED; } Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (ConfigAttribute attribute : attributes) { if (!(attribute instanceof UrlConfigAttribute)) continue ; UrlConfigAttribute urlConfigAttribute = (UrlConfigAttribute) attribute; for (GrantedAuthority authority : authorities) { if (!(authority instanceof UrlGrantedAuthority)) continue ; UrlGrantedAuthority urlGrantedAuthority = (UrlGrantedAuthority) authority; if (StringUtils.isBlank(urlGrantedAuthority.getAuthority())) continue ; //如果數據庫的method字段為null,則默認為所有方法都支持 String httpMethod = StringUtils.isNotBlank(urlGrantedAuthority.getHttpMethod()) ? urlGrantedAuthority.getHttpMethod() : urlConfigAttribute.getHttpServletRequest().getMethod(); //用Spring已經實現的AntPathRequestMatcher進行匹配,這樣我們數據庫中的url也就支持ant風格的配置了(例如:/xxx/user/**) AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(urlGrantedAuthority.getAuthority(), httpMethod); if (antPathRequestMatcher.matches(urlConfigAttribute.getHttpServletRequest())) return ACCESS_GRANTED; } } return ACCESS_ABSTAIN; } } |
6.自定義FilterSecurityInterceptor實現
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
|
public class UrlFilterSecurityInterceptor extends FilterSecurityInterceptor { public UrlFilterSecurityInterceptor() { super (); } @Override public void init(FilterConfig arg0) throws ServletException { super .init(arg0); } @Override public void destroy() { super .destroy(); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { super .doFilter(request, response, chain); } @Override public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return super .getSecurityMetadataSource(); } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return super .obtainSecurityMetadataSource(); } @Override public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) { super .setSecurityMetadataSource(newSource); } @Override public Class<?> getSecureObjectClass() { return super .getSecureObjectClass(); } @Override public void invoke(FilterInvocation fi) throws IOException, ServletException { super .invoke(fi); } @Override public boolean isObserveOncePerRequest() { return super .isObserveOncePerRequest(); } @Override public void setObserveOncePerRequest( boolean observeOncePerRequest) { super .setObserveOncePerRequest(observeOncePerRequest); } } |
配置文件關鍵配置
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
|
< security:http > ... < security:custom-filter ref = "filterSecurityInterceptor" before = "FILTER_SECURITY_INTERCEPTOR" /> </ security:http > < security:authentication-manager alias = "authenticationManager" > < security:authentication-provider ref = "daoAuthenticationProvider" /> </ security:authentication-manager > < bean id = "accessDecisionManager" class = "org.springframework.security.access.vote.AffirmativeBased" > < constructor-arg > < list > < bean id = "authenticatedVoter" class = "org.springframework.security.access.vote.AuthenticatedVoter" /> < bean id = "roleVoter" class = "org.springframework.security.access.vote.RoleVoter" /> < bean id = "urlMatchVoter" class = "com.mobisist.app.security.access.voter.UrlMatchVoter" /> </ list > </ constructor-arg > </ bean > < bean id = "securityMetadataSource" class = "com.mobisist.app.security.access.UrlFilterInvocationSecurityMetadataSource" /> < bean id = "filterSecurityInterceptor" class = "com.mobisist.app.security.access.UrlFilterSecurityInterceptor" > < property name = "authenticationManager" ref = "authenticationManager" /> < property name = "accessDecisionManager" ref = "accessDecisionManager" /> < property name = "securityMetadataSource" ref = "securityMetadataSource" /> </ bean > |
好啦,接下來享受你的Spring Security權限控制之旅吧。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/dongying/p/6128268.html