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