java中自定义Spring Security权限控制管理示例(实战篇)
背景描述
项目中需要做细粒的权限控制,细微至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
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.自定义认证用户实例
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实现
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实现
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实现
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实现
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); } }
配置文件关键配置
<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权限控制之旅吧。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。