SpringSecurity实现动态权限校验的过程

 更新时间:2025年02月15日 12:04:37   作者:深藏Blued蓝先生  
Spring Security过滤器链中,AuthorizationFilter的authorizationManager是我们要找的组件,该组件的check方法已被弃用,推荐使用authorize方法,最终通过接口路径和权限进行校验,本文给大家介绍SpringSecurity实现动态权限校验的相关知识,感兴趣的朋友一起看看吧

在框架DefaultSecurityFilterChain源码内打断点可以找到SpringSecurity的过滤器链可以看见一个叫AuthorizationFilter的过滤器

很明显这个叫authorizationManager的应该是我们要找的玩意,直接去AuthorizationFilter内找这个类看他的源码可以发现check方法已经弃用,他推荐用的方法是authorize但这玩意也还是调用的check

@FunctionalInterface
public interface AuthorizationManager<T> {
	/**
	 * Determines if access should be granted for a specific authentication and object.
	 * @param authentication the {@link Supplier} of the {@link Authentication} to check
	 * @param object the {@link T} object to check
	 * @throws AccessDeniedException if access is not granted
	 */
	default void verify(Supplier<Authentication> authentication, T object) {
		AuthorizationDecision decision = check(authentication, object);
		if (decision != null && !decision.isGranted()) {
			throw new AuthorizationDeniedException("Access Denied", decision);
		}
	}
	/**
	 * Determines if access is granted for a specific authentication and object.
	 * @param authentication the {@link Supplier} of the {@link Authentication} to check
	 * @param object the {@link T} object to check
	 * @return an {@link AuthorizationDecision} or null if no decision could be made
	 * @deprecated please use {@link #authorize(Supplier, Object)} instead
	 */
	@Nullable
	@Deprecated
	AuthorizationDecision check(Supplier<Authentication> authentication, T object);
	/**
	 * Determines if access is granted for a specific authentication and object.
	 * @param authentication the {@link Supplier} of the {@link Authentication} to
	 * authorize
	 * @param object the {@link T} object to authorize
	 * @return an {@link AuthorizationResult}
	 * @since 6.4
	 */
	@Nullable
	default AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
		return check(authentication, object);
	}

继续往下面看可以看见他是进行了校验然后返回了一个布尔值

@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
		boolean granted = this.authorizationStrategy.isGranted(authentication.get());
		return new AuthorizationDecision(granted);
	}

代码实现 逻辑大概是通过传进来的接口路径然后匹配权限

@Component
public class DynamicAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
    @Resource
    private DynamicSecurityMetadataSource securityMetadataSource;
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        HttpServletRequest request = context.getRequest();
        // 获取当前请求所需的权限
        String url = request.getRequestURI();
        String method = request.getMethod();
        FilterInvocation fi = new FilterInvocation(String.valueOf(request), url, method);
        Collection<ConfigAttribute> attributes = securityMetadataSource.getAttributes(fi);
        // 没有配置权限要求,允许访问
        if (CollectionUtils.isEmpty(attributes)) {
            return new AuthorizationDecision(true);
        }
        // 获取当前用户认证信息
        Authentication auth = authentication.get();
        if (auth == null || !auth.isAuthenticated()) {
            return new AuthorizationDecision(false);
        }
        // 获取用户所拥有的权限
        Set<String> userPermissions = auth.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toSet());
        // 判断是否有所需权限
        boolean hasPermission = attributes.stream()
            .map(ConfigAttribute::getAttribute)
            .anyMatch(userPermissions::contains);
        return new AuthorizationDecision(hasPermission);
    }
}

getAllConfigAttributes和supports我大概看了一下直接复制粘贴的框架的源码以后万一有用呢

@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Resource
    private TPMenuService menuService;
    private Map<String, Collection<ConfigAttribute>> configAttributeMap;
    @PostConstruct
    public void loadDataSource() {
        configAttributeMap = new HashMap<>();
        List<TPMenu> menus = menuService.list();
        configAttributeMap = menus.stream()
                .filter(menu -> StringUtils.hasText(menu.getPath()) && StringUtils.hasText(menu.getPerms()))
                .collect(Collectors.toMap(
                        TPMenu::getPath,
                        menu -> {
                            List<ConfigAttribute> attributes = new ArrayList<>();
                            attributes.add(new SecurityConfig(menu.getPerms()));
                            return attributes;
                        }
                ));
    }
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        if (requestUrl.contains("?")) {
            requestUrl = requestUrl.substring(0, requestUrl.indexOf("?"));
        }
        int count = StringUtils.countOccurrencesOf(requestUrl, "/");
        if (count > 2) {
            requestUrl = requestUrl.replaceAll("/[^/]+$", "");
        }
        for (Map.Entry<String, Collection<ConfigAttribute>> entry : configAttributeMap.entrySet()) {
            String pattern = entry.getKey();
            if (new AntPathMatcher().match(pattern, requestUrl)) {
                return entry.getValue();
            }
        }
        return null;
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        Set<ConfigAttribute> allAttributes = new HashSet<>();
        configAttributeMap.values().forEach(allAttributes::addAll);
        return allAttributes;
    }
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

过滤器实现然后记得security配置文件添加这个过滤器就ok了,配置文件可以看我另外一篇文章

@Component
public class DynamicSecurityFilter extends OncePerRequestFilter {
    @Resource
    private DynamicSecurityMetadataSource securityMetadataSource;
    @Resource
    private DynamicAuthorizationManager authorizationManager;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException, IOException {
        if (shouldNotFilter(request)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            RequestAuthorizationContext context = new RequestAuthorizationContext(request);
            // 权限检查
            AuthorizationDecision check = authorizationManager.check(
                    () -> authentication,
                    context
            );
            if (check.isGranted()) {
                chain.doFilter(request, response);
            } else {
                accessDeniedHandler.handle(request, response, new AccessDeniedException("权限不足"));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
        return whitelist.stream()
                .anyMatch(pattern ->
                        pattern.endsWith("/**")
                                ? path.startsWith(pattern.substring(0, pattern.length() - 3))
                                : pattern.equals(path)
                );
    }
}

debug重启可以看见我的过滤器已经添加进去了

如果有需要还可以直接去看官方demo
https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/jwt/login/src/main

到此这篇关于SpringSecurity实现动态权限校验的过程的文章就介绍到这了,更多相关SpringSecurity动态权限校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring创建Bean的生命周期详析

    Spring创建Bean的生命周期详析

    这篇文章主要介绍了Spring创建Bean的生命周期详析,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • 聊聊Spring Cloud Cli 初体验

    聊聊Spring Cloud Cli 初体验

    这篇文章主要介绍了聊聊Spring Cloud Cli 初体验,SpringBoot CLI 是spring Boot项目的脚手架工具。非常具有实用价值,需要的朋友可以参考下
    2018-04-04
  • 详解spring多线程与定时任务

    详解spring多线程与定时任务

    本篇文章主要介绍了spring多线程与定时任务,详细的介绍了spring多线程任务和spring定时任务,有兴趣的可以了解一下。
    2017-04-04
  • 手把手教你JAVA进制之间的转换

    手把手教你JAVA进制之间的转换

    这篇文章主要介绍了Java实现的进制转换,结合完整实例形式分析了Java实现二进制、十六进制、字符串、数组等相关转换操作技巧,需要的朋友可以参考下
    2021-08-08
  • Java如何使用httpclient检测url状态及链接是否能打开

    Java如何使用httpclient检测url状态及链接是否能打开

    这篇文章主要介绍了Java如何使用httpclient检测url状态及链接是否能打开,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • springboot多租户设计过程图解

    springboot多租户设计过程图解

    这篇文章主要介绍了springboot多租户设计过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • 详解springboot热启动与热部署

    详解springboot热启动与热部署

    本篇文章主要介绍了详解springboot热启动与热部署,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Java怎么获取当前时间、计算程序运行时间源码详解(超详细!)

    Java怎么获取当前时间、计算程序运行时间源码详解(超详细!)

    有的时候,我们需要查看某一段代码的性能如何,最为简单的方式,可以通过计算该段代码执行的耗时,来进行简单的判断,这篇文章主要给大家介绍了关于Java怎么获取当前时间、计算程序运行时间的相关资料,需要的朋友可以参考下
    2024-07-07
  • java多线程之定时器Timer的使用详解

    java多线程之定时器Timer的使用详解

    本篇文章主要介绍了java多线程之定时器Timer的使用详解,Time类主要负责完成定时计划任务的功能,有兴趣的可以了解一下。
    2017-04-04
  • Java封装实现自适应的单位转换工具类

    Java封装实现自适应的单位转换工具类

    这篇文章主要为大家详细介绍了如何使用Java封装实现一个自适应的单位转换工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-03-03

最新评论