java中自定义Spring Security权限控制管理示例(实战篇)
背景描述
项目中需要做细粒的权限控制,细微至url+httpmethod(满足restful,例如:https://.../xxx/users/1,某些角色只能查看(HTTPGET),而无权进行增改删(POST,PUT,DELETE))。
表设计
为避嫌,只列出要用到的关键字段,其余敬请自行脑补。
1.admin_user管理员用户表,关键字段(id,role_id)。
2.t_role角色表,关键字段(id,privilege_id)。
3.t_privilege权限表,关键字段(id,url,method)
三个表的关联关系就不用多说了吧,看字段一眼就能看出。
实现前分析
我们可以逆向思考:
要实现我们的需求,最关键的一步就是让SpringSecurity的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
publicclassUrlGrantedAuthorityimplementsGrantedAuthority{ privatefinalStringhttpMethod; privatefinalStringurl; publicUrlGrantedAuthority(StringhttpMethod,Stringurl){ this.httpMethod=httpMethod; this.url=url; } @Override publicStringgetAuthority(){ returnurl; } publicStringgetHttpMethod(){ returnhttpMethod; } publicStringgetUrl(){ returnurl; } @Override publicbooleanequals(Objecto){ if(this==o)returntrue; if(o==null||getClass()!=o.getClass())returnfalse; UrlGrantedAuthoritytarget=(UrlGrantedAuthority)o; if(httpMethod.equals(target.getHttpMethod())&&url.equals(target.getUrl()))returntrue; returnfalse; } @Override publicinthashCode(){ intresult=httpMethod!=null?httpMethod.hashCode():0; result=31*result+(url!=null?url.hashCode():0); returnresult; } }
2.自定义认证用户实例
publicclassSystemUserimplementsUserDetails{ privatefinalAdminadmin; privateList<MenuOutput>menuOutputList; privatefinalList<GrantedAuthority>grantedAuthorities; publicSystemUser(Adminadmin,List<AdminPrivilege>grantedPrivileges,List<MenuOutput>menuOutputList){ this.admin=admin; this.grantedAuthorities=grantedPrivileges.stream().map(it->{ Stringmethod=it.getMethod()!=null?it.getMethod().getLabel():null; returnnewUrlGrantedAuthority(method,it.getUrl()); }).collect(Collectors.toList()); this.menuOutputList=menuOutputList; } @Override publicCollection<?extendsGrantedAuthority>getAuthorities(){ returnthis.grantedAuthorities; } @Override publicStringgetPassword(){ returnadmin.getPassword(); } @Override publicStringgetUsername(){ returnnull; } @Override publicbooleanisAccountNonExpired(){ returntrue; } @Override publicbooleanisAccountNonLocked(){ returntrue; } @Override publicbooleanisCredentialsNonExpired(){ returntrue; } @Override publicbooleanisEnabled(){ returntrue; } publicLonggetId(){ returnadmin.getId(); } publicAdmingetAdmin(){ returnadmin; } publicList<MenuOutput>getMenuOutputList(){ returnmenuOutputList; } publicStringgetSalt(){ returnadmin.getSalt(); } }
3.自定义UrlConfigAttribute实现
publicclassUrlConfigAttributeimplementsConfigAttribute{ privatefinalHttpServletRequesthttpServletRequest; publicUrlConfigAttribute(HttpServletRequesthttpServletRequest){ this.httpServletRequest=httpServletRequest; } @Override publicStringgetAttribute(){ returnnull; } publicHttpServletRequestgetHttpServletRequest(){ returnhttpServletRequest; } }
4.自定义SecurityMetadataSource实现
publicclassUrlFilterInvocationSecurityMetadataSourceimplementsFilterInvocationSecurityMetadataSource{ @Override publicCollection<ConfigAttribute>getAttributes(Objectobject)throwsIllegalArgumentException{ finalHttpServletRequestrequest=((FilterInvocation)object).getRequest(); Set<ConfigAttribute>allAttributes=newHashSet<>(); ConfigAttributeconfigAttribute=newUrlConfigAttribute(request); allAttributes.add(configAttribute); returnallAttributes; } @Override publicCollection<ConfigAttribute>getAllConfigAttributes(){ returnnull; } @Override publicbooleansupports(Class<?>clazz){ returnFilterInvocation.class.isAssignableFrom(clazz); } }
5.自定义voter实现
publicclassUrlMatchVoterimplementsAccessDecisionVoter<Object>{ @Override publicbooleansupports(ConfigAttributeattribute){ if(attributeinstanceofUrlConfigAttribute)returntrue; returnfalse; } @Override publicbooleansupports(Class<?>clazz){ returntrue; } @Override publicintvote(Authenticationauthentication,Objectobject,Collection<ConfigAttribute>attributes){ if(authentication==null){ returnACCESS_DENIED; } Collection<?extendsGrantedAuthority>authorities=authentication.getAuthorities(); for(ConfigAttributeattribute:attributes){ if(!(attributeinstanceofUrlConfigAttribute))continue; UrlConfigAttributeurlConfigAttribute=(UrlConfigAttribute)attribute; for(GrantedAuthorityauthority:authorities){ if(!(authorityinstanceofUrlGrantedAuthority))continue; UrlGrantedAuthorityurlGrantedAuthority=(UrlGrantedAuthority)authority; if(StringUtils.isBlank(urlGrantedAuthority.getAuthority()))continue; //如果数据库的method字段为null,则默认为所有方法都支持 StringhttpMethod=StringUtils.isNotBlank(urlGrantedAuthority.getHttpMethod())?urlGrantedAuthority.getHttpMethod() :urlConfigAttribute.getHttpServletRequest().getMethod(); //用Spring已经实现的AntPathRequestMatcher进行匹配,这样我们数据库中的url也就支持ant风格的配置了(例如:/xxx/user/**) AntPathRequestMatcherantPathRequestMatcher=newAntPathRequestMatcher(urlGrantedAuthority.getAuthority(),httpMethod); if(antPathRequestMatcher.matches(urlConfigAttribute.getHttpServletRequest())) returnACCESS_GRANTED; } } returnACCESS_ABSTAIN; } }
6.自定义FilterSecurityInterceptor实现
publicclassUrlFilterSecurityInterceptorextendsFilterSecurityInterceptor{ publicUrlFilterSecurityInterceptor(){ super(); } @Override publicvoidinit(FilterConfigarg0)throwsServletException{ super.init(arg0); } @Override publicvoiddestroy(){ super.destroy(); } @Override publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{ super.doFilter(request,response,chain); } @Override publicFilterInvocationSecurityMetadataSourcegetSecurityMetadataSource(){ returnsuper.getSecurityMetadataSource(); } @Override publicSecurityMetadataSourceobtainSecurityMetadataSource(){ returnsuper.obtainSecurityMetadataSource(); } @Override publicvoidsetSecurityMetadataSource(FilterInvocationSecurityMetadataSourcenewSource){ super.setSecurityMetadataSource(newSource); } @Override publicClass<?>getSecureObjectClass(){ returnsuper.getSecureObjectClass(); } @Override publicvoidinvoke(FilterInvocationfi)throwsIOException,ServletException{ super.invoke(fi); } @Override publicbooleanisObserveOncePerRequest(){ returnsuper.isObserveOncePerRequest(); } @Override publicvoidsetObserveOncePerRequest(booleanobserveOncePerRequest){ super.setObserveOncePerRequest(observeOncePerRequest); } }
配置文件关键配置
<security:http> ... <security:custom-filterref="filterSecurityInterceptor"before="FILTER_SECURITY_INTERCEPTOR"/> </security:http> <security:authentication-manageralias="authenticationManager"> <security:authentication-providerref="daoAuthenticationProvider"/> </security:authentication-manager> <beanid="accessDecisionManager"class="org.springframework.security.access.vote.AffirmativeBased"> <constructor-arg> <list> <beanid="authenticatedVoter"class="org.springframework.security.access.vote.AuthenticatedVoter"/> <beanid="roleVoter"class="org.springframework.security.access.vote.RoleVoter"/> <beanid="urlMatchVoter"class="com.mobisist.app.security.access.voter.UrlMatchVoter"/> </list> </constructor-arg> </bean> <beanid="securityMetadataSource"class="com.mobisist.app.security.access.UrlFilterInvocationSecurityMetadataSource"/> <beanid="filterSecurityInterceptor" class="com.mobisist.app.security.access.UrlFilterSecurityInterceptor"> <propertyname="authenticationManager"ref="authenticationManager"/> <propertyname="accessDecisionManager"ref="accessDecisionManager"/> <propertyname="securityMetadataSource"ref="securityMetadataSource"/> </bean>
好啦,接下来享受你的SpringSecurity权限控制之旅吧。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。