前不久SpringBoot2。7。0刚刚发布,SpringSecurity也升级到了5。7。1。升级后发现,原来一直在用的SpringSecurity配置方法,居然已经被弃用了。不禁感慨技术更新真快,用着用着就被弃用了!今天带大家体验下SpringSecurity的最新用法,看看是不是够优雅!基本使用 我们先对比下SpringSecurity提供的基本功能登录认证,来看看新版用法是不是更好。升级版本 首先修改项目的pom。xml文件,把SpringBoot版本升级至2。7。0版本。parentgroupIdorg。springframework。bootgroupIdspringbootstarterparentartifactIdversion2。7。0versionrelativePath!lookupparentfromrepositoryparent旧用法 在SpringBoot2。7。0之前的版本中,我们需要写个配置类继承WebSecurityConfigurerAdapter,然后重写Adapter中的三个方法进行配置;SpringSecurity的配置Createdbymacroon2018426。ConfigurationEnableWebSecurityEnableGlobalMethodSecurity(prePostEnabledtrue)publicclassOldSecurityConfigextendsWebSecurityConfigurerAdapter{AutowiredprivateUmsAdminServiceadminSOverrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException{省略HttpSecurity的配置}Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth。userDetailsService(userDetailsService())。passwordEncoder(passwordEncoder());}BeanOverridepublicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper。authenticationManagerBean();}} 如果你在SpringBoot2。7。0版本中进行使用的话,你就会发现WebSecurityConfigurerAdapter已经被弃用了,看样子SpringSecurity要坚决放弃这种用法了! 新用法 新用法非常简单,无需再继承WebSecurityConfigurerAdapter,只需直接声明配置类,再配置一个生成SecurityFilterChainBean的方法,把原来的HttpSecurity配置移动到该方法中即可。SpringSecurity5。4。x以上新用法配置为避免循环依赖,仅用于配置HttpSecurityCreatedbymacroon2022519。ConfigurationpublicclassSecurityConfig{BeanSecurityFilterChainfilterChain(HttpSecurityhttpSecurity)throwsException{省略HttpSecurity的配置returnhttpSecurity。build();}} 新用法感觉非常简洁干脆,避免了继承WebSecurityConfigurerAdapter并重写方法的操作,强烈建议大家更新一波!高级使用 升级SpringBoot2。7。0版本后,SpringSecurity对于配置方法有了大的更改,那么其他使用有没有影响呢?其实是没啥影响的,这里再聊聊如何使用SpringSecurity实现动态权限控制!基于方法的动态权限 首先来聊聊基于方法的动态权限控制,这种方式虽然实现简单,但却有一定的弊端。在配置类上使用EnableGlobalMethodSecurity来开启它;SpringSecurity的配置Createdbymacroon2018426。ConfigurationEnableWebSecurityEnableGlobalMethodSecurity(prePostEnabledtrue)publicclassOldSecurityConfigextendsWebSecurityConfigurerAdapter{}然后在方法中使用PreAuthorize配置访问接口需要的权限;商品管理ControllerCreatedbymacroon2018426。ControllerApi(tagsPmsProductController,description商品管理)RequestMapping(product)publicclassPmsProductController{AutowiredprivatePmsProductServiceproductSApiOperation(创建商品)RequestMapping(valuecreate,methodRequestMethod。POST)ResponseBodyPreAuthorize(hasAuthority(pms:product:create))publicCommonResultcreate(RequestBodyPmsProductParamproductParam,BindingResultbindingResult){intcountproductService。create(productParam);if(count0){returnCommonResult。success(count);}else{returnCommonResult。failed();}}}再从数据库中查询出用户所拥有的权限值设置到UserDetails对象中去,这种做法虽然实现方便,但是把权限值写死在了方法上,并不是一种优雅的做法。UmsAdminService实现类Createdbymacroon2018426。ServicepublicclassUmsAdminServiceImplimplementsUmsAdminService{OverridepublicUserDetailsloadUserByUsername(Stringusername){获取用户信息UmsAdminadmingetAdminByUsername(username);if(admin!null){ListUmsPermissionpermissionListgetPermissionList(admin。getId());returnnewAdminUserDetails(admin,permissionList);}thrownewUsernameNotFoundException(用户名或密码错误);}}基于路径的动态权限 其实每个接口对应的路径都是唯一的,通过路径来进行接口的权限控制才是更优雅的方式。doFilterOPTIONS白名单super。beforeInvocation(fi)AccessDecisionManagerdecide动态权限过滤器,用于实现基于路径的动态权限过滤Createdbymacroon202027。publicclassDynamicSecurityFilterextendsAbstractSecurityInterceptorimplementsFilter{AutowiredprivateDynamicSecurityMetadataSourcedynamicSecurityMetadataSAutowiredprivateIgnoreUrlsConfigignoreUrlsCAutowiredpublicvoidsetMyAccessDecisionManager(DynamicAccessDecisionManagerdynamicAccessDecisionManager){super。setAccessDecisionManager(dynamicAccessDecisionManager);}Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{}OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequestrequest(HttpServletRequest)servletRFilterInvocationfinewFilterInvocation(servletRequest,servletResponse,filterChain);OPTIONS请求直接放行if(request。getMethod()。equals(HttpMethod。OPTIONS。toString())){fi。getChain()。doFilter(fi。getRequest(),fi。getResponse());}白名单请求直接放行PathMatcherpathMatchernewAntPathMatcher();for(Stringpath:ignoreUrlsConfig。getUrls()){if(pathMatcher。match(path,request。getRequestURI())){fi。getChain()。doFilter(fi。getRequest(),fi。getResponse());}}此处会调用AccessDecisionManager中的decide方法进行鉴权操作InterceptorStatusTokentokensuper。beforeInvocation(fi);try{fi。getChain()。doFilter(fi。getRequest(),fi。getResponse());}finally{super。afterInvocation(token,null);}}Overridepublicvoiddestroy(){}OverridepublicC?getSecureObjectClass(){returnFilterInvocation。}OverridepublicSecurityMetadataSourceobtainSecurityMetadataSource(){returndynamicSecurityMetadataS}}接下来我们就需要创建一个类来继承AccessDecisionManager,通过decide方法对访问接口所需权限和用户拥有的权限进行匹配,匹配则放行;动态权限决策管理器,用于判断用户是否有访问权限Createdbymacroon202027。publicclassDynamicAccessDecisionManagerimplementsAccessDecisionManager{Overridepublicvoiddecide(Authenticationauthentication,Objectobject,CollectionConfigAttributeconfigAttributes)throwsAccessDeniedException,InsufficientAuthenticationException{当接口未被配置资源时直接放行if(CollUtil。isEmpty(configAttributes)){}IteratorConfigAttributeiteratorconfigAttributes。iterator();while(iterator。hasNext()){ConfigAttributeconfigAttributeiterator。next();将访问所需资源或用户拥有资源进行比对StringneedAuthorityconfigAttribute。getAttribute();for(GrantedAuthoritygrantedAuthority:authentication。getAuthorities()){if(needAuthority。trim()。equals(grantedAuthority。getAuthority())){}}}thrownewAccessDeniedException(抱歉,您没有访问权限);}Overridepublicbooleansupports(ConfigAttributeconfigAttribute){}Overridepublicbooleansupports(C?aClass){}}decideconfigAttributesFilterInvocationSecurityMetadataSourcegetAttributesgetAttributes动态权限数据源,用于获取动态权限规则Createdbymacroon202027。publicclassDynamicSecurityMetadataSourceimplementsFilterInvocationSecurityMetadataSource{privatestaticMapString,ConfigAttributeconfigAttributeMAutowiredprivateDynamicSecurityServicedynamicSecuritySPostConstructpublicvoidloadDataSource(){configAttributeMapdynamicSecurityService。loadDataSource();}publicvoidclearDataSource(){configAttributeMap。clear();configAttributeM}OverridepublicCollectionConfigAttributegetAttributes(Objecto)throwsIllegalArgumentException{if(configAttributeMapnull)this。loadDataSource();ListConfigAttributeconfigAttributesnewArrayList();获取当前访问的路径Stringurl((FilterInvocation)o)。getRequestUrl();StringpathURLUtil。getPath(url);PathMatcherpathMatchernewAntPathMatcher();IteratorStringiteratorconfigAttributeMap。keySet()。iterator();获取访问该路径所需资源while(iterator。hasNext()){Stringpatterniterator。next();if(pathMatcher。match(pattern,path)){configAttributes。add(configAttributeMap。get(pattern));}}未设置操作请求权限,返回空集合returnconfigA}OverridepublicCollectionConfigAttributegetAllConfigAttributes(){}Overridepublicbooleansupports(C?aClass){}}这里需要注意的是,所有路径对应的权限值数据来自于自定义的DynamicSecurityS动态权限相关业务类Createdbymacroon202027。publicinterfaceDynamicSecurityService{加载资源ANT通配符和资源对应MAPMapString,ConfigAttributeloadDataSource();}一切准备就绪,把动态权限过滤器添加到FilterSecurityInterceptor之前;SpringSecurity5。4。x以上新用法配置为避免循环依赖,仅用于配置HttpSecurityCreatedbymacroon2022519。ConfigurationpublicclassSecurityConfig{AutowiredprivateDynamicSecurityServicedynamicSecuritySAutowiredprivateDynamicSecurityFilterdynamicSecurityFBeanSecurityFilterChainfilterChain(HttpSecurityhttpSecurity)throwsException{省略若干配置。。。有动态权限配置时添加动态权限校验过滤器if(dynamicSecurityService!null){registry。and()。addFilterBefore(dynamicSecurityFilter,FilterSecurityInterceptor。class);}returnhttpSecurity。build();}}如果你看过这篇仅需四步,整合SpringSecurityJWT实现登录认证!的话,就知道应该要配置这两个Bean了,一个负责获取登录用户信息,另一个负责获取存储的动态权限规则,为了适应SpringSecurity的新用法,我们不再继承SecurityConfig,简洁了不少!mallsecurity模块相关配置自定义配置,用于配置如何获取用户信息及动态权限Createdbymacroon2022520。ConfigurationpublicclassMallSecurityConfig{AutowiredprivateUmsAdminServiceadminSBeanpublicUserDetailsServiceuserDetailsService(){获取登录用户信息returnusername{AdminUserDetailsadminadminService。getAdminByUsername(username);if(admin!null){}thrownewUsernameNotFoundException(用户名或密码错误);};}BeanpublicDynamicSecurityServicedynamicSecurityService(){returnnewDynamicSecurityService(){OverridepublicMapString,ConfigAttributeloadDataSource(){MapString,ConfigAttributemapnewConcurrentHashMap();ListUmsResourceresourceListadminService。getResourceList();for(UmsResourceresource:resourceList){map。put(resource。getUrl(),neworg。springframework。security。access。SecurityConfig(resource。getId():resource。getName()));}}};}}效果测试接下来启动我们的示例项目malltinysecurity,使用如下账号密码登录,该账号只配置了访问brandlistAll的权限,访问地址:http:localhost:8088swaggerui 然后把返回的token放入到Swagger的认证头中; 当我们访问有权限的接口时可以正常获取到数据; 当我们访问没有权限的接口时,返回没有访问权限的接口提示。 总结 SpringSecurity的升级用法确实够优雅,够简单,而且对之前用法的兼容性也比较好!个人感觉一个成熟的框架不太会在升级过程中大改用法,即使改了也会对之前的用法做兼容,所以对于绝大多数框架来说旧版本会用,新版本照样会用! 原文链接:https:mp。weixin。qq。coms?bizMzU1Nzg4NjgyMwmid2247500055idx1sn3faa578dd23b296cdb4dbd19910d2a46utmsourcetuicoolutmmediumreferral