Spring Security之基于HttpRequest配置权限

这篇具有很好参考价值的文章主要介绍了Spring Security之基于HttpRequest配置权限。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

今天我们重点聊聊授权方式的另外一种:基于HttpServletRequest配置权限

基于HttpServletRequest配置权限

一个典型的配置demo

http.authorizeHttpRequests(requestMatcherRegstry -> 
// /admin/** 需要有AMIND角色
	requestMatcherRegstry.requestMatchers("/admin/**").hasRole("ADMIN")
			// /log/** 只要有AMIND、USER角色之一
          .requestMatchers("/log/**").hasAnyRole("ADMIN", "USER")
			// 任意请求 只要登录了即可访问
          .anyRequest().authenticated()
);

从这里也可以看出,要实现基于RBAC,还是比较容易的。也比较容易使用。但是如果想要动态的增加角色,就需要我们定制AuthorizationManager。

配置原理

HttpSecurity是负责构建DefaultSecurityFilterChain的。而这个安全过滤器链,则是允许我们进行配置的。而authorizeHttpRequests方法,正是配置AuthorizationFilter的。而我们传入的入参-lambada表达式-则是指引如何配置AuthorizationFilter的。

/**
 * 这个方法是HttpSecurity的方法。
 * 作用是配置AuthorizationFilter。
 * 其入参authorizeHttpRequestsCustomizer正是让我们配置AuthorizationFilter的关键。
 * Customizer:就是定制。原理比较容易理解,就是我把你需要配置的东西丢给你,你往里面赋值。
 * AuthorizeHttpRequestsConfigurer<HttpSecurity>:这个是Configurer的实现,负责引入过滤器的。这里明显就是引入AuthorizationFilter
 * AuthorizationManagerRequestMatcherRegistry:这个就是我们最终配置的东西。而这个配置的正是我们上面的RequestMatcherDelegatingAuthorizationManager。说白了就是往里面添加哪些路径对应哪些AuthorizationManager。只不过,为了方便使用,也帮我们都封装好了。不妨继续往后看看。
 */
public HttpSecurity authorizeHttpRequests(
			Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequestsCustomizer)
			throws Exception {
		ApplicationContext context = getContext();
		// 这里干了三个事情:
		// 1. 如果当前HttpSecurity不存在AuthorizeHttpRequestsConfigurer,则创建一个,并注册到当前的HttpSecurity对象中。
		// 2. 从AuthorizeHttpRequestsConfigurer拿到他的注册器也就是AuthorizationManagerRequestMatcherRegistry
		// 3. 调用传入的参数的customize。如此,我们传入的lambda表达式就被调用了。
		authorizeHttpRequestsCustomizer
			.customize(getOrApply(new AuthorizeHttpRequestsConfigurer<>(context)).getRegistry());
		return HttpSecurity.this;
	}
public final class AuthorizationManagerRequestMatcherRegistry
		extends AbstractRequestMatcherRegistry<AuthorizedUrl> {
	/**
	 * 这是父类的方法
	 * C代表的是AuthorizedUrl
	 */
	public C requestMatchers(String... patterns) {
		// 调用的重载方法第一个参数为HttpMethod,也就是说,我们还可以指定HTTP请求的方法,例如:POST、GET等
		return requestMatchers(null, patterns);
	}
	
	@Override
	protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
		this.unmappedMatchers = requestMatchers;
		return new AuthorizedUrl(requestMatchers);
	}
}
public class AuthorizedUrl {
	private final List<? extends RequestMatcher> matchers;
	public AuthorizationManagerRequestMatcherRegistry permitAll() {
		return access(permitAllAuthorizationManager);
	}
	public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
		return access(withRoleHierarchy(AuthorityAuthorizationManager.hasRole(role)));
	}
	public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
		return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
	}
	public AuthorizationManagerRequestMatcherRegistry authenticated() {
		return access(AuthenticatedAuthorizationManager.authenticated());
	}
	public AuthorizationManagerRequestMatcherRegistry access(
		AuthorizationManager<RequestAuthorizationContext> manager) {
		Assert.notNull(manager, "manager cannot be null");
		return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
	}
}
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {
		
	private AuthorizationManagerRequestMatcherRegistry addMapping(List<? extends RequestMatcher> matchers,
		AuthorizationManager<RequestAuthorizationContext> manager) {
		for (RequestMatcher matcher : matchers) {
			this.registry.addMapping(matcher, manager);
		}
		return this.registry;
	}
}

我们通过lambda表达式:

requestMatcherRegstry -> requestMatcherRegstry.requestMatchers("/admin/**").hasRole("ADMIN")

配置的正是AuthorizationManagerRequestMatcherRegistry
requestMachers方法,构建出AuthorizedUrl,然后通过这个类的hasRole方法注册当前路径所对应的权限/角色。这个对应关系由RequestMatcherEntry保存。key:RequestMatcher requestMatcher;value: AuthorizationManager。

值得一提的是,这个lambda表达式以及其链式调用看起来简单方便,但是其内部涉及多个类的方法调用,实在很容易犯迷糊,这是我觉得比较诟病的地方。在我看来,链式调用还是同一个返回值(每次都返回this)才能做到在方便至于也能清晰明了,容易理解。
而这里在lambda表达式内部:

  • 第一个方法是requestMatcherRegstry.requestMatchers
    AbstractRequestMatcherRegistry,也就是我们的AuthorizationManagerRequestMatcherRegistry的父类。方法返回值是AuthorizedUrl。
  • 第二个方法是AuthorizedUrl.hasRole
    而该方法的返回值为AuthorizationManagerRequestMatcherRegistry

发现什么了吗?链式调用还能玩起递归,又回到最开始的第一个方法了。而要是我们配置HttpSecurity,直接一连串的链式调用,那更是没谱了。经常就是,你只能看着别人这样配置,然后照猫画虎。这个链式调用咋调回来的,一头雾。因为中间可能跨越好几个不同的类。。。
PS:可能官方也有些意识到这点,所以sample工程都是类似于本文开头的那样,传入一个基于lambda表达式的Customizer。一个方法配置一个过滤器的SecurityConfigurer。但,如果你翻看源码,你看到的就是一连串的链式调用。最为明显的一个证明就是HttpSecurity#and方法过期了。因此个人推荐大家用文章开头的那种方法,相对清晰易理解。

我想说,这么玩是深怕别人搞明白了是吗???更绝的是,即便你知晓了原理也没有办法直接注册对应关系,除非你使用反射!

这里给大家提个醒,如果你想搞明白你在使用SpringSecurity究竟在配置些什么,那么你就必须要搞明白上面的套路。

设计方案

Spring Security在5.5版本之后,在鉴权架构上,进行了较大的改动。以至于官方也出了迁移指南

组件 5.5之前 5.5之后
过滤器 FilterSecurityInterceptor AuthorizationFilter
鉴权管理器 AccessDecisionManager AuthorizationManager
访问决策投票员 AccessDecisionVoter -

而原来的设计方案,相较于新的方案,更为复杂。这里给大家一张官方的UML感受感受:
authorizationmanagerrequestmatcherregistry,探索Spring Security,spring,springboot
除却过滤器外,还需要三个组件来构建完整的鉴权:
AccessDecisionManager 、AccessDecisionVoter 、ConfigAttribute。

感兴趣的同学可以自己琢磨琢磨,但已经废弃的方案,这里就不讨论了。

5.6之后的新方案

新方案只有一个包罗万象、且极具扩展性的AuthorizationManager
authorizationmanagerrequestmatcherregistry,探索Spring Security,spring,springboot
我们前面的配置demo,本质上都是在配置RequestMatcherDelegatingAuthorizationManager。他主要是记录每一个路径对应的AuthorizationManager<HttpServletRequest>。当有请求过来时,只需要遍历每一个路径,当找到匹配者就委托该AuthorizationManager<HttpServletRequest>进行鉴权。

在我们的配置demo中,对应的是AuthoriztyAuthorizationManagerAuthenticatedAuthorizationManager。前者,意味着我们配置的是角色/权限,后者对应的是authenticated()这个方法。

如果你认真看了这个关系图,那么一定会发现右边的4个实现类正是我们在上一文讲述基于方法配置权限中所使用到的。

鉴权源码分析

权限过滤的入口:AuthorizationFilter

public class AuthorizationFilter extends GenericFilterBean {

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
			throws ServletException, IOException {
		// 类型转换
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		// 是否需要执行鉴权
		if (this.observeOncePerRequest && isApplied(request)) {
			chain.doFilter(request, response);
			return;
		}
		// /error和异步请求不处理
		if (skipDispatch(request)) {
			chain.doFilter(request, response);
			return;
		}
		// 是否已经执行过鉴权逻辑了
		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
		try {
			// 从SecurityContextHolder中获取凭证,并通过AuthorizationManager做出决策
			AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
			
			// 发布鉴权事件
			this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
			if (decision != null && !decision.isGranted()) {
				// 拒绝访问异常
				throw new AccessDeniedException("Access Denied");
			}
			// 正常执行后续业务逻辑
			chain.doFilter(request, response);
		}
		finally {
			// 处理完业务逻辑后,为当前请求清理标识			
			request.removeAttribute(alreadyFilteredAttributeName);
		}
	}
}

RequestMatcherDelegatingAuthorizationManager

public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
		// 遍历每一个已经登录好的路径,找到对应的AuthorizationManager<RequestAuthorizationContext>>
		for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {

			RequestMatcher matcher = mapping.getRequestMatcher();
			// 匹配当前请求
			MatchResult matchResult = matcher.matcher(request);
			if (matchResult.isMatch()) {
				// 找到匹配的AuthorizationManager就直接调用check方法并返回鉴权结果
				AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
				return manager.check(authentication,
						new RequestAuthorizationContext(request, matchResult.getVariables()));
			}
		}
		// 没有匹配的AuthorizationManager则返回拒绝当前请求
		return DENY;
	}
}

可见,在没有匹配的AuthorizationManager的情况下,默认是拒绝请求的。

总结

  1. 我们在配置中配置的url被封装成RequestMatcher,而hasRole被封装成AuthorityAuthorizationManager。进行注册,在请求过来时,便通过遍历所有注册好的RequestMatch进行匹配,存在匹配就调用AuthorizationManager<RequestAuthorizationContext>#check方法。

  2. 配置的链式调用,会跨越多个不同的类,最终又回到第一个对象的类型。

后记

本文我们聊了基于HttpRequest配置权限的方方面面。相信这里有一个点应该会引起大家的注意:配置。下一次,我们聊聊Spring Security的配置体系。文章来源地址https://www.toymoban.com/news/detail-847816.html

到了这里,关于Spring Security之基于HttpRequest配置权限的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 安全框架Spring Security是什么?如何理解Spring Security的权限管理?

    大家好,我是卷心菜。本篇主要讲解Spring Security的基本介绍和架构分析,如果您看完文章有所收获,可以三连支持博主哦~,嘻嘻。 🎁 作者简介: 在校大学生一枚,Java领域新星创作者,Java、Python正在学习中,期待和大家一起学习一起进步~ 💗 个人主页:我是一棵卷心菜的

    2024年02月02日
    浏览(43)
  • 13.Spring security权限管理

    13.1什么是权限管理 Spring security支持多种不同的认证方式,但是无论开发者使用哪种认证方式,都不会影响授权功能的使用,spring security很好地实现了认证和授权两大功能的解耦。 13.2Spring security权限管理策略 从技术上来说,spring security中提供的权限管理功能主要有两种类型

    2023年04月12日
    浏览(63)
  • 【Spring Security系列】一文带你了解权限框架与Spring Security核心概念

    权限框架是软件开发中用于管理 用户权限和访问控制 的工具。在企业或者我们毕设复杂的系统中,不同的用户或角色需要拥有不同的访问和操作权限,以确保系统的安全性和数据完整性。今天我们就讨论一下Java中的安全框架! 在企业的开发中,Spring Security,Shiro都是比较流

    2024年04月16日
    浏览(32)
  • SpringBoot整合Spring Security实现权限控制

    要对Web资源进行保护,最好的办法莫过于Filter 要想对方法调用进行保护,最好的办法莫过于AOP。 Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。 如图所示,一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中 绿色部分是负责认证的

    2024年02月15日
    浏览(30)
  • Spring Security--守护你的功能权限

    首先,让我们明确一下什么是Spring Security以及前后端分离路径拦截器。Spring Security是一个基于Spring框架的安全框架,它提供了一系列的安全服务,包括但不限于认证、授权、加密和会话管理等。而前后端分离路径拦截器是指在前后端分离的情况下,根据用户角色和权限对请求

    2024年02月06日
    浏览(37)
  • Spring Security 中自定义权限表达式

    前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 通过编程授权方法 首先,声明一个 Bean,如下所示: 然后,在注解中以如下方式引用该 Bean: Spring Security 将在每次方法调用时调用该Bean上的给定方法。 这样

    2024年02月06日
    浏览(39)
  • Spring Security实现用户身份验证及权限管理

    Spring Security是Spring生态的一个成员,提供了一套Web应用安全性的完整解决方案。 Spring Security 旨在以一种自包含的方式进行操作,因此你不需要在 Java 运行时环境中放置任何特殊的配置文件。这种设计使部署极为方便,因为可以将目标 工件 (无论是 JAR还是WAR)从一个系统复

    2024年02月05日
    浏览(30)
  • 【业务功能篇57】Springboot + Spring Security 权限管理 【上篇】

    4.1.1 权限管理的意义 后台管理系统中,通常需要控制不同的登录用户可以操作的内容。权限管理用于管理系统资源,分配用户菜单、资源权限,以及验证用户是否有访问资源权限。 4.1.2 RBAC权限设计模型 ACL介绍 ACL(Access Control List):访问控制列表 用户 - 权限 ACL 模型,权限

    2024年02月15日
    浏览(30)
  • 微服务动态权限管理方案(Spring Cloud Gateway+Spring Cloud Security)

    微服务认证方案的大体方向是统一在网关层面认证鉴权,微服务只负责业务,和鉴权完全隔离 整体包含以下四个角色 客户端 :需要访问微服务资源 网关 :负责转发、认证、鉴权 OAuth2.0授权服务 :负责认证授权颁发令牌 微服务集合 :提供资源的一系列服务。 这里的客户端

    2024年02月12日
    浏览(41)
  • Spring Security 之方法级的权限管控 @PreAuthorize 使用详解

    默认情况下, Spring Security 并不启用方法级的安全管控. 启用方法级的管控后, 可以针对不同的方法通过注解设置不同的访问条件. Spring Security 支持三种方法级注解, 分别是 JSR-205 注解 /@Secured 注解 / prePostEnabled 注解. 这些注解不仅可以直接加 controller 方法上, 也可以注解 Service 或

    2024年04月26日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包