参考:方法安全(Method Security) :: Spring Security Reference (springdoc.cn)、 授权 HttpServletRequest :: Spring Security Reference (springdoc.cn)
前文为:Spring Security:授权框架
Spring Security 允许在 request 层(通过URL和请求类型等)建立授权模型。例如,可以让 /admin 下的所有页面都需要授权,而所有其他页面只需要认证。
AuthorizationFilter 不仅在每个 request 上运行,而且在每个 dispatch 上运行。这意味着 REQUEST dispatch 需要授权,FORWARD、ERROR 和 INCLUDE 也需要:
代码如
@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()
)
对端点进行授权的方式,如/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。
解释一下上述代码:
也可以自定义RequestMatcher,然后提供给http:
RequestMatcher printview = (request) -> request.getParameter("print") != null;//自定义RequestMatcher
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(printview).hasAuthority("print")
.anyRequest().authenticated()
)
如:配置 /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();
}
数据库中权限为:
复习一下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等信息进行判定。
Spring Security 将其所有的授权字段和方法都封装在一组根(root)对象中。最通用的根对象被称为 SecurityExpressionRoot,它是 WebSecurityExpressionRoot 的基础。当准备评估一个授权表达式时,Spring Security 将这个根对象提供给 StandardEvaluationContext。
常见方法:
举例:
除了对 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实现的。调用流程:
注解使用方式:
@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;
}
}
在方法安全中使用方法参数:
@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);//要求 name 等于 Authentication#getName 才能对调用进行授权。
@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))。
@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
}
}