文章目录

  • 1. 权限管理
    • 1. 授权核心概念
    • 2. 权限管理策略
  • 2. 基于 URL 权限管理
  • 3. 基于 方法 权限管理
  • 4. 授权流程
    • 1. FilterSecurityInterceptor#invoke
    • 2. AbstractSecurityInterceptor#beforeInvocation
      • 步骤1:DefaultFil#terInvocationSecurityMetadataSource#getAttributes
      • 步骤2:AbstractSecurityInterceptor#authenticateIfRequired
      • 步骤3:AbstractSecurityInterceptor#attemptAuthorization

1. 权限管理

1. 授权核心概念

身份认证 ,就是判断一个用户是否为合法用户的处理过程。Spring Security 中支持多种不同方式的认证,但是无论开发者使用那种方式认证,都不会影响授权功能使用。因为Spring Security 很好做到了认证和授权解耦。

授权 ,即访问控制,控制谁能访问哪些资源。简单的理解授权就是根据系统提前设置好的规则,给用户分配可以访问某一个资源的权限,用户根据自己所具有权限,去执行相应操作。

认证成功之后会将当前登录用户信息保存到Authentication 对象中,Authentication 对象中有一个 getAuthorities() 方法,用来返回当前登录用户具备的权限信息,也就是当前用户具有权限信息。该方法的返回
值为 Collection<? extends GrantedAuthority>,当需要进行权限判断时,就回根据集合返回权限信息调用相应方法进行判断。

当客户端发起请求时,身份认证过滤器会对用户进行身份认证,身份认证成功后,身份认证过滤器会将用户详情信息存储在安全上下文中,并将请求转发给授权过滤器,授权过滤器决定是否允许调用。

public interface Authentication extends Principal, Serializable {// 由 AuthenticationManager 设置以指示已授予主体的权限。Collection<? extends GrantedAuthority> getAuthorities();// 返回身份认证过程中返回的密码或者任何秘钥Object getCredentials();// 存储有关身份验证请求的其他详细信息。 这些可能是 IP 地址、证书序列号等。Object getDetails();// 被认证的主体的身份。// 被认证的主体的身份。 在使用用户名和密码的身份验证请求的情况下,这将是用户名。 调用者应填充身份验证请求的主体。// AuthenticationManager 实现通常会返回一个包含更丰富信息的 Authentication 作为应用程序使用的主体。 许多身份验证提供程序将创建一个 UserDetails 对象作为主体。Object getPrincipal();// 如果身份认证程序结束则返回true,如果正在进行则返回falseboolean isAuthenticated();void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;}
public interface GrantedAuthority extends Serializable {// 返回权限的名称String getAuthority();
}

那么问题来了,针对于这个返回值 GrantedAuthority 应该如何理解呢? 是⻆色还是权限?

我们针对于授权可以是 基于⻆色权限管理 和 基于资源权限管理 ,从设计层面上来说,⻆色和权限是两个完全不同的东⻄:权限是一些具体操作,⻆色则是某些权限集合。如:READ_BOOK 和 ROLE_ADMIN 是完全不同的。因此至于返回值是什么取决于你的业务设计情况:

基于⻆色权限设计就是: 用户–》⻆色–》资源,返回就是用户的 ⻆色
基于资源权限设计就是: 用户–》权限–》资源,返回就是用户的 权限
基于⻆色和资源权限设计就是: 用户–》⻆色–》权限–》资源,返回统称为用户的权限

为什么可以统称为权限,因为从代码层面⻆色和权限没有太大不同都是权限,特别是在Spring Security 中,⻆色和权限处理方式基本上都是一样的。唯一区别SpringSecurity 在很多时候会自动给⻆色添加一个 ROLE_ 前缀,而权限则不会自动添加。

2. 权限管理策略

Spring Security 中提供的权限管理策略主要有两种类型:

基于过滤器(URL)的权限管理 (FilterSecurityInterceptor) :基于过滤器的权限管理主要是用来拦截 HTTP 请求,拦截下来之后,根据 HTTP请求地址进行权限校验。

基于 AOP (方法)的权限管理 (MethodSecurityInterceptor):基于 AOP 权限管理主要是用来处理方法级别的权限问题。当需要调用某一个方法时,通过 AOP 将操作拦截下来,然后判断用户是否具备相关的权限。

2. 基于 URL 权限管理

@RestController
public class DemoController {@GetMapping("/admin")  //ADMINpublic String admin() {return "admin ok";}@GetMapping("/user")  //USERpublic String user() {return "user ok";}@GetMapping("/getInfo")  //READ_INFOpublic String getInfo() {return "info ok";}
}
/*** 自定义 spring security 配置类*/
@Configuration
public class SecurityConfig  extends WebSecurityConfigurerAdapter {//创建内存数据源@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN","USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("lisi").password("{noop}123").roles("USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("win7").password("{noop}123").authorities("READ_INFO").build());return inMemoryUserDetailsManager;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//具有 admin 角色   通用: /admin /admin/  /admin.html.mvcMatchers(HttpMethod.GET,"/admin").hasRole("ADMIN")//具有 user 角色.mvcMatchers("/user").hasRole("USER")//READ_INFO 权限.mvcMatchers("/getInfo").hasAuthority("READ_INFO").antMatchers(HttpMethod.GET,"/admin").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().and().csrf().disable();}
}

访问:http://localhost:8080/user,使用root/123登录后可访问,再访问http://localhost:8080/getInfo 则会报错403异常。

3. 基于 方法 权限管理

基于方法的权限管理主要是通过 A0P 来实现的,Spring Security 中通过MethodSecurityInterceptor 来提供相关的实现。不同在于FilterSecurityInterceptor 只是在请求之前进行前置处理,MethodSecurityInterceptor 除了前置处理之外还可以进行后置处理。前置处理就是在请求之前判断是否具备相应的权限,后置处理则是对方法的执行结果进行二次过滤。前置处理和后置处理分别对应了不同的实现类。

@EnableGlobalMethodSecurity 该注解是用来开启权限注解,用法如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true, jsr250Enabled=true)
public class SecurityConfig extends WebsecurityConfigurerAdapter{}

perPostEnabled: 开启 Spring Security 提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter;

securedEnabled: 开启 Spring Security 提供的 @Secured 注解支持,该注解不支持权限表达式;

jsr250Enabled: 开启 JSR-250 提供的注解,主要是@DenyAll、@PermitAll、@RolesAll 同样这些注解也不支持权限表达式;

@PostAuthorize: 在目前标方法执行之后进行权限校验。

@PostFiter: 在目标方法执行之后对方法的返回结果进行过滤。

@PreAuthorize:在目标方法执行之前进行权限校验。

@PreFiter:在目前标方法执行之前对方法参数进行过滤。

@Secured:访问目标方法必须具各相应的⻆色。

@DenyAll:拒绝所有访问。

@PermitAll:允许所有访问。

@RolesAllowed:访问目标方法必须具备相应的⻆色。

@RestController
@RequestMapping("/hello")
public class AuthorizeMethodController {// @PreAuthorize("hasRole('ADMIN')  and authentication.name =='win7'")@PreAuthorize("hasAuthority('READ_INFO')")@GetMappingpublic String hello() {return "hello";}@PreAuthorize("authentication.name==#name")@GetMapping("/name")public String hello(String name) {return "hello:" + name;}// filterTarget 必须是数组集合类型@PreFilter(value = "filterObject.id%2!=0",filterTarget = "users") @PostMapping("/users")public void addUsers(@RequestBody List<User> users) {System.out.println("users = " + users);}@PostAuthorize("returnObject.id==1")@GetMapping("/userId")public User getUserById(Integer id) {return new User(id, "blr");}// 用来对方法返回值进行过滤@PostFilter("filterObject.id%2==0")@GetMapping("/lists")public List<User> getAll() {List<User> users = new ArrayList<>();for (int i = 0; i < 10; i++) {users.add(new User(i, "blr:" + i));}return users;}// 只能判断角色@Secured({"ROLE_USER"}) @GetMapping("/secured")public User getUserByUsername() {return new User(99, "secured");}// 具有其中一个即可@Secured({"ROLE_ADMIN","ROLE_USER"}) @GetMapping("/username")public User getUserByUsername2(String username) {return new User(99, username);}@PermitAll@GetMapping("/permitAll")public String permitAll() {return "PermitAll";}@DenyAll@GetMapping("/denyAll")public String denyAll() {return "DenyAll";}// 具有其中一个角色即可@RolesAllowed({"ROLE_ADMIN","ROLE_USER"}) @GetMapping("/rolesAllowed")public String rolesAllowed() {return "RolesAllowed";}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Integer id;private String name;
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {//创建内存数据源@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN","USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("lisi").password("{noop}123").roles("USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("win7").password("{noop}123").authorities("READ_INFO").build());return inMemoryUserDetailsManager;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//具有 admin 角色   通用: /admin /admin/  /admin.html.mvcMatchers(HttpMethod.GET,"/admin").hasRole("ADMIN")//具有 user 角色.mvcMatchers("/user").hasRole("USER")//READ_INFO 权限.mvcMatchers("/getInfo").hasAuthority("READ_INFO").antMatchers(HttpMethod.GET,"/admin").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().and().csrf().disable();}
}

4. 授权流程

SpringSecurity存在一个称作 FilterSecurityInterceptor 的拦截器,该拦截器位于整个过滤器链的末端,核心功能是对权限控制过程进行拦截,即用来判断该请求能否访问HTTP端点。当对请求进行拦截后,下一步是获取请求的访问资源,以及访问这些资源所需要的权限信息,这一步称为权限配置。

ConfigAttribute 在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的⻆色会被封装成一个 ConfigAttribute 对象,在ConfigAttribute 中只有一个 getAttribute方法,该方法返回一个 String 字符
串,就是⻆色的名称。一般来说,⻆色名称都带有一个 ROLE_ 前缀,投票器AccessDecisionVoter 所做的事情,其实就是比较用户所具各的⻆色和请求某个资源所需的 ConfigAtuibute 之间的关系。

AccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在AccessDecisionManager 中会换个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AaccesDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager 的关系。

1. FilterSecurityInterceptor#invoke

// 实现了Servlet的Filter接口
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {private FilterInvocationSecurityMetadataSource securityMetadataSource;public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {this.securityMetadataSource = newSource;}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {invoke(new FilterInvocation(request, response, chain));}public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {// ...// 调用父类的beforeInvocation方法InterceptorStatusToken token = super.beforeInvocation(filterInvocation);// ...super.afterInvocation(token, null);}
}

2. AbstractSecurityInterceptor#beforeInvocation

public abstract class AbstractSecurityInterceptorimplements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {public abstract SecurityMetadataSource obtainSecurityMetadataSource();protected InterceptorStatusToken beforeInvocation(Object object) {// ... // 从SecurityMetadataSource中获取请求对应的ConfigAttribute集合(权限信息)Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);// ...// 是否需要认证,获取认证信息Authentication authenticated = authenticateIfRequired();// 执行授权attemptAuthorization(object, attributes, authenticated);// ...}
}

步骤1:DefaultFil#terInvocationSecurityMetadataSource#getAttributes

public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {protected final Log logger = LogFactory.getLog(getClass());private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) {final HttpServletRequest request = ((FilterInvocation) object).getRequest();int count = 0;for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : this.requestMap.entrySet()) {if (entry.getKey().matches(request)) {return entry.getValue();}else {if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Did not match request to %s - %s (%d/%d)", entry.getKey(),entry.getValue(), ++count, this.requestMap.size()));}}}return null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}}

步骤2:AbstractSecurityInterceptor#authenticateIfRequired

public abstract class AbstractSecurityInterceptorimplements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {private Authentication authenticateIfRequired() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Did not re-authenticate %s before authorizing", authentication));}return authentication;}authentication = this.authenticationManager.authenticate(authentication);// Don't authenticated.setAuthentication(true) because each provider does thatif (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Re-authenticated %s before authorizing", authentication));}SecurityContext context = SecurityContextHolder.createEmptyContext();context.setAuthentication(authentication);SecurityContextHolder.setContext(context);return authentication;}}

根据上下文对象中的 Authentication 对象判断用户是否通过身份认证,如果尚未通过身份认证,则调用AuthenticationManager 进行认证,并把 Authentication 对象存储在上下文对象中。

步骤3:AbstractSecurityInterceptor#attemptAuthorization

public abstract class AbstractSecurityInterceptorimplements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {private AccessDecisionManager accessDecisionManager;private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,Authentication authenticated) {try {this.accessDecisionManager.decide(authenticated, object, attributes);}// ...}
}
public class AffirmativeBased extends AbstractAccessDecisionManager {public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {int deny = 0;for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:return;case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default:break;}}if (deny > 0) {throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}
}

SpringSecurity系列 - 17 SpringSecurity 授权管理相关推荐

  1. SpringSecurity系列——会话管理,CSRFday8-1(源于官网5.7.2版本)

    SpringSecurity系列--会话管理,CSRFday8-1(源于官网5.7.2版本)) 会话管理 会话并发管理 限制会话实例 关闭session会话管理 开启会话管理并设置最大会话数为1 前后 ...

  2. java.securti_springboot集成springsecurity 使用OAUTH2做权限管理的教程

    Spring Security OAuth2 主要配置,注意application.yml最后的配置resource filter顺序配置,不然会能获取token但是访问一直 没有权限 WebSecu ...

  3. SpringSecurity系列——密码存储加密策略day7-1(源于官网5.7.2版本)

    SpringSecurity系列--密码存储加密策略day7-1(源于官网5.7.2版本) Password Storage(密码存储) 密码存储策略历史 委托密码编码器 创建默认 Delegatin ...

  4. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(17)-注册用户功能的细节处理(各种验证)...

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(17)-注册用户功能的细节处理(各种验证) ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框 ...

  5. SpringSecurity系列(四) Spring Security 实现权限树形菜单

    SpringSecurity系列(一) 初识 Spring Security SpringSecurity系列(二) Spring Security入门 SpringSecurity系列(三) Spr ...

  6. SpringSecurity系列(三) Spring Security 表单登录

    SpringSecurity系列(一) 初识 Spring Security SpringSecurity系列(二) Spring Security入门 1. 服务端 1.1 依赖 <?xml ...

  7. SpringSecurity系列之基于数据库认证

    SpringSecurity系列之基于数据库认证 本文中所使用的技术栈如下: SpringBoot 2.6.2 MyBatis Plus 3.5.0 SpringSecurity 5.6.1 一.创建 ...

  8. SpringSecurity系列——其他的权限控制,基于access表达式的权限控制day6-2(源于官网5.7.2版本)

    SpringSecurity系列--其他的权限控制,基于access表达式的权限控制day6-2(源于官网5.7.2版本) 常见权限控制总表 基于access表达式的权限控制 实例1:改写hasRol ...

  9. Flask扩展系列(八)–用户会话管理

    安装和启用 遵循标准的Flask扩展安装和启用方式,先通过pip来安装扩展: $ pip install Flask-Login 接下来创建扩展对象实例: 1 2 3 4 5 from flask i ...

最新文章

  1. 网页中 哪些是 GET 请求,哪些是 POST 请求
  2. python批量生成文件夹_python实现批量获取指定文件夹下的所有文件的厂
  3. 全球及中国天然气市场产销规模及十四五投资价值分析报告2021年版
  4. sap百分数表示Demo
  5. 《Python Cookbook 3rd》笔记(4.13):创建数据处理管道
  6. C语言头文件一般以什么名称结尾,c语言书写规范.doc
  7. 我对软件发展的思考,一个不变却一直在变的话题
  8. 芯故事 心感动:英特尔企业文化的力量
  9. 一起谈.NET技术,ASP.NET的状态管理
  10. 第三章 pro2信道编码咬尾卷积编码与维特比硬判决译码
  11. 理工专业单身男终极把妹大法
  12. 电子计算机的基本概念简述
  13. 开关电源的开关管一般用MOS管而不是三极管原因
  14. linux串口ttys1,linux ttySx 应用
  15. 【金猿人物展】袋鼠云易知微宁海元:从平台到场景,数字化进入全产业发展新阶段价值...
  16. 淘宝运营 淘宝补流量的作用 如何安全补单
  17. DPU网络开发SDK—DPDK(八)
  18. 停车场车辆计数案例---以西门子1200PLC演示
  19. pytorch安装和tensorflow环境搭建和cuda加速和cudann安装教程记录日期2022.10.20日
  20. 算法图解第十、十一章读书笔记

热门文章

  1. layui 怎么设置点击图片放大_layui表格内放置图片,并点击放大的实例
  2. python平均_python 算平均
  3. php怎么设置页间全局变量,PHP怎么设置全局变量?
  4. 交通均衡UE\SO的定义与优化框架推导,以及拥堵收费的原理与方法
  5. win10 升级到win11镜像setup安装
  6. /java-php-python-ssm共享充电宝管理系统计算机毕业设计
  7. linux命令 查看文件行号的几种方式
  8. 启动tomcat - preparing launch delegate..
  9. springcloud之服务配置中心
  10. 两个PDF比较标出差异_轻松搞定PDF格式转换