spring security 权限动态管理
前言
相信每天工作都要用spring框架的大家一定使用过spring security,security的概念是权限管控,且管控级别可以到达最小颗粒也就是功能管控,相对与shiro的权限管控机制,它更灵活可扩展方法也更多,当然这也是Spring官方推荐的权限管控光甲,security更关注的是访问地址与用户的权限是否一致,从而达到灵活去验证权限相关功能,更好的实现灵活配置,比如说一个大型系统中功能管理,菜单管理,前端可视化权限管理等等,都是各个模块都需要的操作,那样代表着这些操作会散落在系统的各个地方,不易管理且杂乱无章,而security就是关注的这些,spring security使得我们的权限开发工作变得简单,这次我就给大家讲讲spring security的权限动态配置实现。
使用
要分析spring security的底层原理,首先要会使用,先创建一个普通spring boot工程,引入spring-boot-starter-security
,spring-boot-starter-web 依赖。
1.增加基础web依赖
org.springframework.boot spring-boot-starter-web
2.增加权限依赖
org.springframework.boot spring-boot-starter-security
3.你也可以在创建Spring Initializr工程的时候按下方点击直接加入这两个依赖
代码编写
1.配置文件application.yml
LOGIN: login: /login logout: mes/login not-authority: ROLE_LOGIN au: au un: nu role-name: roleCode #无需认证可访问地址,以逗号隔开 antMatchers: - /login.html - /authentication/request - /favicon.ico - /code/image - /**# - /index# - /a/a# - /b/a - /swagger-ui.html - /v2/** - /swagger-resources/configuration/ui - /swagger-resources - /swagger-resources/configuration/security - /webjars/** - /csrf# - /Rule/**
2.调用工具类AuthDaoTools.java
package com.hr.auth.common.tools;import com.hr.auth.config.redis.service.RedisService;import com.hr.auth.cookie.CookieService;import com.hr.repository.authority.ApiAuthorityService;import com.hr.repository.authority.PermissionOperationService;import com.hr.repository.authority.RoleDefinitionService;import com.hr.repository.authority.RouteAuthorityService;import com.hr.repository.security.RSAHelper;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Repository;import javax.annotation.Resource;/** * @author xgp * @version 1.0 * @date 2020/5/14 12:53 */@Repositorypublic class AuthDaoTools { @Value("${LOGIN.logout}") protected String LOGIN_URL ; @Value("${LOGIN.login}") protected String LOGIN ; @Value("${LOGIN.not-authority}") protected String NOT_AUTHORITY; @Value("${LOGIN.au}") protected String AU ; @Value("${LOGIN.un}") protected String UN ; @Value("${LOGIN.role-name}") protected String ROLECODE; @Resource protected RSAHelper rsaHelper; @Resource protected CookieService cookieService; @Resource protected RedisService redisService;}
3:动态授权
1.1:我们先创建一个WebSecurityConfig进行静态授权管理
package com.hr.auth.config.security;import com.hr.auth.config.response.CustomExpiredSessionStrategy;import com.hr.auth.config.response.MyAccessDeniedHandler;import com.hr.auth.config.check.roles.CustomAccessDecisionManager;import com.hr.auth.config.check.route.CustomFilterInvocationSecurtityMetadataSource;import com.hr.auth.config.login.CustomAuthenticationFailureHandler;import com.hr.auth.config.login.CustomAuthenticationSuccessHandler;import com.hr.auth.config.login.MyLogoutSuccessHandler;import com.hr.auth.config.login.MyUserDetailsService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.context.properties.bind.Bindable;import org.springframework.boot.context.properties.bind.Binder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.env.Environment;import org.springframework.security.config.annotation.ObjectPostProcessor;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;import org.springframework.web.cors.CorsUtils;import java.util.List;/** * @author xgp * @version 1.0 * @date 2020/5/6 13:28 * @Annotate 权限配置 */@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 登入失败处理逻辑类 **/ @Autowired private CustomAuthenticationFailureHandler customAuthenticationFailureHandler; /** * 登入成功处理逻辑类 **/ @Autowired private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; /** * SESSION被挤处理逻辑类(类似于下线) **/ @Autowired private CustomExpiredSessionStrategy customExpiredSessionStrategy; /** * 拦截访问URL赋值URL访问权限 **/ @Autowired private CustomFilterInvocationSecurtityMetadataSource customFilterInvocationSecurtityMetadataSource; /** * 拦截访问进行用户权限与网页权限进行CHEICK **/ @Autowired private CustomAccessDecisionManager customAccessDecisionManager; /** * 权限不足处理类 **/ @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; /** * 用户登录验证及权限赋值 **/ @Autowired private MyUserDetailsService myUserDetailsService; @Autowired private MyLogoutSuccessHandler myLogoutSuccessHandler; private List antMatchers = null; @Autowired private Environment evn; /* @Bean UserDetailsService userDetails(){ return new MyUserDetailsService(); }*/ /** * 密码加密方式,可在登录处理类进行获取及赋值 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 用户登录处理方法及面加密方式 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService) .passwordEncoder(passwordEncoder()); } /** * 无权限通行相关URL */ @Override public void configure(WebSecurity web) throws Exception {/** 绑定配置器*/ if (antMatchers == null) {bindNoneUriProp();} web.ignoring().antMatchers(antMatchers.toArray(new String[antMatchers.size()])); } /** * spring security 权限相关配置类 */ @Override protected void configure(HttpSecurity http) throws Exception { http.cors(); if (antMatchers == null) {bindNoneUriProp();} http.authorizeRequests().antMatchers(antMatchers.toArray(new String[antMatchers.size()])) .permitAll() .withObjectPostProcessor(new ObjectPostProcessor() { @Override public O postProcess(O object) { object.setSecurityMetadataSource(customFilterInvocationSecurtityMetadataSource); object.setAccessDecisionManager(customAccessDecisionManager); return object; } }) // 如果有允许匿名的url,填在下面// .antMatchers().permitAll()// .antMatchers("/a/**").access("hasRole('ADMIN')")// .anyRequest().authenticated() .and()// .antMatcher("/mes") // 设置登陆页 .formLogin() .loginPage("/login") //登录成功处理位置 .successHandler(customAuthenticationSuccessHandler) //登录失败处理位置 .failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login/error")// .defaultSuccessUrl("/") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessHandler(myLogoutSuccessHandler) .deleteCookies("JSESSIONID") .permitAll() .and() .sessionManagement()//.invalidSessionUrl("/login") .maximumSessions(10) .maxSessionsPreventsLogin(false) //登录超限处理位置 .expiredSessionStrategy(customExpiredSessionStrategy); http.csrf().disable().exceptionHandling().accessDeniedHandler(myAccessDeniedHandler); } private List bindNoneUriProp() { Binder binder = Binder.get(evn); return antMatchers = binder.bind("login.antmatchers", Bindable.listOf(String.class)).get(); }}
1.1.2这个配置项最终的就是这段,这段实现了动态权限配置的关键地方
withObjectPostProcessor(new ObjectPostProcessor() { @Override public O postProcess(O object) { object.setSecurityMetadataSource(customFilterInvocationSecurtityMetadataSource); object.setAccessDecisionManager(customAccessDecisionManager); return object; } })
上述这段代码实现了调用接口权限注入以及用户权限与接口权限匹配
1.1.3:customFilterInvocationSecurtityMetadataSource 接口权限注入
package com.hr.auth.config.check.route;import com.hr.auth.common.tools.AuthDaoTools;import com.hr.auth.config.entity.RequestUrl;import com.hr.auth.service.AuthService;import com.hr.core.global.CurrentUserHolder;import lombok.SneakyThrows;import lombok.extern.log4j.Log4j2;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.SecurityConfig;import org.springframework.security.web.FilterInvocation;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import javax.annotation.Resource;import java.util.Collection;/** * @author xgp * @version 1.0 * @date 2020/5/12 9:18 * @Annotate 拦截访问URL赋值URL访问权限 * */@Component@Log4j2public class CustomFilterInvocationSecurtityMetadataSource extends AuthDaoTools implements FilterInvocationSecurityMetadataSource { private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Resource protected AuthService authService; @SneakyThrows @Override public Collection getAttributes(Object object) throws IllegalArgumentException { FilterInvocation invocation = (FilterInvocation) object; RequestUrl requestUrl = authService.getRequestUrl(invocation); /**判断是否为登录界面URL*/ if(antPathMatcher.match(LOGIN,requestUrl.getRequestUrl())){ return null; } /**获取当前页面的权限*/ String roleName = authService.getRoleCode(username, requestUrl.getRequestUrl(), requestUrl.getRequestUrlAll()); return SecurityConfig.createList(roleName);权限注入 } @Override public Collection getAllConfigAttributes() { return null; } @Override public boolean supports(Class clazz) { return true; }}
1.1.4:CustomAccessDecisionManager用户权限与接口权限比对,判定是否可以操作,如不可以抛出权限不足异常。
package com.hr.auth.config.check.roles;import com.hr.auth.common.tools.AuthDaoTools;import com.hr.auth.service.AuthService;import lombok.extern.log4j.Log4j2;import org.springframework.security.access.AccessDecisionManager;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.authentication.AnonymousAuthenticationToken;import org.springframework.security.authentication.InsufficientAuthenticationException;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.util.Collection;/** * @author xgp * @version 1.0 * @date 2020/5/12 10:37 * @Annotate 拦截访问进行用户权限与网页权限进行CHEICK */@Component@Log4j2public class CustomAccessDecisionManager extends AuthDaoTools implements AccessDecisionManager { @Resource protected AuthService authService; @Override public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { for (ConfigAttribute configAttribute : configAttributes) { /**判定是否为登录页面*/ if (NOT_AUTHORITY.equals(configAttribute.getAttribute())) { if (authentication instanceof AnonymousAuthenticationToken) { log.info("匿名用户"); log.info("效验TOKEN判断是否登录通过验证"); throw new AccessDeniedException(configAttribute.getAttribute() + "权限不足"); } else { log.info("其他类型用户"); throw new AccessDeniedException(configAttribute.getAttribute() + "其他用户权限不足"); } } Collection authorities = authentication.getAuthorities(); for (GrantedAuthority grantedAuthority : authorities) { log.info("账户所拥有的权限:" + grantedAuthority.getAuthority()); log.info("路径所需要角色:" + configAttribute.getAttribute()); if (grantedAuthority.getAuthority().equals(configAttribute.getAttribute())) { return; } } } throw new AccessDeniedException("权限不足,无法访问"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class clazz) { return true; }}
尾言
至此,动态权限配置及管控也就完成了,主要是权限业务方面的问题,可以自己去定义已什么中心进行管控,我是已角色进行管控菜单及接口之间的关系,这样可以很好的保证每个接口是否可以进行访问,可以精确的阻挡一些特定接口。比如,当一个页面A用户有增删改查权限而B用户只有增查时。这时候就能非常突出security 的动态配置优势,下一篇文章讲解我是如何实现SESSION共享以及整个权限管控的实际流程。谢谢!