【spring oauth2】spring oauth2添加自定义拦截验证

tech2022-11-04  129

前言

在看其他文章,实现了sso之后,发现通过security配置的拦截失效了,没有起作用。 接着在查找oauth2拦截配置的过程中,有人说到AccessDecisionManager。通过它来配置拦截。 下面开始实现。

查找资料

搜索关键字:AccessDecisionManager配置 参考文档:

https://www.cnblogs.com/chenhonggao/p/9152751.htmlhttp://www.mamicode.com/info-detail-2578727.htmlhttps://blog.csdn.net/zimou5581/article/details/89511381https://www.cnblogs.com/weilu2/p/springsecurity_custom_decision_metadata.htmlhttps://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后!自己根据自己的逻辑,慢慢一点一点添加,尽量不要使用我上面贴的代码,因为里面包含了自己的逻辑。 后面的再继续完善。

最新回复(0)