SpringBoot integrates SpringSecurity to implement dynamic interface management permissions
Continuing from the previous article, permission management is an indispensable part of background management. Today, combined with Spring Security to implement dynamic management of interfaces.
Dynamic rights management
Spring Security implements dynamic management of permissions. The first step is to create a filter. The doFilter method needs to pay attention to OPTIONS directly, otherwise cross-domain problems will occur. And the whitelist in IgnoreUrlsConfig mentioned in the previous article is also directly released, and all permission operations will be implemented in super.beforeInvocation(fi).
/**
* 动态权限过滤器,用于实现基于路径的动态权限过滤
*
*/
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
@Autowired
private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Autowired
public void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager) {
super.setAccessDecisionManager(dynamicAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
//OPTIONS请求直接放行
if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
//白名单请求直接放行
PathMatcher pathMatcher = new AntPathMatcher();
for (String path : ignoreUrlsConfig.getUrls()) {
if(pathMatcher.match(path,request.getRequestURI())){
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
}
//此处会调用AccessDecisionManager中的decide方法进行鉴权操作
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return dynamicSecurityMetadataSource;
}
}
When the super.beforeInvocation(fi) method is called in DynamicSecurityFilter, the decide method in AccessDecisionManager is called for authentication operation, and the configAttributes parameter in the decide method will be obtained through the getAttributes method in SecurityMetadataSource. configAttributes is actually configured to access the current The permissions required by the interface, the following is a simplified version of the beforeInvocation source code
public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
protected InterceptorStatusToken beforeInvocation(Object object) {
//获取元数据
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
Authentication authenticated = authenticateIfRequired();
//进行鉴权操作
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
}
}
The above introduction, next we implement the getAttributes method of the SecurityMetadataSource interface to obtain the currently accessed path resources
/**
* 动态权限数据源,用于获取动态权限规则
*
*/
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private static Map<String, ConfigAttribute> configAttributeMap = null;
@Autowired
private DynamicSecurityService dynamicSecurityService;
@PostConstruct
public void loadDataSource() {
configAttributeMap = dynamicSecurityService.loadDataSource();
}
public void clearDataSource() {
configAttributeMap.clear();
configAttributeMap = null;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
if (configAttributeMap == null) this.loadDataSource();
List<ConfigAttribute> configAttributes = new ArrayList<>();
//获取当前访问的路径
String url = ((FilterInvocation) o).getRequestUrl();
String path = URLUtil.getPath(url);
PathMatcher pathMatcher = new AntPathMatcher();
Iterator<String> iterator = configAttributeMap.keySet().iterator();
//获取访问该路径所需资源
while (iterator.hasNext()) {
String pattern = iterator.next();
if (pathMatcher.match(pattern, path)) {
configAttributes.add(configAttributeMap.get(pattern));
}
}
// 未设置操作请求权限,返回空集合
return configAttributes;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
Our background resources are cached in a MAP object by rules. When the background resources change, the cache needs to be cleared and reloaded in the next query. We need to modify MyMesResourceController to inject DynamicSecurityMetadataSource. When modifying the background resource, we need to call the clearDataSource method to clear the cached data.
/**
* 后台资源管理Controller
*
*/
@Controller
@Api(tags = "MyMesResourceController", description = "后台资源管理")
@RequestMapping("/resource")
public class MyMesResourceController {
@Autowired
private MyMesResourceService resourceService;
@Autowired
private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
@ApiOperation("添加后台资源")
@RequestMapping(value = "/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult create(@RequestBody UmsResource umsResource) {
int count = resourceService.create(umsResource);
dynamicSecurityMetadataSource.clearDataSource();
if (count > 0) {
return CommonResult.success(count);
} else {
return CommonResult.failed();
}
}
}
We need to implement the AccessDecisionManager interface to achieve permission verification. For interfaces that are not configured with resources, we directly allow access. For interfaces with configured resources, we compare the resources required for access with the resources owned by the user, and if they match, access is allowed.
/**
* 动态权限决策管理器,用于判断用户是否有访问权限
*
*/
public class DynamicAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// 当接口未被配置资源时直接放行
if (CollUtil.isEmpty(configAttributes)) {
return;
}
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//将访问所需资源或用户拥有资源进行比对
String needAuthority = configAttribute.getAttribute();
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("抱歉,您没有访问权限");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
We previously injected a DynamicSecurityService object into DynamicSecurityMetadataSource, which is a dynamic permission business interface I customized, which is mainly used to load all background resource rules.
/**
* 动态权限相关业务类
*
*/
public interface DynamicSecurityService {
/**
* 加载资源ANT通配符和资源对应MAP
*/
Map<String, ConfigAttribute> loadDataSource();
}
The dynamic management authority of the interface combined with Spring Security has been basically realized. I will explain the Redis+AOP optimization authority management tomorrow.
the public
Official account https://mp.weixin.qq.com/s/nfat2WWWUXdmfUGFBAVEuA