Spring Security之基于方法配置权限

前言

Spring Security有两种配置方式,今天重点是绍基于方法配置的方式。

基于方法配置权限

这个主要是有一些注解提供给大家使用,今天来给大家一个demo(参考自官方sample)。

maven就不多累赘了。重点看看配置。

  • 基于角色配置
/**
 * 启用方法安全:@EnableMethodSecurity
 * 
 * 启用@secured注解: **securedEnabled = true**
 * <p>会导入配置:SecuredMethodSecurityConfiguration<p>
 * 
 * 启用@PreAuthorize@PostAuthorize@PreFilter@PostFilter:**prePostEnabled = false**
 * <p>会导入配置:PrePostMethodSecurityConfiguration</p>
 * 
 * 启用jsr250相关的安全注解:**jsr250Enabled = true**
 * <p>会导入配置:Jsr250MethodSecurityConfiguration</p>
 * <p>jsr250包括@RolesAllowed@PermitAll@DenyAll</p>
 */ 
@Configuration
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class AspectjSecurityConfig {

}

/**
 * 这个类的所有方法都需要有ROLE_USER角色才能执行
 */
@Service
@Secured("ROLE_USER")
public class SecuredService {

	public void secureMethod() {
		// nothing
	}

}

@Service
public class SecuredService {
    

	public void publicMethod() {
		// nothing
	}
    /**
     * 这个方法需要ROLE_USER才能执行
     * 这三种配置方式都是等价的,只是提供支持的Advice不同。
     */ 
	@Secured("ROLE_USER")
    // @RolesAllowed("ROLE_USER")
    // @PreAuthorize("hasRole('ROLE_USER')")
	public void secureMethod() {
		// nothing
	}

	@PreAuthorize("arguments[0] ne 'tony'")
	// @PreAuthorize("filterObject ne 'tony'")
    public void preAuthorize(@RequestParam("userCode") String userCode) {
        // @preAuthorize不会赋值ROOT的filterObject和returnObject,因此无法使用入参。只能使用MethodSecurityExpressionRoot的其他方法
		// 这个方法的实验现象为:不管传什么都能通过表达式
        logger.info("preAuthorize:{}", userCode);
        // nothing
    }

    /** 	
     * 这个入参会被过滤地只剩下与authentication.name一样的
     */ 
	@PreFilter("filterObject == authentication.name")
	public void preFilter(@RequestParam("userCodeList") List<String> userCodeList) {
		// http://localhost:8090/foo/preFilter?userCodeList=leo,tony
        logger.info("preAuthorize:{}", userCode);
	}

    /**
     * 这个方法需要ROLE_USER才能执行
     */ 
	@PostAuthorize("returnObject ne 'tony'")
	public String postAuthorize(@RequestParam("userCode") String userCode) {
		// 传入的是不是tony,会抛出异常: 403
        logger.info("postAuthorize:{}", userCode);
		return userCode;
	}

    @GetMapping("/preAuthorizeSpel")
    @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
    public void preAuthorizeHasRole() {
        logger.info("preAuthorizeSpel:{}", userCode);
        // nothing
    }

    /**
     * 这个方法需要ROLE_USER才能执行
     */ 
	@PostFilter("returnObject == authentication.name")
	public List<String> PostFilter(@RequestParam("userCodeList") List<String> userCodeList) {
		// 实验结果就是,传入了的参数有值,但只有跟用户名一样的才会返回
        logger.info("PostFilter:{}", userCodeList);
		return userCodeList;
	}

}

以上就是怎么使用,接着我们看下是如何实现的。

基于方法授权方式的实现原理

前面我们说过,是基于AOP实现的。那么,现在我们从源码层面来看看。

我们可以看到上面按照@EnableMethodSecurity的配置,分别对应地导入三个配置。
但是,我们可以先从AOP的角度设想一下,我们需要的是哪种类型的通知?不妨归类一下:

注解类型通知类型描述/备注
@PreFilter@PreAuthorize@Secured以及JSR-250的相关注解前置通知这些很明显都是需要先校验/过滤参数再执行目标方法
@PostFilter@PostAuthorize后置通知先执行目标方法,再校验/过滤结果集

接着我们来看看Spring Security的设计:

AuthorizationManagerBeforeMethodInterceptor

在执行方法之前进行鉴权,这也意味着,当权限不足时,他会抛出异常。
实际上,他是一个通用的增强,全取决于你怎么使用它。
在SpringSecurity的配置里,他可以负责@PreAuthor,也可以负责@Secured,甚至还能负责jsr250的相关注解。
需要提醒一点,一个实例对象只能一种注解哈。

但是,大家有没有想过一个问题:为什么@PreAuthor@Secured以及jsr250的相关注解,都能交给他来处理?
又或者说,他的设计是如何将这三者的处理抽象统一起来的?更具体一点,此三者有何共同之处,可以进行抽象和统一的??

要回答这个问题,我们需要先从这个三者的入手:
共同点:

都需要在执行方法前进行权限校验,校验不通过则都需要抛出异常,阻断方法调用。

异同点:

注解配置描述执行
@PreAuthorize配置的是SPEL表达式通过执行表达式来得出是否满足访问权限
@Secured指定角色需要校验当前用户是否拥有指定角色
jsr250的@PermitAll-任何人都可以访问
jsr250的@DenyAll-任何人都不能访问
jsr250的@RolesAllowed指定角色需要校验当前用户是否拥有指定角色

从共同点出发,本质上无非就是鉴权嘛,这不是很符合Spring Security的AuthorizationManager的职责吗?
然后就是不同点,我们发现无非就是权限的配置来源不同需要解析不同的注解咯。
有了这个思路,接下来我们翻找源码,理解起来就容易多了。

public final class AuthorizationManagerBeforeMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
	// 构造器,需要传入Pointcut,和AuthorizationManager<MethodInvocation>,以便校验权限。
	public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,
			AuthorizationManager<MethodInvocation> authorizationManager) {
		Assert.notNull(pointcut, "pointcut cannot be null");
		Assert.notNull(authorizationManager, "authorizationManager cannot be null");
		this.pointcut = pointcut;
		this.authorizationManager = authorizationManager;
	}

	/**
	 * 创建负责处理@PreAuthorize的增强
	 */
	public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
			PreAuthorizeAuthorizationManager authorizationManager) {
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
		interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
		return interceptor;
	}

	/**
	 * 创建负责处理@Secured
	 */
	public static AuthorizationManagerBeforeMethodInterceptor secured(
			SecuredAuthorizationManager authorizationManager) {
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager);
		interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder());
		return interceptor;
	}

	/**
	 * 创建负责处理jsr250相关注解: @RolesAllow@PermitAll@DenyAll
	 */
	public static AuthorizationManagerBeforeMethodInterceptor jsr250(Jsr250AuthorizationManager authorizationManager) {
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class),
				authorizationManager);
		interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder());
		return interceptor;
	}
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 校验权限
		attemptAuthorization(mi);
		return mi.proceed();
	}
	
	private void attemptAuthorization(MethodInvocation mi) {
		// 通过AuthorizationManager校验权限,这意味者AuthorizationManager必须具备解析相关注解的能力,实际上他交给了另外一个组件,后面会说到。
		AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
		// 发布授权事件
		this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
		// 不通过就抛出访问拒绝异常
		if (decision != null && !decision.isGranted()) {
			throw new AccessDeniedException("Access Denied");
		}
	}

从源码,我们可以发现之前分析的三类注解,只需要一个MethodInterceptor。而他们的不同之处则由AuthorizationManager这个同一个的接口进行统一调度。
对应地:

注解AuthorizationManager
@PreAuthorizePreAuthorizeAuthorizationManager
@SecuredSecuredAuthorizationManager
jsr250的注解Jsr250AuthorizationManager
  • SecuredAuthorizationManager:
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
	// 这是个最为简单的AuthorizationManager实现,无非就是将当前用户的权限与所需要的权限进行比较,如果找到就认为拥有访问权限。
	private AuthorizationManager<Collection<String>> authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
	// 缓存已经解析过的方法所对应的权限
	private final Map<MethodClassKey, Set<String>> cachedAuthorities = new ConcurrentHashMap<>();

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
		// 获取目标方法配置的访问权限
		Set<String> authorities = getAuthorities(mi);
		// 比较权限,从authoritiesAuthorizationManager.check方法的入参也能大概猜到怎么实现
		return authorities.isEmpty() ? null : this.authoritiesAuthorizationManager.check(authentication, authorities);
	}

	private Set<String> getAuthorities(MethodInvocation methodInvocation) {
		Method method = methodInvocation.getMethod();
		Object target = methodInvocation.getThis();
		Class<?> targetClass = (target != null) ? target.getClass() : null;
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		// 如果尚未存在相关缓存,则进行解析。
		// 由此可见,只有当目标方法被第一次执行/调用的时候才会出发解析动作
		return this.cachedAuthorities.computeIfAbsent(cacheKey, (k) -> resolveAuthorities(method, targetClass));
	}

	private Set<String> resolveAuthorities(Method method, Class<?> targetClass) {
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
		// 尝试在方法上寻找目标注解@Secured
		Secured secured = findSecuredAnnotation(specificMethod);
		// 返回@Secured注解所配置的权限
		return (secured != null) ? Set.of(secured.value()) : Collections.emptySet();
	}
}
  • Jsr250AuthorizationManager
    相较于SecuredAuthorizationManager,他负责的则是3个注解,而不是一个。
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
	// 这是AuthorizationManager的注册器
	// 因为要处理三个注解,每个注解的处理逻辑虽然简单,但是确实不一样。
	// 而每个方法存在的注解也不一样
	private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
		// 根据方法,从注册器中获取对应的AuthorizationManager
		AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
		// 执行授权校验逻辑
		return delegate.check(authentication, methodInvocation);
	}

	// 这是Jsr250AuthorizationManager的内部类
	private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
		@NonNull
		@Override
		AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
			// 解析注解
			Annotation annotation = findJsr250Annotation(method, targetClass);
			if (annotation instanceof DenyAll) {
				// 返回一个lambda表达式构建的AuthorizationManager,其实现为:直接返回拒绝访问
				return (a, o) -> new AuthorizationDecision(false);
			}
			if (annotation instanceof PermitAll) {
				// 返回一个lambda表达式构建的AuthorizationManager,其实现为:直接返回允许访问
				return (a, o) -> new AuthorizationDecision(true);
			}
			if (annotation instanceof RolesAllowed) {
				RolesAllowed rolesAllowed = (RolesAllowed) annotation;
				// 返回与@Secured一样的AuthorityAuthorizationManager
				return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
						rolesAllowed.value());
			}
			// 这里应当看到,这三个注解的判断顺序。事实上,在解析注解的时候,只会返回其中之一。
			// 而jsr250的注解也是不能同时使用的,只能用其中一个。
			return NULL_MANAGER;
		}
	}
}

abstract class AbstractAuthorizationManagerRegistry {
	private final Map<MethodClassKey, AuthorizationManager<MethodInvocation>> cachedManagers = new ConcurrentHashMap<>();

	final AuthorizationManager<MethodInvocation> getManager(MethodInvocation methodInvocation) {
		Method method = methodInvocation.getMethod();
		Object target = methodInvocation.getThis();
		Class<?> targetClass = (target != null) ? target.getClass() : null;
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		// 这里就跟@Secured类似了,只不过其需要兼顾3个注解,因此value变成了AuthorizationManager
		return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));
	}	
	
	abstract AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass);
}

  • PreAuthorizeAuthorizationManager
    与他的同伴不同,他可是要支持SPEL的。
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
	private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
		// 通过方法,从注册器获取ExpressionAttribute。
		ExpressionAttribute attribute = this.registry.getAttribute(mi);
		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
			return null;
		}
		// 通过注册器找到ExpressionHandler,并创建EvaluationContext
		EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
		// 执行SPEL表达式
		boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
		// 返回决策:是否允许访问
		return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
	}
}

这里我们看到了为了支持SPEL的第一个抽象:ExpressionAttribute。他最重要的使命就是记录表达式。
再来看看,最为重要的:PreAuthorizeExpressionAttributeRegistry

final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
	private final MethodSecurityExpressionHandler expressionHandler;

	@Override
	ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
		// 寻找@PreAuthorize注解
		PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
		if (preAuthorize == null) {
			return ExpressionAttribute.NULL_ATTRIBUTE;
		}
		// 解析表达式
		Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
			.parseExpression(preAuthorize.value());
		// 返回解析到的表达式
		return new ExpressionAttribute(preAuthorizeExpression);
	}
}

abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
	// 缓存方法对应的表达式
	private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();

	final T getAttribute(Method method, Class<?> targetClass) {
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		// 解析并缓存表达式
		return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
	}
}

AuthorizationManagerAfterMethodInterceptor

他与前面介绍的MethodInterceptor相呼应,一前一后,你从名字就能发现。只不过与前者相比,目前他只有一个注解需要关注@PostAuthorize。

public final class AuthorizationManagerAfterMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {

	private final AuthorizationManager<MethodInvocationResult> authorizationManager;
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 先执行
		Object result = mi.proceed();
		// 再校验。这里应当注意到他的入参包括方法返回值
		attemptAuthorization(mi, result);
		return result;
	}
	
	private void attemptAuthorization(MethodInvocation mi, Object result) {
		// 熟悉的配方,只不过入参变成了方法返回值罢了
		MethodInvocationResult object = new MethodInvocationResult(mi, result);
		AuthorizationDecision decision = this.authorizationManager.check(this.authentication, object);

		this.eventPublisher.publishAuthorizationEvent(this.authentication, object, decision);
		if (decision != null && !decision.isGranted()) {
			throw new AccessDeniedException("Access Denied");
		}
	}
}

public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {

	private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
	
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult mi) {
		ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());
		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
			return null;
		}

		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
		expressionHandler.setReturnObject(mi.getResult(), ctx);

		boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
		return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
	}
}

经过了前面的分析,这里也就没啥多说的了。与@PreAuthorize很相似。

PreFilterAuthorizationMethodInterceptor

负责处理@PreFilter注解的方法调用。

public final class PreFilterAuthorizationMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {

	public PreFilterAuthorizationMethodInterceptor() {
		// 默认处理的是@PreFilter
		this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);
	}
	// ...

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 从属性注册器中获取到目标方法解析好的@PreFilter的相关属性信息。
		PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
		if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
			return mi.proceed();
		}
		// 从属性注册器中获取对应的SPEL的表达式处理器,以便创建SPEL的上下文
		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
		EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
		// 从表达式上下文中获取需要过滤的目标
		Object filterTarget = findFilterTarget(attribute.getFilterTarget(), ctx, mi);
		// 根据SPEL表达式执行过滤
		expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
		// 执行目标方法
		return mi.proceed();
	}

	// ...
}

可以看出,与@PreAuthorize类似,只是少了一层AuthorizationManager的封装。原因也很简单,AuthorizationManager是用来鉴权的,而@PreFilter不需要鉴权。
只需要过滤参数即可。因此他也不会抛出访问异常。

PostFilterAuthorizationMethodInterceptor

负责处理@PostFilter。

public final class PostFilterAuthorizationMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
	// 这个注册器与PreAuthorizeExpressionAttributeRegistry类似,都继承同一个父类。
	// 只有解析的注解不一样,对应ExpressionAttribute稍稍不一样,前者记录有方法参数,而后者记录有方法返回值。
	private PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();

	public PostFilterAuthorizationMethodInterceptor() {
		this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);
	}
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 先执行了方法,获得返回值
		Object returnedObject = mi.proceed();
		ExpressionAttribute attribute = this.registry.getAttribute(mi);
		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
			return returnedObject;
		}
		// 获取ExpressionHandler,创建EvaluationContext
		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
		EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
		// 执行过滤逻辑
		return expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
	}

我们可以看到@PostFilter,只是对返回进行过滤,同样也不会抛出访问异常。

小结

分析完源码之后,我们不难发现,每一个类都很小,且每一个方法都足够简单。这点是值得大家学习的。
当然,不能盲目。因为作为一款优秀的框架,必须要具备良好的可扩展性。通过分析和抽象,能够很好起到这个作用。这是框架源码设计者所追求的。
但是其弊端也很明显,那就是一个完整的功能实现,往往需要诸多组件协作完成,初学者很难入门。因此,在实际业务开发过程中,不能盲目地追求这种扩展性。
同时,面对复杂业务时,不妨多分析,是否可以像框架源码的设计者那样思考?当然,设计完成后,需要留下相当的文档。

总结

各方法注解的应用场景

注解应用场景实现原理
@Secured配置需要满足的权限,可以是角色名或者权限基于AuthorityAuthorizationManager
JSR-250的@PermitAll任何人都可以访问基于lambda表达式实现的简单的AuthorizationManager
JSR-250的@DenyAll任何人都不能访问基于lambda表达式实现的简单的AuthorizationManager
JSR-250的@RolesAllowed配置需要满足的权限,可以是角色名或者权限。可以于@Secured相互取代。基于AuthorityAuthorizationManager,可以于@Secured相互取代。
@PreAuthorize用于在方法调用之前鉴权,支持方法入参作为表达式的一部分基于SPEL表达式
@PostAuthorize用于在方法调用之后鉴权,支持方法返回值作为表达式的一部分基于SPEL表达式
@PreFilter用于过滤方法入参基于SPEL表达式
@PostFilter用于过滤方法返回值基于SPEL表达式

在搞清楚了各注解的用处和使用后,我们在回过头来看,我们可以使用哪些授权方式:

  1. RBAC(基于角色的访问控制)
    这个除了@PreFilter@PostFilter,其他注解都能实现。SPEL可以使用hasAnyRole("ADMIN","USER")

后记

至此,我们应该算全方面聊完了基于方法注解怎么配置权限这个话题。不管各个注解的使用场景,亦或者其实现原理,还是各种设计思路。
下一节,我们将聊聊基于HttpRequest的权限配置方式。这个直接使用倒是简单,但是想要定制就必须深入理解其设计和原理。加油。

参照

Method Security

Spring Security的@PreAythorize、@PostAuthorize、@PreFilter 和@PostFilter

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于基于 RBAC(Role-Based Access Control)的权限控制,可以使用 Spring Boot 和 Spring Security 来实现。下面是一个简单的步骤指南: 1. 添加依赖:在你的 Spring Boot 项目的 pom.xml 文件中,添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 创建用户和角色实体:创建用户(User)和角色(Role)的实体类,可以使用 JPA 或者其他持久化框架来进行数据库操作。 3. 实现 UserDetailsService:创建一个实现了 Spring Security 的 UserDetailsService 接口的类,用于加载用户信息。这个类需要重写 loadUserByUsername 方法,根据用户名从数据库中查询用户信息并返回一个 UserDetails 对象。 4. 创建权限访问控制配置类:创建一个配置类,继承自 WebSecurityConfigurerAdapter,并重写 configure 方法。在这个方法中,你可以配置哪些 URL 需要哪些角色或权限才能访问。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and().formLogin().permitAll() .and().logout().permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public UserDetailsService userDetailsService() { // 返回自定义的 UserDetailsService 实现类 // 在这个实现类中通过 JPA 或其他方式查询用户信息 return new CustomUserDetailsService(); } } ``` 5. 配置密码加密:在上面的配置类中,我们使用了 BCryptPasswordEncoder 作为密码加密方式。确保你的用户表中保存的密码是经过 BCrypt 加密的。 6. 创建登录页面:创建一个登录页面,可以是一个简单的 HTML 页面或者使用模板引擎进行渲染。 7. 配置登录页面:在 application.properties 或 application.yml 文件中,配置登录页面的路径和其他相关属性。 ```properties spring.security.login-page=/login spring.security.logout-success-url=/login?logout ``` 以上步骤完成后,你的 Spring Boot 应用程序就可以基于 RBAC 实现简单的权限控制了。根据实际需求,你可以进一步扩展和定制 Spring Security 的功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值