前言
在看其他文章,实现了sso之后,发现通过security配置的拦截失效了,没有起作用。
接着在查找oauth2拦截配置的过程中,有人说到AccessDecisionManager
。通过它来配置拦截。
下面开始实现。
查找资料
搜索关键字:AccessDecisionManager配置
参考文档:
- https://www.cnblogs.com/chenhonggao/p/9152751.html
- http://www.mamicode.com/info-detail-2578727.html
- https://blog.csdn.net/zimou5581/article/details/89511381
- https://www.cnblogs.com/weilu2/p/springsecurity_custom_decision_metadata.html
- https://blog.csdn.net/u011318142/article/details/80083573
搜到了很多,有的可以,有的不行。影响不大的稍微改下就行了。
最后,发现这篇合适http://www.mamicode.com/info-detail-2578727.html。
已经贴到项目中,下面开始实现。
开始实现
首先,参考文档,略做修改,实现了基本的拦截
文档:http://www.mamicode.com/info-detail-2578727.html
原文:https://www.cnblogs.com/Mr-XiaoLiu/p/10231542.html
摘自原文:
在Spring Security中实现通过数据库动态配置url资源权限,需要通过配置验证过滤器来实现资源权限的加载、验证。系统启动时,到数据库加载系统资源权限列表,当有请求访问时,通过对比系统资源权限列表和用户资源权限列表(在用户登录时添加到用户信息中)来判断用户是否有该url的访问权限。
在配置验证过滤器时需要的配置项有如下几个:
filterSecurityInterceptor
:通过继承AbstractSecurityInterceptor并实现Filter接口自定义一个验证过滤器,替换默认验证过滤器。
accessDecisionManager
:通过实现AccessDecisionManager接口自定义一个决策管理器,判断是否有访问权限。判断逻辑可以写在决策管理器的决策方法中,也可以通过投票器实现,除了框架提供的三种投票器还可以添加自定义投票器。自定义投票器通过实现AccessDecisionVoter接口来实现。
securityMetadataSource
:实现FilterInvocationSecurityMetadataSource接口,在实现类中加载资源权限,并在filterSecurityInterceptor中注入该实现类。
WebSecurityConfig
:系统配置类,需要在配置类中配置启用filterSecurityInterceptor。
下面是代码
MyAccessDecisionManager
import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collection;
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* 决策方法:权限判断
*
* @param authentication 用户的身份信息;
* @param object 包含客户端发起的请求的request信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* @param configAttributes 是MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限;如果不在权限表中则放行。
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if (CollectionUtils.isEmpty(configAttributes)) {
throw new AccessDeniedException("没有权限");
}
for (GrantedAuthority ga : authentication.getAuthorities()) {
String authority = ga.getAuthority();
if (configAttributes.contains(new SecurityConfig(ga.getAuthority()))) {
return;
}
}
throw new AccessDeniedException("没有权限");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
MyFilterInvocationSecurityMetadataSource
import com.core.server.entity.BasePrivilege;
import com.oauth2.server.system.init.feign.AllFeignServiceApi;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@Service
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
AllFeignServiceApi allFeignServiceApi;
/**
* 资源权限
*/
private volatile HashMap<String, Collection<ConfigAttribute>> urlPermMap = null;
/**
* 加载资源,初始化资源变量
*/
public void loadResourceDefine() {
List<BasePrivilege> basePrivileges = allFeignServiceApi.baseUserFeignServiceApi.queryAllBasePrivilege();
if(CollectionUtils.isNotEmpty(basePrivileges)){
urlPermMap = new HashMap<>();
basePrivileges.forEach(item->{
if(StringUtils.isNotBlank(item.getPath())){
List<ConfigAttribute> authorityList = new ArrayList<>();
ConfigAttribute auth = new SecurityConfig(String.format("%s-%s", item.getResourceCode(), item.getCode()));
authorityList.add(auth);
urlPermMap.put(item.getPath(), authorityList);
}
});
}
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
loadResourceDefine();
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
// 资源权限为空,初始化资源
if (null == urlPermMap) {
synchronized (MyFilterInvocationSecurityMetadataSource.class) {
if (null == urlPermMap) {
loadResourceDefine();
}
}
}
return urlPermMap.get(url);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
MyFilterSecurityInterceptor
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
/**
* @param fi 里面有一个被拦截的url,调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限,
* 再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
* @throws IOException
* @throws ServletException
*/
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<ConfigAttribute> attributes = myFilterInvocationSecurityMetadataSource.getAttributes(fi);
HttpServletRequest request = fi.getHttpRequest();
myAccessDecisionManager.decide(authentication, request, attributes);
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 this.myFilterInvocationSecurityMetadataSource;
}
}
代码添加完毕,可以给过滤器添加断点,会发现请求可以会拦截到。但是想要正式使用,还要加一些逻辑判断。
上面这些代码中,主要有这以下几点,是我这边自己补充的:
-
第一点,
urlPermMap
。这里是参考这篇文章:https://www.cnblogs.com/weilu2/p/springsecurity_custom_decision_metadata.html。
其次,MyFilterSecurityInterceptor.invoke方法做了实现。根据方法上面的注释,添加了代码。/** * @param fi 里面有一个被拦截的url,调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限, * 再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够 * @throws IOException * @throws ServletException */ public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Collection<ConfigAttribute> attributes = myFilterInvocationSecurityMetadataSource.getAttributes(fi); HttpServletRequest request = fi.getHttpRequest(); myAccessDecisionManager.decide(authentication, request, attributes); fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } }
-
第二点,上面调用了决策方法。
myAccessDecisionManager.decide(authentication, request, attributes)
决策方法也做了修改,具体改动,可以自己做对比。/** * 决策方法:权限判断 * * @param authentication 用户的身份信息; * @param object 包含客户端发起的请求的request信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); * @param configAttributes 是MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果, * 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限;如果不在权限表中则放行。 * @throws AccessDeniedException * @throws InsufficientAuthenticationException */ @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (CollectionUtils.isEmpty(configAttributes)) { throw new AccessDeniedException("没有权限"); } for (GrantedAuthority ga : authentication.getAuthorities()) { if (configAttributes.contains(new SecurityConfig(ga.getAuthority()))) { return; } } throw new AccessDeniedException("没有权限"); }
这块逻辑,是看懂判断后,根据自己实现的
UserDetailsService
,里面的List<SimpleGrantedAuthority> authorities
数据结构来实现的。 -
第三点,就是
MyFilterInvocationSecurityMetadataSource.loadResourceDefine
这里是加载所有的资源。逻辑:通过fi获取到用户请求的地址,然后通过请求的地址找到controller。 然而,controller 里面的地址都是在数据库的资源里面配置的。 在这里做了初始化。
如果请求地址没有在这里匹配到,则全部被拦截。
示例:
/** * 加载资源,初始化资源变量 */ public void loadResourceDefine() { List<BasePrivilege> basePrivileges = allFeignServiceApi.baseUserFeignServiceApi.queryAllBasePrivilege(); if(CollectionUtils.isNotEmpty(basePrivileges)){ urlPermMap = new HashMap<>(); basePrivileges.forEach(item->{ if(StringUtils.isNotBlank(item.getPath())){ List<ConfigAttribute> authorityList = new ArrayList<>(); ConfigAttribute auth = new SecurityConfig(String.format("%s-%s", item.getResourceCode(), item.getCode())); authorityList.add(auth); urlPermMap.put(item.getPath(), authorityList); } }); } }
结尾
实现步骤:先参考原文,把代码复制进去。然后参考上面的文章,添加urlPermMap
后!自己根据自己的逻辑,慢慢一点一点添加,尽量不要使用我上面贴的代码,因为里面包含了自己的逻辑。
后面的再继续完善。