背景描述
项目中需要做细粒的权限控制,细微至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