Spring Security的基本授权配置方式

参考:方法安全(Method Security) :: Spring Security Reference (springdoc.cn)、 授权 HttpServletRequest :: Spring Security Reference (springdoc.cn)

前文为:Spring Security:授权框架

一、HttpServletRequest授权

Spring Security 允许在 request 层(通过URL和请求类型等)建立授权模型。例如,可以让 /admin 下的所有页面都需要授权,而所有其他页面只需要认证。

1.1调度(Dispatche)授权

AuthorizationFilter 不仅在每个 request 上运行,而且在每个 dispatch 上运行。这意味着 REQUEST dispatch 需要授权,FORWARD、ERROR 和 INCLUDE 也需要:

  • 比如 Spring MVC 将一个请求 FORWARD 到一个渲染 Thymeleaf 模板的视图解析器,会发生两次授权:一次是授权 /endpoint,一次是转发到 Thymeleaf 以渲染 "endpoint" 模板。这就是 FORWARD dispatche。
  • 比如 Spring Boot 捕捉到一个异常后,会将它 dispatch 给 ERROR dispatch,也会发生两次授权:一次是授权 /endpoint ,一次是 dispatch error。

代码如

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {//Spring MVC的FORWARD dispatche
        return "endpoint";
    }
    @GetMapping("/endpoint2")
    public String endpoint2() {//Spring Boot的ERROR dispatch
        throw new UnsupportedOperationException("unsupported");
    }
}

进行放行,就需要:

http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .requestMatchers("/endpoint2").permitAll()
        .anyRequest().denyAll()
    )

1.2Endpoint授权

对端点进行授权的方式,如/endpoint 只能被具有 USER 权限的终端用户访问:

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers("/endpoint").hasAuthority('USER')
            .anyRequest().authenticated()
        )
        // ...

    return http.build();
}

观察代码可以发现,声明可以被分解为 pattern/rule 对,即requestMatchers("pattern").rule。

解释一下上述代码:

  • requestMatchers("/endpoint").XXX:授权规则
    • permitAll:该请求不需要授权。(不会从 session 中检索 Authentication)
    • denyAll:任何情况下都不允许。(不会从 session 中检索 Authentication)
    • hasAuthority: Authentication 有一个符合给定值的 GrantedAuthority。
    • hasRole:hasAuthority 的一个快捷方式,自动添加默认的前缀。
    • hasAnyAuthority:要求 Authentication 有一个符合任何给定值的 GrantedAuthority。
    • hasAnyRole:hasAnyAuthority 的一个快捷方式,自动添加默认的前缀。
    • access:该请求使用这个自定义的 AuthorizationManager 来确定访问权限。
  • requestMatchers(XXX):匹配请求
    • URI模式
      • Ant语言:如 requestMatchers("/resource/**").hasAuthority("USER"),代表——如果请求是 /resource 或一些子目录,则要求 USER 权限。
      • 正则表达式:如requestMatchers(RegexRequestMatcher.regexMatcher("/username/[A-Za-z0-9]+")).hasAuthority("USER"),代表包含 username 的路径和所有 username 必须是字母数字。
    • Http方法
      • 如:所有的 GET 都有 read 权限——requestMatchers(HttpMethod.GET).hasAuthority("read")。

也可以自定义RequestMatcher,然后提供给http:

RequestMatcher printview = (request) -> request.getParameter("print") != null;//自定义RequestMatcher
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(printview).hasAuthority("print")
        .anyRequest().authenticated()
    )

1.3案例

基础配置

如:配置 /auth/hello 的 get 请求需要 user 权限;配置 /auth/hello 的 post 请求需要 manager 权限;配置 /hello 的 get 请求需要 manager 权限:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(authorize -> authorize
                    .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
                    .requestMatchers(HttpMethod.GET,"/auth/hello").hasAuthority("user")
                    .requestMatchers(HttpMethod.POST,"/auth/hello").hasAuthority("manager")
                    .requestMatchers("/hello").hasAuthority("manager")
                    .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults());
    http.userDetailsService(myJdbcDaoImpl());
    return http.build();
}

数据库中权限为:

Spring Security的基本授权配置方式_第1张图片

自定义AuthorizationManager

复习一下AuthorizationManager是什么:用来判断授权是否通过的组件。

代码:

@Component
public final class OpenPolicyAgentAuthorizationManager implements AuthorizationManager {
    @Override
    public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) {
        return new AuthorizationDecision(true);
    }

    @Override
    public void verify(Supplier authentication, RequestAuthorizationContext object) {
        AuthorizationManager.super.verify(authentication, object);
    }
}

@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager authz) throws Exception {
    http.authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(authz)
        );
    return http.build();
}

check内填写授权的方式,我直接返回授权成功了...RequestAuthorizationContext context 中包含了请求信息,可从中获得URL等信息进行判定。

1.4SpEL表示授权

Spring Security 将其所有的授权字段和方法都封装在一组根(root)对象中。最通用的根对象被称为 SecurityExpressionRoot,它是 WebSecurityExpressionRoot 的基础。当准备评估一个授权表达式时,Spring Security 将这个根对象提供给 StandardEvaluationContext。

常见方法:

  • permitAll - 该请求不需要授权即可调用;注意,在这种情况下,将不会从 session 中检索 Authentication。
  • denyAll - 该请求在任何情况下都是不允许的;注意在这种情况下,永远不会从会话中检索 Authentication。
  • hasAuthority - 请求要求 Authentication 的 GrantedAuthority 符合给定值。
  • hasRole - hasAuthority 的快捷方式,前缀为 ROLE_ 或任何配置为默认前缀的内容。
  • hasAnyAuthority - 请求要 Authentication 具有符合任何给定值的 GrantedAuthority。
  • hasAnyRole - hasAnyAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。
  • hasPermission - 用于对象级授权的 PermissionEvaluator 实例的 hook。

举例:


     
     
     
     
  • 我们指定了一个任何用户都可以访问的 URL patter。具体来说,如果URL以 "/static/" 开头,任何用户都可以访问一个请求。
  • 任何以 "/admin/" 开头的 URL 将被限制给拥有 "ROLE_ADMIN" 角色的用户。你会注意到,由于我们调用的是 hasRole 方法,我们不需要指定 "ROLE_" 前缀。
  • 任何以 "/db/" 开头的 URL 都需要用户被授予 "db" 权限,并且是 "ROLE_ADMIN"。你会注意到,由于我们使用的是 hasRole 表达式,我们不需要指定 "ROLE_" 前缀。
  • 任何还没有被匹配的URL都会被拒绝访问。如果你不想意外地忘记更新你的授权规则,这是一个好的策略。

二、方法安全

2.1基础流程

除了对 HttpServletRequest 授权外,Spring Security 还支持在方法级别进行授权,方法安全具有细粒度、服务层的优点,并在风格上优于基于 HttpSecurity 的配置。

方法安全需要通过注解@EnableMethodSecurity启用,启用后即可使用注解 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter。

代码举例:

@Service
public class MyCustomerService {
    @PreAuthorize("hasAuthority('permission:read')")
    @PostAuthorize("returnObject.owner == authentication.name")
    public Customer readCustomer(String id) { ... }
}

方法安全是基于Spring AOP实现的。调用流程:

Spring Security的基本授权配置方式_第2张图片

  1. Spring AOP 为 readCustomer 调用了代理方法,调用了一个与 @PreAuthorize pointcut 匹配的拦截器 AuthorizationManagerBeforeMethodInterceptor。
  2. 拦截器调用 PreAuthorizeAuthorizationManager#check。
  3. PreAuthorizeAuthorizationManager 使用 MethodSecurityExpressionHandler 解析注解的 SpEL 表达式,并从包含 Supplier 和 MethodInvocation 的 MethodSecurityExpressionRoot 构建相应的 EvaluationContext。
  4. 拦截器使用该上下文来评估表达式;具体地说,它从 Supplier 读取 Authentication,并检查其 权限 集合中是否有 permission:read。
  5. 如果评估通过,Spring AOP 将继续调用该方法。
  6. 如果没有,拦截器会发布一个 AuthorizationDeniedEvent,并抛出一个 AccessDeniedException, ExceptionTranslationFilter 会捕获并向响应返回一个403状态码。
  7. 在方法返回后,Spring AOP 调用一个与 @PostAuthorize pointcut 相匹配的拦截器 AuthorizationManagerAfterMethodInterceptor,操作与上面相同,但使用了 PostAuthorizeAuthorizationManager。
  8. 如果评估通过(在这种情况下,返回值属于登录的用户),处理继续正常进行。
  9. 如果没有,拦截器会发布一个 AuthorizationDeniedEvent,并抛出一个 AccessDeniedException, ExceptionTranslationFilter 会捕获并向响应返回一个 403 状态码。

2.2注解使用介绍与举例

注解使用方式:

  1. 多个注解串联计算:如果方法调用涉及多个 方法安全注解,则每个注解一次处理一个(等同于&&)。
  2. 同一个方法上不能重复相同的注解。
  3. 注解内的授权使用 SpEL 定义。
  4. 在类和接口级别也支持方法安全注解,所有方法都继承类级别的行为。
  5. 通过@Param或@P注解,可在方法安全中使用方法参数。

@PreAuthorize应用代码举例:

@RestController
@RequestMapping("/auth")
public class TestController {

    @PreAuthorize("hasAuthority('user')")
    @GetMapping("/hello")
    public String sayHello(){
        return "hello security";
    }

    @PreAuthorize("hasAuthority('manager')")
    @PostMapping("/hello")
    public String sayHello2(){
        return "hello security";
    }
}

要求调用处理/auth/hello的get请求的控制权方法时需要user权限,调用处理/auth/hello的post请求的控制权方法时需要manager权限。

@PostAuthorize、@PreFilter、@PostFilter 应用举例:

@Component
public class BankService {//返回的 Account 对象.owner == authentication.name 通过时才能返回值。
    @PostAuthorize("returnObject.owner == authentication.name")
    public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
    }
}
@Component
public class BankService {//过滤掉实参 accounts 中的 filterObject.owner == authentication.name 表达式失败的值。
//filterObject 代表 accounts 中的每个 account,用于测试每个 account。
    @PreFilter("filterObject.owner == authentication.name")
    public Collection updateAccounts(Account... accounts) {
        // ... `accounts` will only contain the accounts owned by the logged-in user
        return updated;
    }
}
@Component
public class BankService {//过滤掉返回值 accounts 中的 filterObject.owner == authentication.name 表达式失败的值。
    @PostFilter("filterObject.owner == authentication.name")
    public Collection readAccounts(String... ids) {
        // ... the return value will be filtered to only contain the accounts owned by the logged-in user
        return accounts;
    }
}

在方法安全中使用方法参数:

  1. @Param 注解:适用于方法有一个及以上参数使用注解。
    @PreAuthorize("#n == authentication.name")
    Contact findContactByName(@Param("n") String name);//要求 name 等于 Authentication#getName 才能对调用进行授权。
  2. @P 注解:适用于方法仅有一个参数使用注解。
    @PreAuthorize("hasPermission(#c, 'write')")
    public void updateContact(@P("c") Contact contact);//当前 Authentication 具有专门针对此 Contact 实例的 write 权限。

在方法安全中,注解的拦截器可以自定义,拦截器使用的AuthenticationManager也可以自定义;SpEL 中可以使用自定义 Bean 进行判断,SpEL的表达式的处理方式也可以自定义,这些内容请自行了解(方法安全(Method Security) :: Spring Security Reference (springdoc.cn))。 

2.3SpEL表达式案例

@Component
public class MyService {
    //任何人不得以任何理由调用该方法。
    @PreAuthorize("denyAll")
    MyResource myDeprecatedMethod(...);
    //该方法只能由被授予 ROLE_ADMIN 权限的 Authentication 调用。
    @PreAuthorize("hasRole('ADMIN')")
    MyResource writeResource(...)
    //该方法只能由被授予 db 和 ROLE_ADMIN 权限的 Authentication 调用。
    @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')")
    MyResource deleteResource(...)
    //本方法仅可由 aud claim 等于 "my-audience" 的 Princpal 调用。
    @PreAuthorize("principal.claims['aud'] == 'my-audience'")
    MyResource readResource(...);
    //只有当 Bean authz 的 decide 方法返回 true 时,才能调用该方法。
    @PreAuthorize("@authz.decide(#root)")
    MyResource shareResource(...);
}
//Bean authz 的定义
@Component("authz")
public class AuthorizationLogic {
    public boolean decide(MethodSecurityExpressionOperations operations) {
        // ... authorization logic
    }
}

你可能感兴趣的:(Spring,Security框架学习,spring,java,后端,Spring,Security)