监听授权成功和失败的消息

This commit is contained in:
bunny 2025-07-15 21:15:52 +08:00
parent 41cd4c9976
commit 4a43bf26b8
7 changed files with 326 additions and 124 deletions

View File

@ -1077,7 +1077,9 @@ public class AuthTestController {
} }
``` ```
## 通过编程方式授权方法 ## 其余授权方式
### 通过编程方式授权方法
如果需要对权限做出自定义的需求,将传入参数作为判断权限条件,这会很有用,比如某些参数不可以传入,或者参数做权限校验等。 如果需要对权限做出自定义的需求,将传入参数作为判断权限条件,这会很有用,比如某些参数不可以传入,或者参数做权限校验等。
@ -1121,13 +1123,13 @@ public Result<String> lowerUser(String name) {
} }
``` ```
## 使用自定义授权管理器 ### 使用自定义授权管理器
在实际开发中对于SpringSecurity提供的两个权限校验注解`@PreAuthorize`和`@PostAuthorize`,需要对这两个进行覆盖或者改造,需要实现两个`AuthorizationManager<T>`。 在实际开发中对于SpringSecurity提供的两个权限校验注解`@PreAuthorize`和`@PostAuthorize`,需要对这两个进行覆盖或者改造,需要实现两个`AuthorizationManager<T>`。
实现完成后需要显式的在配置中禁用原先的内容。 实现完成后需要显式的在配置中禁用原先的内容。
### 1. 实现前置 #### 1. 实现前置
在方法中写入自己的校验逻辑。 在方法中写入自己的校验逻辑。
@ -1148,7 +1150,7 @@ public class PostAuthorizationManager implements AuthorizationManager<MethodInvo
} }
``` ```
### 2. 实现后置 #### 2. 实现后置
```java ```java
/** /**
@ -1168,7 +1170,7 @@ public class PreAuthorizationManager implements AuthorizationManager<MethodInvoc
} }
``` ```
### 3. 禁用自带的 #### 3. 禁用自带的
需要加上注解`@EnableMethodSecurity(prePostEnabled = false)`。 需要加上注解`@EnableMethodSecurity(prePostEnabled = false)`。
@ -1192,7 +1194,7 @@ public class AuthorizationManagerConfiguration {
} }
``` ```
## 将方法与自定义切入点相匹配 ### 将方法与自定义切入点相匹配
由于是基于 Spring AOP 构建的,您可以声明与注解无关的模式,类似于请求级别的授权。 这具有将方法级别的授权规则集中化的潜在优势。 由于是基于 Spring AOP 构建的,您可以声明与注解无关的模式,类似于请求级别的授权。 这具有将方法级别的授权规则集中化的潜在优势。
@ -1210,3 +1212,146 @@ static Advisor protectServicePointcut() {
} }
``` ```
## 监听授权事件
监听授权事件分为两种,一种是授权成功的事件,一种是授权失败事件。
对于授权事件可以作为日志进行处理,一般来说会处理授权失败事件,如果要处理成功的事件确保不会影响太多业务上的内容。
### 授权失败和成功事件
在这里说下失败的事件,如果要处理成功的事件和失败的事件一样。
授权事件都实现于`AuthorizationEvent`接口:
```java
public class AuthorizationDeniedEvent<T> extends AuthorizationEvent {}
public class AuthorizationGrantedEvent<T> extends AuthorizationEvent {}
```
**getSource和getObject**
这两个本质上是一样的,`getObject`也是获取`getSource`只是Security做了类型转换`(T) getSource();`。
所以在获取的时候如果传入了泛型,那么直接获取`getObject`即可。
如果要获取别的信息可以自己手动转换`getSource`方法内容。
源码如下:
```java
public class AuthorizationDeniedEvent<T> extends AuthorizationEvent {
public AuthorizationDeniedEvent(Supplier<Authentication> authentication, T object, AuthorizationDecision decision) {
super(authentication, object, decision);
}
@Override
@SuppressWarnings("unchecked")
public T getObject() {
return (T) getSource();
}
}
```
**Authentication**
获取授权相关的信息比如当前用户会话Id(前后端不分离)当前用户的IP地址(remoteAddress)等。
如下面的信息:
```JSON
{
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "B830DE326FC6F4468B539A7A519C0257"
},
"authenticated": true,
"principal": {
"password": null,
"username": "Bunny",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "Bunny"
}
```
**AuthorizationDecision**
授权方法上的信息,被拒绝的方法名称、表达式信息。
```properties
ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasAuthority('ADMIN')]
```
### 示例
```java
@Slf4j
@Component
public class AuthenticationEvents {
/**
* 监听拒绝授权内容
*
* @param failure 授权失败
*/
@EventListener
public void onFailure(AuthorizationDeniedEvent<MethodInvocation> failure) {
// getSource 和 getObject意思一样一种是传入泛型自动转换一种是要手动转换
Object source = failure.getSource();
// 直接获取泛型对象
MethodInvocation methodInvocation = failure.getObject();
Method method = methodInvocation.getMethod();
Object[] args = methodInvocation.getArguments();
log.warn("方法调用被拒绝: {}.{}, 参数: {}",
method.getDeclaringClass().getSimpleName(),
method.getName(),
Arrays.toString(args));
// 这里面的信息,和接口 /api/security/current-user 内容一样
Authentication authentication = failure.getAuthentication().get();
AuthorizationDecision authorizationDecision = failure.getAuthorizationDecision();
// ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasAuthority('ADMIN')]
System.out.println(authorizationDecision);
log.warn("授权失败 - 用户: {}, 权限: {}", authentication.getName(), authorizationDecision);
}
/**
* 监听授权的内容
* 如果要监听授权成功的内容,这个内容可能相当的多,毕竟正常情况授权成功的内容还是比较多的。
* 既然内容很多又要监听,如果真的需要,一定要处理好业务逻辑,不要被成功的消息淹没。
*
* @param success 授权成功
*/
@EventListener
public void onSuccess(AuthorizationGrantedEvent<Object> success) {
}
}
```

View File

@ -1,30 +1,21 @@
package com.spring.step2.security.config; package com.spring.step2.security.config;
import com.spring.step2.security.manger.PostAuthorizationManager;
import com.spring.step2.security.manger.PreAuthorizationManager;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@Configuration @Configuration
@EnableMethodSecurity(prePostEnabled = false) // @EnableMethodSecurity(prePostEnabled = false)
public class AuthorizationManagerConfiguration { public class AuthorizationManagerConfiguration {
@Bean // @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) // @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorize(PreAuthorizationManager manager) { // Advisor preAuthorize(PreAuthorizationManager manager) {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager); // return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
} // }
//
// @Bean
// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// Advisor postAuthorize(PostAuthorizationManager manager) {
// return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
// }
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorize(PostAuthorizationManager manager) {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
}
} }

View File

@ -0,0 +1,60 @@
package com.spring.step2.security.event;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.event.EventListener;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Slf4j
@Component
public class AuthenticationEvents {
/**
* 监听拒绝授权内容
*
* @param failure 授权失败
*/
@EventListener
public void onFailure(AuthorizationDeniedEvent<MethodInvocation> failure) {
// getSource getObject意思一样一种是传入泛型自动转换一种是要手动转换
Object source = failure.getSource();
// 直接获取泛型对象
MethodInvocation methodInvocation = failure.getObject();
Method method = methodInvocation.getMethod();
Object[] args = methodInvocation.getArguments();
log.warn("方法调用被拒绝: {}.{}, 参数: {}",
method.getDeclaringClass().getSimpleName(),
method.getName(),
Arrays.toString(args));
// 这里面的信息和接口 /api/security/current-user 内容一样
Authentication authentication = failure.getAuthentication().get();
AuthorizationDecision authorizationDecision = failure.getAuthorizationDecision();
// ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasAuthority('ADMIN')]
System.out.println(authorizationDecision);
log.warn("授权失败 - 用户: {}, 权限: {}", authentication.getName(), authorizationDecision);
}
/**
* 监听授权的内容
* 如果要监听授权成功的内容这个内容可能相当的多毕竟正常情况授权成功的内容还是比较多的
* 既然内容很多又要监听如果真的需要一定要处理好业务逻辑不要被成功的消息淹没
*
* @param success 授权成功
*/
@EventListener
public void onSuccess(AuthorizationGrantedEvent<MethodInvocation> success) {
}
}

View File

@ -0,0 +1,21 @@
package com.spring.step2.security.event;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.stereotype.Component;
/**
* 如果要监听授权和拒绝的授权需要发布一个像下面这样的事件
* 之后使用 Spring @EventListener
*/
@Component
public class SecurityAuthorizationPublisher {
@Bean
public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
return new SpringAuthorizationEventPublisher(applicationEventPublisher);
}
}

View File

@ -1,56 +1,48 @@
package com.spring.step2.security.manger; package com.spring.step2.security.manger;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
/** /**
* 处理方法调用后的授权检查 * 处理方法调用后的授权检查
* check()方法接收的是MethodInvocationResult对象包含已执行方法的结果 * check()方法接收的是MethodInvocationResult对象包含已执行方法的结果
* 用于决定是否允许返回某个方法的结果(后置过滤) * 用于决定是否允许返回某个方法的结果(后置过滤)
* 这是Spring Security较新的"后置授权"功能 * 这是Spring Security较新的"后置授权"功能
*/ */
@Component // @Component
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> { // public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
//
/** // /**
* 这里两个实现方法按照Security官方要求进行实现 // * 这里两个实现方法按照Security官方要求进行实现
* <h4>类说明</h4> // * <h4>类说明</h4>
* 下面的实现是对方法执行前进行权限校验的判断 // * 下面的实现是对方法执行前进行权限校验的判断
* <pre> // * <pre>
* <code>AuthorizationManager &ltMethodInvocation></code> // * <code>AuthorizationManager &ltMethodInvocation></code>
* </pre> // * </pre>
* 下面的这个是对方法执行后对权限的判断 // * 下面的这个是对方法执行后对权限的判断
* <pre> // * <pre>
* <code>AuthorizationManager &ltMethodInvocationResult></code> // * <code>AuthorizationManager &ltMethodInvocationResult></code>
* </pre> // * </pre>
* // *
* <h4>注意事项</h4> // * <h4>注意事项</h4>
* 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的 // * 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的
* <pre> // * <pre>
* &#064;Configuration // * &#064;Configuration
* &#064;EnableMethodSecurity(prePostEnabled = false) // * &#064;EnableMethodSecurity(prePostEnabled = false)
* class MethodSecurityConfig { // * class MethodSecurityConfig {
* &#064;Bean // * &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE) // * &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor preAuthorize(MyAuthorizationManager manager) { // * Advisor preAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager); // * return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
* } // * }
* // *
* &#064;Bean // * &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE) // * &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor postAuthorize(MyAuthorizationManager manager) { // * Advisor postAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager); // * return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
* } // * }
* } // * }
* </pre> // * </pre>
*/ // */
@Override // @Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) { // public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
return new AuthorizationDecision(true); // return new AuthorizationDecision(true);
} // }
} // }

View File

@ -1,57 +1,49 @@
package com.spring.step2.security.manger; package com.spring.step2.security.manger;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
/** /**
* 处理方法调用前的授权检查 * 处理方法调用前的授权检查
* check()方法接收的是MethodInvocation对象包含即将执行的方法调用信息 * check()方法接收的是MethodInvocation对象包含即将执行的方法调用信息
* 用于决定是否允许执行某个方法 * 用于决定是否允许执行某个方法
* 这是传统的"前置授权"模式 * 这是传统的"前置授权"模式
*/ */
@Component // @Component
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> { // public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
//
/** // /**
* 这里两个实现方法按照Security官方要求进行实现 // * 这里两个实现方法按照Security官方要求进行实现
* <h4>类说明</h4> // * <h4>类说明</h4>
* 下面的实现是对方法执行前进行权限校验的判断 // * 下面的实现是对方法执行前进行权限校验的判断
* <pre> // * <pre>
* <code>AuthorizationManager &ltMethodInvocation></code> // * <code>AuthorizationManager &ltMethodInvocation></code>
* </pre> // * </pre>
* 下面的这个是对方法执行后对权限的判断 // * 下面的这个是对方法执行后对权限的判断
* <pre> // * <pre>
* <code>AuthorizationManager &ltMethodInvocationResult></code> // * <code>AuthorizationManager &ltMethodInvocationResult></code>
* </pre> // * </pre>
* // *
* <h4>注意事项</h4> // * <h4>注意事项</h4>
* 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的 // * 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的
* <pre> // * <pre>
* &#064;Configuration // * &#064;Configuration
* &#064;EnableMethodSecurity(prePostEnabled = false) // * &#064;EnableMethodSecurity(prePostEnabled = false)
* class MethodSecurityConfig { // * class MethodSecurityConfig {
* &#064;Bean // * &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE) // * &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor preAuthorize(MyAuthorizationManager manager) { // * Advisor preAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager); // * return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
* } // * }
* // *
* &#064;Bean // * &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE) // * &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor postAuthorize(MyAuthorizationManager manager) { // * Advisor postAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager); // * return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
* } // * }
* } // * }
* </pre> // * </pre>
*/ // */
@Override // @Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) { // public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
return new AuthorizationDecision(true); // return new AuthorizationDecision(true);
} // }
//
} // }

View File

@ -0,0 +1 @@
如果需要重写验证逻辑(自定义)使用这里面的类,并在配置类`AuthorizationManagerConfiguration`解开注释,