From 4a43bf26b8fc806a6902e2ea5dae5d24e2c33caa Mon Sep 17 00:00:00 2001 From: bunny <1319900154@qq.com> Date: Tue, 15 Jul 2025 21:15:52 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E7=9B=91=E5=90=AC=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E6=88=90=E5=8A=9F=E5=92=8C=E5=A4=B1=E8=B4=A5=E7=9A=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-security/ReadMe.md | 157 +++++++++++++++++- .../AuthorizationManagerConfiguration.java | 33 ++-- .../security/event/AuthenticationEvents.java | 60 +++++++ .../event/SecurityAuthorizationPublisher.java | 21 +++ .../manger/PostAuthorizationManager.java | 88 +++++----- .../manger/PreAuthorizationManager.java | 90 +++++----- .../spring/step2/security/manger/ReadMe.md | 1 + 7 files changed, 326 insertions(+), 124 deletions(-) create mode 100644 spring-security/step-2/src/main/java/com/spring/step2/security/event/AuthenticationEvents.java create mode 100644 spring-security/step-2/src/main/java/com/spring/step2/security/event/SecurityAuthorizationPublisher.java create mode 100644 spring-security/step-2/src/main/java/com/spring/step2/security/manger/ReadMe.md diff --git a/spring-security/ReadMe.md b/spring-security/ReadMe.md index 55f6292..946ecc9 100644 --- a/spring-security/ReadMe.md +++ b/spring-security/ReadMe.md @@ -1077,7 +1077,9 @@ public class AuthTestController { } ``` -## 通过编程方式授权方法 +## 其余授权方式 + +### 通过编程方式授权方法 如果需要对权限做出自定义的需求,将传入参数作为判断权限条件,这会很有用,比如某些参数不可以传入,或者参数做权限校验等。 @@ -1121,13 +1123,13 @@ public Result lowerUser(String name) { } ``` -## 使用自定义授权管理器 +### 使用自定义授权管理器 在实际开发中对于SpringSecurity提供的两个权限校验注解`@PreAuthorize`和`@PostAuthorize`,需要对这两个进行覆盖或者改造,需要实现两个`AuthorizationManager`。 实现完成后需要显式的在配置中禁用原先的内容。 -### 1. 实现前置 +#### 1. 实现前置 在方法中写入自己的校验逻辑。 @@ -1148,7 +1150,7 @@ public class PostAuthorizationManager implements AuthorizationManager extends AuthorizationEvent {} +public class AuthorizationGrantedEvent extends AuthorizationEvent {} +``` + +**getSource和getObject** + +这两个本质上是一样的,`getObject`也是获取`getSource`只是Security做了类型转换:`(T) getSource();`。 + +所以在获取的时候如果传入了泛型,那么直接获取`getObject`即可。 + +如果要获取别的信息可以自己手动转换`getSource`方法内容。 + +源码如下: + +```java +public class AuthorizationDeniedEvent extends AuthorizationEvent { + + public AuthorizationDeniedEvent(Supplier 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 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 success) { + } +} +``` diff --git a/spring-security/step-2/src/main/java/com/spring/step2/security/config/AuthorizationManagerConfiguration.java b/spring-security/step-2/src/main/java/com/spring/step2/security/config/AuthorizationManagerConfiguration.java index b6f0d7b..dcb7ccf 100644 --- a/spring-security/step-2/src/main/java/com/spring/step2/security/config/AuthorizationManagerConfiguration.java +++ b/spring-security/step-2/src/main/java/com/spring/step2/security/config/AuthorizationManagerConfiguration.java @@ -1,30 +1,21 @@ 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.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 -@EnableMethodSecurity(prePostEnabled = false) +// @EnableMethodSecurity(prePostEnabled = false) public class AuthorizationManagerConfiguration { - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor preAuthorize(PreAuthorizationManager manager) { - return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager); - } + // @Bean + // @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + // Advisor preAuthorize(PreAuthorizationManager 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); - } - } diff --git a/spring-security/step-2/src/main/java/com/spring/step2/security/event/AuthenticationEvents.java b/spring-security/step-2/src/main/java/com/spring/step2/security/event/AuthenticationEvents.java new file mode 100644 index 0000000..46706ac --- /dev/null +++ b/spring-security/step-2/src/main/java/com/spring/step2/security/event/AuthenticationEvents.java @@ -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 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 success) { + } +} \ No newline at end of file diff --git a/spring-security/step-2/src/main/java/com/spring/step2/security/event/SecurityAuthorizationPublisher.java b/spring-security/step-2/src/main/java/com/spring/step2/security/event/SecurityAuthorizationPublisher.java new file mode 100644 index 0000000..8273edb --- /dev/null +++ b/spring-security/step-2/src/main/java/com/spring/step2/security/event/SecurityAuthorizationPublisher.java @@ -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); + } + +} diff --git a/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PostAuthorizationManager.java b/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PostAuthorizationManager.java index 9e8690b..c2a3400 100644 --- a/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PostAuthorizationManager.java +++ b/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PostAuthorizationManager.java @@ -1,56 +1,48 @@ 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对象,包含已执行方法的结果 * 用于决定是否允许返回某个方法的结果(后置过滤) * 这是Spring Security较新的"后置授权"功能 */ -@Component -public class PostAuthorizationManager implements AuthorizationManager { - - /** - * 这里两个实现方法按照Security官方要求进行实现 - *

类说明:

- * 下面的实现是对方法执行前进行权限校验的判断 - *
-     *     AuthorizationManager <MethodInvocation>
-     * 
- * 下面的这个是对方法执行后对权限的判断 - *
-     *     AuthorizationManager <MethodInvocationResult>
-     * 
- * - *

注意事项:

- * 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。 - *
-     * @Configuration
-     * @EnableMethodSecurity(prePostEnabled = false)
-     * class MethodSecurityConfig {
-     *     @Bean
-     *     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-     *    Advisor preAuthorize(MyAuthorizationManager manager) {
-     * 		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
-     *    }
-     *
-     *    @Bean
-     *    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-     *    Advisor postAuthorize(MyAuthorizationManager manager) {
-     * 		return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
-     *    }
-     * }
-     * 
- */ - @Override - public AuthorizationDecision check(Supplier authentication, MethodInvocationResult invocation) { - return new AuthorizationDecision(true); - } -} \ No newline at end of file +// @Component +// public class PostAuthorizationManager implements AuthorizationManager { +// +// /** +// * 这里两个实现方法按照Security官方要求进行实现 +// *

类说明:

+// * 下面的实现是对方法执行前进行权限校验的判断 +// *
+//      *     AuthorizationManager <MethodInvocation>
+//      * 
+// * 下面的这个是对方法执行后对权限的判断 +// *
+//      *     AuthorizationManager <MethodInvocationResult>
+//      * 
+// * +// *

注意事项:

+// * 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。 +// *
+//      * @Configuration
+//      * @EnableMethodSecurity(prePostEnabled = false)
+//      * class MethodSecurityConfig {
+//      *     @Bean
+//      *     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+//      *    Advisor preAuthorize(MyAuthorizationManager manager) {
+//      * 		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
+//      *    }
+//      *
+//      *    @Bean
+//      *    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+//      *    Advisor postAuthorize(MyAuthorizationManager manager) {
+//      * 		return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
+//      *    }
+//      * }
+//      * 
+// */ +// @Override +// public AuthorizationDecision check(Supplier authentication, MethodInvocationResult invocation) { +// return new AuthorizationDecision(true); +// } +// } \ No newline at end of file diff --git a/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PreAuthorizationManager.java b/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PreAuthorizationManager.java index 9057ea1..ae3115e 100644 --- a/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PreAuthorizationManager.java +++ b/spring-security/step-2/src/main/java/com/spring/step2/security/manger/PreAuthorizationManager.java @@ -1,57 +1,49 @@ 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对象,包含即将执行的方法调用信息 * 用于决定是否允许执行某个方法 * 这是传统的"前置授权"模式 */ -@Component -public class PreAuthorizationManager implements AuthorizationManager { - - /** - * 这里两个实现方法按照Security官方要求进行实现 - *

类说明:

- * 下面的实现是对方法执行前进行权限校验的判断 - *
-     *     AuthorizationManager <MethodInvocation>
-     * 
- * 下面的这个是对方法执行后对权限的判断 - *
-     *     AuthorizationManager <MethodInvocationResult>
-     * 
- * - *

注意事项:

- * 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。 - *
-     * @Configuration
-     * @EnableMethodSecurity(prePostEnabled = false)
-     * class MethodSecurityConfig {
-     *     @Bean
-     *     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-     *    Advisor preAuthorize(MyAuthorizationManager manager) {
-     * 		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
-     *    }
-     *
-     *    @Bean
-     *    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-     *    Advisor postAuthorize(MyAuthorizationManager manager) {
-     * 		return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
-     *    }
-     * }
-     * 
- */ - @Override - public AuthorizationDecision check(Supplier authentication, MethodInvocation invocation) { - return new AuthorizationDecision(true); - } - -} \ No newline at end of file +// @Component +// public class PreAuthorizationManager implements AuthorizationManager { +// +// /** +// * 这里两个实现方法按照Security官方要求进行实现 +// *

类说明:

+// * 下面的实现是对方法执行前进行权限校验的判断 +// *
+//      *     AuthorizationManager <MethodInvocation>
+//      * 
+// * 下面的这个是对方法执行后对权限的判断 +// *
+//      *     AuthorizationManager <MethodInvocationResult>
+//      * 
+// * +// *

注意事项:

+// * 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。 +// *
+//      * @Configuration
+//      * @EnableMethodSecurity(prePostEnabled = false)
+//      * class MethodSecurityConfig {
+//      *     @Bean
+//      *     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+//      *    Advisor preAuthorize(MyAuthorizationManager manager) {
+//      * 		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
+//      *    }
+//      *
+//      *    @Bean
+//      *    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+//      *    Advisor postAuthorize(MyAuthorizationManager manager) {
+//      * 		return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
+//      *    }
+//      * }
+//      * 
+// */ +// @Override +// public AuthorizationDecision check(Supplier authentication, MethodInvocation invocation) { +// return new AuthorizationDecision(true); +// } +// +// } \ No newline at end of file diff --git a/spring-security/step-2/src/main/java/com/spring/step2/security/manger/ReadMe.md b/spring-security/step-2/src/main/java/com/spring/step2/security/manger/ReadMe.md new file mode 100644 index 0000000..1547f4b --- /dev/null +++ b/spring-security/step-2/src/main/java/com/spring/step2/security/manger/ReadMe.md @@ -0,0 +1 @@ +如果需要重写验证逻辑(自定义)使用这里面的类,并在配置类`AuthorizationManagerConfiguration`解开注释, \ No newline at end of file