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权限控制之旅吧。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。