✨ 自定义覆盖原校验方式
This commit is contained in:
parent
1bb9a251a7
commit
d8a34d7fa6
|
@ -1137,19 +1137,88 @@ public Result<String> lowerUser(String name) {
|
|||
|
||||
在方法中写入自己的校验逻辑。
|
||||
|
||||
`MethodInvocation`类型是执行方法之前相当于是一个反射,可以获取到当前方法上的注解、方法名称、当前类等。
|
||||
|
||||
```java
|
||||
/**
|
||||
* 处理方法调用后的授权检查
|
||||
* check()方法接收的是MethodInvocationResult对象,包含已执行方法的结果
|
||||
* 用于决定是否允许返回某个方法的结果(后置过滤)
|
||||
* 这是Spring Security较新的"后置授权"功能
|
||||
*/
|
||||
@Component
|
||||
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
@RequiredArgsConstructor
|
||||
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
private final SecurityConfigProperties securityConfigProperties;
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
|
||||
return new AuthorizationDecision(true);
|
||||
public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocation methodInvocation) {
|
||||
Authentication authentication = authenticationSupplier.get();
|
||||
|
||||
// 如果方法有 @PreAuthorize 注解,会先到这里
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
boolean granted = hasPermission(authentication, methodInvocation);
|
||||
return new AuthorizationDecision(granted);
|
||||
}
|
||||
|
||||
private boolean hasPermission(Authentication authentication, MethodInvocation methodInvocation) {
|
||||
PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class);
|
||||
if (preAuthorize == null) {
|
||||
return true; // 没有注解默认放行
|
||||
}
|
||||
|
||||
String expression = preAuthorize.value();
|
||||
// 解析表达式中的权限要求
|
||||
List<String> requiredAuthorities = extractAuthoritiesFromExpression(expression);
|
||||
|
||||
// 获取配置的admin权限
|
||||
List<String> adminAuthorities = securityConfigProperties.getAdminAuthorities();
|
||||
|
||||
return authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.anyMatch(auth ->
|
||||
adminAuthorities.contains(auth) ||
|
||||
requiredAuthorities.contains(auth)
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> extractAuthoritiesFromExpression(String expression) {
|
||||
List<String> authorities = new ArrayList<>();
|
||||
|
||||
// 处理 hasAuthority('permission') 格式
|
||||
Pattern hasAuthorityPattern = Pattern.compile("hasAuthority\\('([^']+)'\\)");
|
||||
Matcher hasAuthorityMatcher = hasAuthorityPattern.matcher(expression);
|
||||
while (hasAuthorityMatcher.find()) {
|
||||
authorities.add(hasAuthorityMatcher.group(1));
|
||||
}
|
||||
|
||||
// 处理 hasRole('ROLE_XXX') 格式 (Spring Security 会自动添加 ROLE_ 前缀)
|
||||
Pattern hasRolePattern = Pattern.compile("hasRole\\('([^']+)'\\)");
|
||||
Matcher hasRoleMatcher = hasRolePattern.matcher(expression);
|
||||
while (hasRoleMatcher.find()) {
|
||||
authorities.add(hasRoleMatcher.group(1));
|
||||
}
|
||||
|
||||
// 处理 hasAnyAuthority('perm1','perm2') 格式
|
||||
Pattern hasAnyAuthorityPattern = Pattern.compile("hasAnyAuthority\\(([^)]+)\\)");
|
||||
Matcher hasAnyAuthorityMatcher = hasAnyAuthorityPattern.matcher(expression);
|
||||
while (hasAnyAuthorityMatcher.find()) {
|
||||
String[] perms = hasAnyAuthorityMatcher.group(1).split(",");
|
||||
for (String perm : perms) {
|
||||
authorities.add(perm.trim().replaceAll("'", ""));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 hasAnyRole('role1','role2') 格式
|
||||
Pattern hasAnyRolePattern = Pattern.compile("hasAnyRole\\(([^)]+)\\)");
|
||||
Matcher hasAnyRoleMatcher = hasAnyRolePattern.matcher(expression);
|
||||
while (hasAnyRoleMatcher.find()) {
|
||||
String[] roles = hasAnyRoleMatcher.group(1).split(",");
|
||||
for (String role : roles) {
|
||||
authorities.add(role.trim().replaceAll("'", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -1158,24 +1227,64 @@ public class PostAuthorizationManager implements AuthorizationManager<MethodInvo
|
|||
|
||||
```java
|
||||
/**
|
||||
* 处理方法调用前的授权检查
|
||||
* check()方法接收的是MethodInvocation对象,包含即将执行的方法调用信息
|
||||
* 用于决定是否允许执行某个方法
|
||||
* 这是传统的"前置授权"模式
|
||||
* 处理方法调用后的授权检查
|
||||
* check()方法接收的是MethodInvocationResult对象,包含已执行方法的结果
|
||||
* 用于决定是否允许返回某个方法的结果(后置过滤)
|
||||
* 这是Spring Security较新的"后置授权"功能
|
||||
*/
|
||||
@Component
|
||||
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
@RequiredArgsConstructor
|
||||
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
|
||||
private final SecurityConfigProperties securityConfigProperties;
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
|
||||
return new AuthorizationDecision(true);
|
||||
public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocationResult methodInvocationResult) {
|
||||
Authentication authentication = authenticationSupplier.get();
|
||||
|
||||
// 如果方法有 @PreAuthorize 注解,会先到这里
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
boolean granted = hasPermission(authentication, methodInvocationResult);
|
||||
return new AuthorizationDecision(granted);
|
||||
}
|
||||
|
||||
private boolean hasPermission(Authentication authentication, MethodInvocationResult methodInvocationResult) {
|
||||
// 获取当前校验方法的返回值
|
||||
if (methodInvocationResult.getResult() instanceof Result<?> result) {
|
||||
// 拿到当前返回值中权限内容
|
||||
List<String> auths = result.getAuths();
|
||||
|
||||
// 允许全局访问的 角色或权限
|
||||
List<String> adminAuthorities = securityConfigProperties.adminAuthorities;
|
||||
|
||||
// 判断返回值中返回方法全新啊是否和用户权限匹配
|
||||
return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority)
|
||||
.anyMatch(auth ->
|
||||
// 允许放行的角色或权限 和 匹配到的角色或权限
|
||||
adminAuthorities.contains(auth) || auths.contains(auth)
|
||||
);
|
||||
}
|
||||
|
||||
// ❗这里可以设置自己的返回状态
|
||||
// ======================================
|
||||
// 默认返回 TRUE 是因为有可能当前方法不需要验证
|
||||
// 所以才设置默认返回为 TURE
|
||||
// ======================================
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 禁用自带的
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 这是一个非常关键的一步,如果需要实现自定义,必须要禁用原来的,替换之前的;否则不会生效。
|
||||
|
||||
需要加上注解`@EnableMethodSecurity(prePostEnabled = false)`。
|
||||
|
||||
```java
|
||||
|
@ -1535,3 +1644,371 @@ Bunny
|
|||
[permission::read, role::read]
|
||||
```
|
||||
|
||||
### 创建过滤器
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenService jwtTokenService;
|
||||
private final DbUserDetailService userDetailsService;
|
||||
private final SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request,
|
||||
@NotNull HttpServletResponse response,
|
||||
@NotNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
// 检查白名单路径
|
||||
if (isNoAuthPath(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否需要认证的路径
|
||||
if (!isSecurePath(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证 Token
|
||||
if (validToken(request, response, filterChain)) return;
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (AuthenticSecurityException e) {
|
||||
// 直接处理认证异常,不再调用filterChain.doFilter()
|
||||
securityAuthenticationEntryPoint.commence(
|
||||
request,
|
||||
response,
|
||||
new MyAuthenticationException(e.getMessage(), e)
|
||||
);
|
||||
} catch (RuntimeException e) {
|
||||
securityAuthenticationEntryPoint.commence(
|
||||
request,
|
||||
response,
|
||||
new MyAuthenticationException("Authentication failed", e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validToken(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws IOException, ServletException {
|
||||
// 验证Token
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
|
||||
// Token验证
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
// throw new AuthenticSecurityException(ResultCodeEnum.LOGIN_AUTH);
|
||||
}
|
||||
|
||||
String jwtToken = authHeader.substring(7);
|
||||
|
||||
if (jwtTokenService.isExpired(jwtToken)) {
|
||||
throw new AuthenticSecurityException(ResultCodeEnum.AUTHENTICATION_EXPIRED);
|
||||
}
|
||||
|
||||
// 设置认证信息
|
||||
String username = jwtTokenService.getUsernameFromToken(jwtToken);
|
||||
Long userId = jwtTokenService.getUserIdFromToken(jwtToken);
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||
userDetails,
|
||||
null,
|
||||
userDetails.getAuthorities()
|
||||
);
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
BaseContext.setUsername(username);
|
||||
BaseContext.setUserId(userId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是不用验证的路径
|
||||
*/
|
||||
private boolean isNoAuthPath(HttpServletRequest request) {
|
||||
RequestMatcher[] matchers = SecurityWebConfiguration.noAuthPaths.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toArray(RequestMatcher[]::new);
|
||||
return new OrRequestMatcher(matchers).matches(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是要验证的路径
|
||||
*/
|
||||
private boolean isSecurePath(HttpServletRequest request) {
|
||||
RequestMatcher[] matchers = SecurityWebConfiguration.securedPaths.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toArray(RequestMatcher[]::new);
|
||||
return new OrRequestMatcher(matchers).matches(request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 添加过滤器
|
||||
|
||||
添加过滤器为之前认证。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityWebConfiguration {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http
|
||||
// ...
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
;
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 返回两次
|
||||
|
||||
如果请求接口时候发现接口返回两次问题,往往是过滤器链写的有问题,比如使用过滤器链后面没有return。
|
||||
|
||||
比如下面写法就会导致返回两次情况。
|
||||
|
||||
#### 问题复现
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request,
|
||||
@NotNull HttpServletResponse response,
|
||||
@NotNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
// 检查白名单路径
|
||||
// 略.
|
||||
|
||||
// 检查是否需要认证的路径
|
||||
// 略.
|
||||
|
||||
// ⚠⚠⚠ 这里没有 return
|
||||
checkToken(request, response, filterChain);
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (AuthenticSecurityException e) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
private void checkToken(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws IOException, ServletException {
|
||||
// 验证Token
|
||||
// 设置认证信息,但是没有返回 ...
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是不用验证的路径
|
||||
*/
|
||||
private boolean isNoAuthPath(HttpServletRequest request) {
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是要验证的路径
|
||||
*/
|
||||
private boolean isSecurePath(HttpServletRequest request) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 解决方案
|
||||
|
||||
z只需要将`checkToken`返回Boolean值,之后判断再return。
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request,
|
||||
@NotNull HttpServletResponse response,
|
||||
@NotNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
// 检查白名单路径
|
||||
// 略.
|
||||
|
||||
// 检查是否需要认证的路径
|
||||
// 略.
|
||||
|
||||
// ⚠⚠⚠ 这里判断是否为ture
|
||||
if (checkToken(request, response, filterChain)) return;
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (AuthenticSecurityException e) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkToken(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws IOException, ServletException {
|
||||
// 验证Token
|
||||
// 设置认证信息,但是没有返回 ...
|
||||
return ture Or false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是不用验证的路径
|
||||
*/
|
||||
private boolean isNoAuthPath(HttpServletRequest request) {
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是要验证的路径
|
||||
*/
|
||||
private boolean isSecurePath(HttpServletRequest request) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. requestMatchers和@PermitAll权重
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 可参考文档:[在类或接口级别声明注解](https://docs.spring.io/spring-security/reference/6.3/servlet/authorization/method-security.html#class-or-interface-annotations)
|
||||
>
|
||||
> 官方文档中也提到方法授权和请求授权比较:[请求级授权与方法级授权的比较](https://docs.spring.io/spring-security/reference/6.3/servlet/authorization/method-security.html#request-vs-method)
|
||||
|
||||
假如你在requestMatchers上配置了`/api/**`(只要是此接口下都要认证登录),之后你在`/api/abc`上加上了注解`@PermitAll`或者`@PreAuthorize("permitAll()")`,那么接口`/api/abc`并不会按照你所期望的结果运行(不用登录也可访问)。
|
||||
|
||||
因为Security会先校验你的requestMatchers,之后再去方法上验证。大多数开发者直觉上认为,方法注解权重高于requestMatchers,实际上,会校验requestMatchers上的路径之后再去方法。
|
||||
|
||||
那么类似`permitAll`这种注解应该怎么用,比如你在控制器上加上了注解`@PreAuthorize("hasAuthority('USER')")`,方法上的`@PreAuthorize("hasAuthority('ADMIN')")`会覆盖掉类上的,同理,如果在方法上加上`@PermitAll`也会覆盖掉类上的。
|
||||
|
||||
在官方文档中,并没用直接阐述requestMatchers高于`@PermitAll`只是大多数人觉得,`@PermitAll`高于requestMatchers实际不然;
|
||||
|
||||
```java
|
||||
@RequestMapping("/api/security/programmatically")
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
public class SecurityProgrammaticallyController {
|
||||
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
官方文档只提到了方法注解会覆盖掉类上注解,同时不可以在方法上加上多个注解比如像下面这样。
|
||||
|
||||
```java
|
||||
@RequestMapping("/api/security/programmatically")
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
public class SecurityProgrammaticallyController {
|
||||
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
如果要使用`@PermitAll`需要显式的开启。
|
||||
|
||||
```java
|
||||
@EnableMethodSecurity(jsr250Enabled = true) // 启用了 JSR-250
|
||||
```
|
||||
|
||||
### 3. 忽略接口无法获取用户信息
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 可以参考文档:[使用 SpEL 表达授权](https://docs.spring.io/spring-security/reference/6.3/servlet/authorization/method-security.html#authorization-expressions)
|
||||
|
||||
假如说现在需求是`/api/**`的接口路径是都需要认证的,但是我们想忽略掉部分路径,比如:`/api/a/**`、`/api/b`,那么访问忽略的接口是无法获取到当前用户信息的。因为是不走校验的。
|
||||
|
||||
SpringSecurity官方解释的是,这样校验会更快。
|
||||
|
||||
```java
|
||||
permitAll - The method requires no authorization to be invoked; note that in this case, the Authentication is never retrieved from the session
|
||||
|
||||
denyAll - The method is not allowed under any circumstances; note that in this case, the Authentication is never retrieved from the session
|
||||
```
|
||||
|
||||
### 4. 同时使用@PreAuthorize和@PermitAll达不到预期
|
||||
|
||||
- 如果当前用户无权限或者未登录:
|
||||
|
||||
- 访问当前控制器必须要有`USER`权限,但是访问`upperUser`方法时无需登录或认证,这时`@PermitAll`不会起到作用,相反会拒绝请求。
|
||||
|
||||
- 即使显式的开启了jsr250Enabled也无效。
|
||||
|
||||
```java
|
||||
@EnableMethodSecurity(jsr250Enabled = true) // 启用了 JSR-250
|
||||
```
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/security/programmatically")
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
public class SecurityProgrammaticallyController {
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
解决方法是,使用`@PreAuthorize("permitAll()")`
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/security/programmatically")
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
public class SecurityProgrammaticallyController {
|
||||
|
||||
@PreAuthorize("permitAll()")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import com.spring.step2.service.RoleService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -48,7 +47,6 @@ public class RoleController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "获取全部角色列表", description = "获取全部角色列表")
|
||||
@GetMapping("all")
|
||||
public Result<List<RoleVo>> getRoleList() {
|
||||
|
|
|
@ -12,7 +12,6 @@ import com.spring.step3.service.roles.PermissionService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -35,7 +34,6 @@ public class PermissionController {
|
|||
|
||||
private final PermissionService permissionService;
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "分页查询系统权限表", description = "分页系统权限表")
|
||||
@GetMapping("{page}/{limit}")
|
||||
public Result<PageResult<PermissionVo>> getPermissionPage(
|
||||
|
@ -49,7 +47,6 @@ public class PermissionController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "所有的权限列表", description = "获取所有的权限列表")
|
||||
@GetMapping("all")
|
||||
public Result<List<PermissionVo>> getAllPermission() {
|
||||
|
@ -57,7 +54,6 @@ public class PermissionController {
|
|||
return Result.success(voList);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "添加系统权限表", description = "添加系统权限表")
|
||||
@PostMapping()
|
||||
public Result<String> addPermission(@Valid @RequestBody PermissionDto dto) {
|
||||
|
@ -65,7 +61,6 @@ public class PermissionController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "更新系统权限表", description = "更新系统权限表")
|
||||
@PutMapping()
|
||||
public Result<String> updatePermission(@Valid @RequestBody PermissionDto dto) {
|
||||
|
@ -73,7 +68,6 @@ public class PermissionController {
|
|||
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "删除系统权限表", description = "删除系统权限表")
|
||||
@DeleteMapping()
|
||||
public Result<String> deletePermission(@RequestBody List<Long> ids) {
|
||||
|
|
|
@ -11,7 +11,6 @@ import com.spring.step3.service.roles.RoleService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -35,7 +34,6 @@ public class RoleController {
|
|||
|
||||
private final RoleService roleService;
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "分页查询系统角色表", description = "分页系统角色表")
|
||||
@GetMapping("{page}/{limit}")
|
||||
public Result<PageResult<RoleVo>> getRolePage(
|
||||
|
@ -49,7 +47,6 @@ public class RoleController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "获取全部角色列表", description = "获取全部角色列表")
|
||||
@GetMapping("all")
|
||||
public Result<List<RoleVo>> getRoleList() {
|
||||
|
@ -57,7 +54,6 @@ public class RoleController {
|
|||
return Result.success(roleVoList);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "添加系统角色表", description = "添加系统角色表")
|
||||
@PostMapping()
|
||||
public Result<String> addRole(@Valid @RequestBody RoleDto dto) {
|
||||
|
@ -65,7 +61,6 @@ public class RoleController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "更新系统角色表", description = "更新系统角色表")
|
||||
@PutMapping()
|
||||
public Result<String> updateRole(@Valid @RequestBody RoleDto dto) {
|
||||
|
@ -73,7 +68,6 @@ public class RoleController {
|
|||
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "删除系统角色表", description = "删除系统角色表")
|
||||
@DeleteMapping()
|
||||
public Result<String> deleteRole(@RequestBody List<Long> ids) {
|
||||
|
|
|
@ -13,7 +13,6 @@ import com.spring.step3.service.roles.RolePermissionService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -36,7 +35,6 @@ public class RolePermissionController {
|
|||
|
||||
private final RolePermissionService rolePermissionService;
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "分页查询角色权限关联表", description = "分页角色权限关联表")
|
||||
@GetMapping("{page}/{limit}")
|
||||
public Result<PageResult<RolePermissionVo>> getRolePermissionPage(
|
||||
|
@ -51,14 +49,11 @@ public class RolePermissionController {
|
|||
}
|
||||
|
||||
@GetMapping("permissions")
|
||||
@PermitAll
|
||||
@Operation(summary = "根据角色id获取权限内容", description = "根据角色id获取权限内容")
|
||||
public Result<List<RolePermissionVo>> getRolePermissionById(Long permissionId) {
|
||||
List<RolePermissionVo> voList = rolePermissionService.getRolePermissionById(permissionId);
|
||||
return Result.success(voList);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "添加角色权限关联表", description = "添加角色权限关联表")
|
||||
@PostMapping()
|
||||
public Result<String> addRolePermission(@Valid @RequestBody RolePermissionDto dto) {
|
||||
|
@ -66,7 +61,6 @@ public class RolePermissionController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "为角色分配权限", description = "根据角色id分配权限")
|
||||
@PostMapping("assign-permission")
|
||||
public Result<String> assignRolePermission(@Valid @RequestBody AssignRolePermissionDto dto) {
|
||||
|
@ -74,7 +68,6 @@ public class RolePermissionController {
|
|||
return Result.success();
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "更新角色权限关联表", description = "更新角色权限关联表")
|
||||
@PutMapping()
|
||||
public Result<String> updateRolePermission(@Valid @RequestBody RolePermissionDto dto) {
|
||||
|
@ -82,7 +75,6 @@ public class RolePermissionController {
|
|||
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "删除角色权限关联表", description = "删除角色权限关联表")
|
||||
@DeleteMapping()
|
||||
public Result<String> deleteRolePermission(@RequestBody List<Long> ids) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
|||
* @since 2025-07-11 22:36:53
|
||||
*/
|
||||
@Tag(name = "用户基本信息表", description = "用户基本信息表相关接口")
|
||||
@PermitAll
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
@RequiredArgsConstructor
|
||||
|
@ -48,7 +49,6 @@ public class UserController {
|
|||
return Result.success(pageResult, ResultCodeEnum.LOAD_FINISHED);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "添加用户基本信息表", description = "添加用户基本信息表")
|
||||
@PostMapping()
|
||||
public Result<String> addUser(@Valid @RequestBody UserDto dto) {
|
||||
|
@ -56,7 +56,6 @@ public class UserController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "更新用户基本信息表", description = "更新用户基本信息表")
|
||||
@PutMapping()
|
||||
public Result<String> updateUser(@Valid @RequestBody UserDto dto) {
|
||||
|
@ -64,7 +63,6 @@ public class UserController {
|
|||
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "删除用户基本信息表", description = "删除用户基本信息表")
|
||||
@DeleteMapping()
|
||||
public Result<String> deleteUser(@RequestBody List<Long> ids) {
|
||||
|
|
|
@ -12,7 +12,6 @@ import com.spring.step3.service.user.UserRoleService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -35,7 +34,6 @@ public class UserRoleController {
|
|||
|
||||
private final UserRoleService userRoleService;
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "分页查询用户角色关联表", description = "分页用户角色关联表")
|
||||
@GetMapping("{page}/{limit}")
|
||||
public Result<PageResult<UserRoleVo>> getUserRolePage(
|
||||
|
@ -49,7 +47,6 @@ public class UserRoleController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "根据用户id获取当前用户角色列表", description = "根据用户id获取当前用户角色列表")
|
||||
@GetMapping("roles")
|
||||
public Result<List<UserRoleVo>> getRoleListByUserId(Long userId) {
|
||||
|
@ -57,7 +54,6 @@ public class UserRoleController {
|
|||
return Result.success(voList);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "添加用户角色关联表", description = "添加用户角色关联表")
|
||||
@PostMapping()
|
||||
public Result<String> addUserRole(@Valid @RequestBody UserRoleDto dto) {
|
||||
|
@ -65,7 +61,6 @@ public class UserRoleController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "为用户分配角色id", description = "根据用户id分配用户角色")
|
||||
@PostMapping("assign-role")
|
||||
public Result<String> assignUserRole(@Valid @RequestBody AssignUserRoleDto dto) {
|
||||
|
@ -73,7 +68,6 @@ public class UserRoleController {
|
|||
return Result.success();
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "更新用户角色关联表", description = "更新用户角色关联表")
|
||||
@PutMapping()
|
||||
public Result<String> updateUserRole(@Valid @RequestBody UserRoleDto dto) {
|
||||
|
@ -81,7 +75,6 @@ public class UserRoleController {
|
|||
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "删除用户角色关联表", description = "删除用户角色关联表")
|
||||
@DeleteMapping()
|
||||
public Result<String> deleteUserRole(@RequestBody List<Long> ids) {
|
||||
|
|
|
@ -3,19 +3,17 @@ package com.spring.step3.controller.test;
|
|||
import com.spring.step3.domain.vo.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "自定义方法前的校验", description = "自定义方法前的校验 SecurityPreAuthorization")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/security/pre")
|
||||
public class SecurityPreAuthorizationController {
|
||||
|
||||
@PreAuthorize("hasAuthority('role::read')")
|
||||
@PreAuthorize("hasPermission('role::read')")
|
||||
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
|
||||
@GetMapping("role-user")
|
||||
public Result<String> roleUser() {
|
||||
|
@ -30,7 +28,7 @@ public class SecurityPreAuthorizationController {
|
|||
return Result.success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('admin')")
|
||||
@PreAuthorize("'admin'")
|
||||
@Operation(summary = "拥有 admin 的角色可以访问", description = "当前用户拥有 admin 角色可以访问这个接口")
|
||||
@GetMapping("lower-admin")
|
||||
public Result<String> lowerAdmin() {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.spring.step3.controller.test;
|
|||
import com.spring.step3.domain.vo.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
@ -13,8 +14,11 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/security/programmatically")
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
public class SecurityProgrammaticallyController {
|
||||
|
||||
// @PreAuthorize("permitAll()")
|
||||
@PermitAll
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
|
@ -23,7 +27,7 @@ public class SecurityProgrammaticallyController {
|
|||
}
|
||||
|
||||
@PreAuthorize("@auth.decide(#name)")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@Operation(summary = "auth.decide访问", description = "auth.decide访问")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser(String name) {
|
||||
return Result.success(name);
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package com.spring.step3.domain.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(name = "AuthLogDTO对象", title = "系统授权日志表", description = "系统授权日志表的DTO对象")
|
||||
public class AuthLogDto {
|
||||
|
||||
@Schema(name = "eventType", title = "事件类型(GRANTED=授权成功,DENIED=授权拒绝)")
|
||||
private String eventType;
|
||||
|
||||
@Schema(name = "username", title = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(name = "userId", title = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(name = "requestIp", title = "请求IP")
|
||||
private String requestIp;
|
||||
|
||||
@Schema(name = "requestMethod", title = "请求方法(GET,POST等)")
|
||||
private String requestMethod;
|
||||
|
||||
@Schema(name = "requestUri", title = "请求URI")
|
||||
private String requestUri;
|
||||
|
||||
@Schema(name = "className", title = "类名")
|
||||
private String className;
|
||||
|
||||
@Schema(name = "methodName", title = "方法名")
|
||||
private String methodName;
|
||||
|
||||
@Schema(name = "methodParams", title = "方法参数(JSON格式)")
|
||||
private String methodParams;
|
||||
|
||||
@Schema(name = "requiredAuthority", title = "所需权限表达式")
|
||||
private String requiredAuthority;
|
||||
|
||||
@Schema(name = "userAuthorities", title = "用户拥有的权限(JSON格式)")
|
||||
private String userAuthorities;
|
||||
|
||||
@Schema(name = "decisionReason", title = "决策原因")
|
||||
private String decisionReason;
|
||||
|
||||
@Schema(name = "exceptionMessage", title = "异常信息")
|
||||
private String exceptionMessage;
|
||||
|
||||
@Schema(name = "isDeleted", title = "删除标志(0=未删除 1=已删除)")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.spring.step3.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.spring.step3.domain.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@TableName("sys_auth_log")
|
||||
@Schema(name = "AuthLog对象", title = "系统授权日志表", description = "系统授权日志表的实体类对象")
|
||||
public class AuthLogEntity extends BaseEntity {
|
||||
|
||||
@Schema(name = "eventType", title = "事件类型(GRANTED=授权成功,DENIED=授权拒绝)")
|
||||
private String eventType;
|
||||
|
||||
@Schema(name = "username", title = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(name = "userId", title = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(name = "requestIp", title = "请求IP")
|
||||
private String requestIp;
|
||||
|
||||
@Schema(name = "requestMethod", title = "请求方法(GET,POST等)")
|
||||
private String requestMethod;
|
||||
|
||||
@Schema(name = "requestUri", title = "请求URI")
|
||||
private String requestUri;
|
||||
|
||||
@Schema(name = "className", title = "类名")
|
||||
private String className;
|
||||
|
||||
@Schema(name = "methodName", title = "方法名")
|
||||
private String methodName;
|
||||
|
||||
@Schema(name = "methodParams", title = "方法参数(JSON格式)")
|
||||
private String methodParams;
|
||||
|
||||
@Schema(name = "requiredAuthority", title = "所需权限表达式")
|
||||
private String requiredAuthority;
|
||||
|
||||
@Schema(name = "userAuthorities", title = "用户拥有的权限(JSON格式)")
|
||||
private String userAuthorities;
|
||||
|
||||
@Schema(name = "decisionReason", title = "决策原因")
|
||||
private String decisionReason;
|
||||
|
||||
@Schema(name = "exceptionMessage", title = "异常信息")
|
||||
private String exceptionMessage;
|
||||
|
||||
@Schema(name = "isDeleted", title = "删除标志(0=未删除 1=已删除)")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.spring.step3.domain.vo;
|
||||
|
||||
import com.spring.step3.domain.vo.base.BaseVo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "AuthLogVO对象", title = "系统授权日志表", description = "系统授权日志表的VO对象")
|
||||
public class AuthLogVo extends BaseVo {
|
||||
|
||||
@Schema(name = "eventType", title = "事件类型(GRANTED=授权成功,DENIED=授权拒绝)")
|
||||
private String eventType;
|
||||
|
||||
@Schema(name = "username", title = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(name = "userId", title = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(name = "requestIp", title = "请求IP")
|
||||
private String requestIp;
|
||||
|
||||
@Schema(name = "requestMethod", title = "请求方法(GET,POST等)")
|
||||
private String requestMethod;
|
||||
|
||||
@Schema(name = "requestUri", title = "请求URI")
|
||||
private String requestUri;
|
||||
|
||||
@Schema(name = "className", title = "类名")
|
||||
private String className;
|
||||
|
||||
@Schema(name = "methodName", title = "方法名")
|
||||
private String methodName;
|
||||
|
||||
@Schema(name = "methodParams", title = "方法参数(JSON格式)")
|
||||
private String methodParams;
|
||||
|
||||
@Schema(name = "requiredAuthority", title = "所需权限表达式")
|
||||
private String requiredAuthority;
|
||||
|
||||
@Schema(name = "userAuthorities", title = "用户拥有的权限(JSON格式)")
|
||||
private String userAuthorities;
|
||||
|
||||
@Schema(name = "decisionReason", title = "决策原因")
|
||||
private String decisionReason;
|
||||
|
||||
@Schema(name = "exceptionMessage", title = "异常信息")
|
||||
private String exceptionMessage;
|
||||
|
||||
@Schema(name = "isDeleted", title = "删除标志(0=未删除 1=已删除)")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.spring.step3.mapper;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step3.domain.dto.AuthLogDto;
|
||||
import com.spring.step3.domain.entity.AuthLogEntity;
|
||||
import com.spring.step3.domain.vo.AuthLogVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统授权日志表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author AuthoritySystem
|
||||
* @since 2025-07-19 14:26:58
|
||||
*/
|
||||
@Mapper
|
||||
public interface AuthLogMapper extends BaseMapper<AuthLogEntity> {
|
||||
|
||||
/**
|
||||
* 分页查询系统授权日志表内容
|
||||
*
|
||||
* @param pageParams 系统授权日志表分页参数
|
||||
* @param dto 系统授权日志表查询表单
|
||||
* @return 系统授权日志表分页结果
|
||||
*/
|
||||
IPage<AuthLogVo> selectListByPage(@Param("page") Page<AuthLogEntity> pageParams, @Param("dto") AuthLogDto dto);
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.spring.step3.security.config;
|
|||
import com.spring.step3.security.filter.JwtAuthenticationFilter;
|
||||
import com.spring.step3.security.handler.SecurityAccessDeniedHandler;
|
||||
import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint;
|
||||
import com.spring.step3.security.properties.SecurityConfigProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
@ -14,24 +15,21 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
|||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
|
||||
@EnableMethodSecurity(jsr250Enabled = true)
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityWebConfiguration {
|
||||
|
||||
public static List<String> securedPaths = List.of("/api/**");
|
||||
public static List<String> noAuthPaths = List.of("/*/login");
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
private final SecurityConfigProperties pathsProperties;
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http
|
||||
// 前端段分离不需要---禁用明文验证
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
// .httpBasic(AbstractHttpConfigurer::disable)
|
||||
// 前端段分离不需要---禁用默认登录页
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
// 前端段分离不需要---禁用退出页
|
||||
|
@ -45,18 +43,19 @@ public class SecurityWebConfiguration {
|
|||
.sessionManagement(session ->
|
||||
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
// 如果要对部分接口做登录校验 或者 项目中需要使用粗粒度的 校验
|
||||
.authorizeHttpRequests(authorizeRequests ->
|
||||
// 访问路径为 /api 时需要进行认证
|
||||
authorizeRequests
|
||||
// 不认证登录接口
|
||||
.requestMatchers(noAuthPaths.toArray(String[]::new)).permitAll()
|
||||
// ❗只认证 securedPaths 下的所有接口
|
||||
// =======================================================================
|
||||
// 也可以在这里写多参数传入,如:"/api/**","/admin/**"
|
||||
// 但是在 Spring过滤器中,如果要放行不需要认证请求,但是需要认证的接口必需要携带token。
|
||||
// 做法是在这里定义要认证的接口,如果要做成动态可以放到数据库。
|
||||
// =======================================================================
|
||||
// .requestMatchers(securedPaths.toArray(String[]::new)).authenticated()
|
||||
// // 不认证登录接口
|
||||
// .requestMatchers(pathsProperties.noAuthPaths.toArray(String[]::new)).permitAll()
|
||||
// // ❗只认证 securedPaths 下的所有接口
|
||||
// // =======================================================================
|
||||
// // 也可以在这里写多参数传入,如:"/api/**","/admin/**"
|
||||
// // 但是在 Spring过滤器中,如果要放行不需要认证请求,但是需要认证的接口必需要携带token。
|
||||
// // 做法是在这里定义要认证的接口,如果要做成动态可以放到数据库。
|
||||
// // =======================================================================
|
||||
// .requestMatchers(pathsProperties.securedPaths.toArray(String[]::new)).authenticated()
|
||||
// 其余请求都放行
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
|
@ -66,7 +65,7 @@ public class SecurityWebConfiguration {
|
|||
// 没有权限访问
|
||||
exception.accessDeniedHandler(new SecurityAccessDeniedHandler());
|
||||
})
|
||||
.addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
;
|
||||
|
||||
return http.build();
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
package com.spring.step3.security.event;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.spring.step3.config.context.BaseContext;
|
||||
import com.spring.step3.domain.entity.AuthLogEntity;
|
||||
import com.spring.step3.service.log.AuthLogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
@ -8,14 +14,18 @@ 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 org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticationEvents {
|
||||
|
||||
private final AuthLogService authLogService;
|
||||
|
||||
/**
|
||||
* 监听拒绝授权内容
|
||||
*
|
||||
|
@ -24,29 +34,48 @@ public class AuthenticationEvents {
|
|||
@EventListener
|
||||
public void onFailure(AuthorizationDeniedEvent<MethodInvocation> failure) {
|
||||
try {
|
||||
// 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();
|
||||
// 用户名
|
||||
String username = authentication.getName();
|
||||
// 决策结果
|
||||
AuthorizationDecision decision = failure.getAuthorizationDecision();
|
||||
|
||||
AuthorizationDecision authorizationDecision = failure.getAuthorizationDecision();
|
||||
// ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasAuthority('ADMIN')]
|
||||
System.out.println(authorizationDecision);
|
||||
// 获取请求上下文信息
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
|
||||
AuthLogEntity authLog = new AuthLogEntity();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
authLog.setRequestIp(request.getRemoteAddr());
|
||||
authLog.setRequestMethod(request.getMethod());
|
||||
authLog.setRequestUri(request.getRequestURI());
|
||||
}
|
||||
|
||||
// 构建日志实体
|
||||
authLog.setEventType("DENIED");
|
||||
authLog.setUsername(username);
|
||||
// 需要实现获取用户ID的方法
|
||||
authLog.setUserId(BaseContext.getUserId());
|
||||
authLog.setClassName(method.getDeclaringClass().getName());
|
||||
authLog.setMethodName(method.getName());
|
||||
authLog.setMethodParams(JSON.toJSONString(args));
|
||||
authLog.setRequiredAuthority(decision.toString());
|
||||
authLog.setUserAuthorities(JSON.toJSONString(authentication.getAuthorities()));
|
||||
authLog.setCreateUser(BaseContext.getUserId());
|
||||
|
||||
// 保存到数据库
|
||||
authLogService.save(authLog);
|
||||
|
||||
log.warn("授权失败 - 用户: {}, 权限: {}", authentication.getName(), authorizationDecision);
|
||||
} catch (Exception e) {
|
||||
log.info(e.getMessage());
|
||||
log.error("记录授权失败日志异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import com.spring.step3.config.context.BaseContext;
|
|||
import com.spring.step3.domain.vo.result.ResultCodeEnum;
|
||||
import com.spring.step3.exception.AuthenticSecurityException;
|
||||
import com.spring.step3.exception.MyAuthenticationException;
|
||||
import com.spring.step3.security.config.SecurityWebConfiguration;
|
||||
import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint;
|
||||
import com.spring.step3.security.properties.SecurityConfigProperties;
|
||||
import com.spring.step3.security.service.DbUserDetailService;
|
||||
import com.spring.step3.security.service.JwtTokenService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
|
@ -35,6 +35,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
private final JwtTokenService jwtTokenService;
|
||||
private final DbUserDetailService userDetailsService;
|
||||
private final SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint;
|
||||
private final SecurityConfigProperties pathsProperties;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request,
|
||||
|
@ -54,8 +55,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
return;
|
||||
}
|
||||
|
||||
// 验证Token
|
||||
validateAndSetAuthentication(request, response, filterChain);
|
||||
// 验证 Token
|
||||
if (validToken(request, response, filterChain)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (AuthenticSecurityException e) {
|
||||
|
@ -74,36 +78,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是不用验证的路径
|
||||
*/
|
||||
private boolean isNoAuthPath(HttpServletRequest request) {
|
||||
RequestMatcher[] matchers = SecurityWebConfiguration.noAuthPaths.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toArray(RequestMatcher[]::new);
|
||||
return new OrRequestMatcher(matchers).matches(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是要验证的路径
|
||||
*/
|
||||
private boolean isSecurePath(HttpServletRequest request) {
|
||||
RequestMatcher[] matchers = SecurityWebConfiguration.securedPaths.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toArray(RequestMatcher[]::new);
|
||||
return new OrRequestMatcher(matchers).matches(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并设置身份验证
|
||||
*/
|
||||
private void validateAndSetAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
private boolean validToken(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws IOException, ServletException {
|
||||
// 验证Token
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
|
||||
// Token验证
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
return true;
|
||||
// throw new AuthenticSecurityException(ResultCodeEnum.LOGIN_AUTH);
|
||||
}
|
||||
|
||||
|
@ -129,5 +110,26 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
BaseContext.setUsername(username);
|
||||
BaseContext.setUserId(userId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是不用验证的路径
|
||||
*/
|
||||
private boolean isNoAuthPath(HttpServletRequest request) {
|
||||
RequestMatcher[] matchers = pathsProperties.noAuthPaths.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toArray(RequestMatcher[]::new);
|
||||
return new OrRequestMatcher(matchers).matches(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是要验证的路径
|
||||
*/
|
||||
private boolean isSecurePath(HttpServletRequest request) {
|
||||
RequestMatcher[] matchers = pathsProperties.securedPaths.stream()
|
||||
.map(AntPathRequestMatcher::new)
|
||||
.toArray(RequestMatcher[]::new);
|
||||
return new OrRequestMatcher(matchers).matches(request);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
|
|||
log.error("SecurityAccessDeniedHandler:{}", accessDeniedException.getLocalizedMessage());
|
||||
|
||||
// 无权访问接口
|
||||
Result<Object> result = Result.error(accessDeniedException.getMessage(), ResultCodeEnum.LOGIN_AUTH);
|
||||
Result<Object> result = Result.error(accessDeniedException.getMessage(), ResultCodeEnum.FAIL_NO_ACCESS_DENIED);
|
||||
ResponseUtil.out(response, result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.spring.step3.security.manger;
|
||||
|
||||
import com.spring.step3.domain.vo.result.Result;
|
||||
import com.spring.step3.security.properties.SecurityConfigProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
|
@ -18,40 +20,11 @@ import java.util.function.Supplier;
|
|||
* 这是Spring Security较新的"后置授权"功能
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
|
||||
/**
|
||||
* 这里两个实现方法按照Security官方要求进行实现
|
||||
* <h4>类说明:</h4>
|
||||
* 下面的实现是对方法执行前进行权限校验的判断
|
||||
* <pre>
|
||||
* <code>AuthorizationManager <MethodInvocation></code>
|
||||
* </pre>
|
||||
* 下面的这个是对方法执行后对权限的判断
|
||||
* <pre>
|
||||
* <code>AuthorizationManager <MethodInvocationResult></code>
|
||||
* </pre>
|
||||
*
|
||||
* <h4>注意事项:</h4>
|
||||
* 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。
|
||||
* <pre>
|
||||
* @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);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
private final SecurityConfigProperties securityConfigProperties;
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocationResult methodInvocationResult) {
|
||||
Authentication authentication = authenticationSupplier.get();
|
||||
|
@ -67,17 +40,19 @@ public class PostAuthorizationManager implements AuthorizationManager<MethodInvo
|
|||
}
|
||||
|
||||
private boolean hasPermission(Authentication authentication, MethodInvocationResult methodInvocationResult) {
|
||||
// 1. 获取当前校验方法的返回值
|
||||
// 获取当前校验方法的返回值
|
||||
if (methodInvocationResult.getResult() instanceof Result<?> result) {
|
||||
// 拿到当前返回值中权限内容
|
||||
List<String> auths = result.getAuths();
|
||||
|
||||
// 允许全局访问的 角色或权限
|
||||
List<String> adminAuthorities = securityConfigProperties.adminAuthorities;
|
||||
|
||||
// 判断返回值中返回方法全新啊是否和用户权限匹配
|
||||
return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority)
|
||||
.anyMatch(auth ->
|
||||
// ❗这里是忽略了大小写匹配的 admin 权限,如果包含 admin 无论大小写都可以放行
|
||||
auth.equalsIgnoreCase("admin")
|
||||
|| auths.contains(auth)
|
||||
// 允许放行的角色或权限 和 匹配到的角色或权限
|
||||
adminAuthorities.contains(auth) || auths.contains(auth)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
package com.spring.step3.security.manger;
|
||||
|
||||
import com.spring.step3.security.properties.SecurityConfigProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 处理方法调用前的授权检查
|
||||
|
@ -17,40 +24,11 @@ import java.util.function.Supplier;
|
|||
* 这是传统的"前置授权"模式
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
/**
|
||||
* 这里两个实现方法按照Security官方要求进行实现
|
||||
* <h4>类说明:</h4>
|
||||
* 下面的实现是对方法执行前进行权限校验的判断
|
||||
* <pre>
|
||||
* <code>AuthorizationManager <MethodInvocation></code>
|
||||
* </pre>
|
||||
* 下面的这个是对方法执行后对权限的判断
|
||||
* <pre>
|
||||
* <code>AuthorizationManager <MethodInvocationResult></code>
|
||||
* </pre>
|
||||
*
|
||||
* <h4>注意事项:</h4>
|
||||
* 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。
|
||||
* <pre>
|
||||
* @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);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
private final SecurityConfigProperties securityConfigProperties;
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocation methodInvocation) {
|
||||
Authentication authentication = authenticationSupplier.get();
|
||||
|
@ -66,18 +44,63 @@ public class PreAuthorizationManager implements AuthorizationManager<MethodInvoc
|
|||
}
|
||||
|
||||
private boolean hasPermission(Authentication authentication, MethodInvocation methodInvocation) {
|
||||
// 1. 获取方法上的权限注解(如果有)
|
||||
// 例如:@PreAuthorize("hasRole('ADMIN')") 或其他自定义注解
|
||||
PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class);
|
||||
if (preAuthorize == null) {
|
||||
return true; // 没有注解默认放行
|
||||
}
|
||||
|
||||
// 2. 获取用户权限
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
String expression = preAuthorize.value();
|
||||
// 解析表达式中的权限要求
|
||||
List<String> requiredAuthorities = extractAuthoritiesFromExpression(expression);
|
||||
|
||||
// 3. 实现你的权限逻辑
|
||||
// 这里简单示例:检查方法名是否包含在权限中
|
||||
String methodName = methodInvocation.getMethod().getName();
|
||||
return authorities.stream()
|
||||
// 获取配置的admin权限
|
||||
List<String> adminAuthorities = securityConfigProperties.getAdminAuthorities();
|
||||
|
||||
return authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
// ❗这里是忽略了大小写匹配的 admin 权限,如果包含 admin 无论大小写都可以放行
|
||||
.anyMatch(auth -> auth.equalsIgnoreCase("admin") || auth.equals(methodName));
|
||||
.anyMatch(auth ->
|
||||
adminAuthorities.contains(auth) ||
|
||||
requiredAuthorities.contains(auth)
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> extractAuthoritiesFromExpression(String expression) {
|
||||
List<String> authorities = new ArrayList<>();
|
||||
|
||||
// 处理 hasAuthority('permission') 格式
|
||||
Pattern hasAuthorityPattern = Pattern.compile("hasAuthority\\('([^']+)'\\)");
|
||||
Matcher hasAuthorityMatcher = hasAuthorityPattern.matcher(expression);
|
||||
while (hasAuthorityMatcher.find()) {
|
||||
authorities.add(hasAuthorityMatcher.group(1));
|
||||
}
|
||||
|
||||
// 处理 hasRole('ROLE_XXX') 格式 (Spring Security 会自动添加 ROLE_ 前缀)
|
||||
Pattern hasRolePattern = Pattern.compile("hasRole\\('([^']+)'\\)");
|
||||
Matcher hasRoleMatcher = hasRolePattern.matcher(expression);
|
||||
while (hasRoleMatcher.find()) {
|
||||
authorities.add(hasRoleMatcher.group(1));
|
||||
}
|
||||
|
||||
// 处理 hasAnyAuthority('perm1','perm2') 格式
|
||||
Pattern hasAnyAuthorityPattern = Pattern.compile("hasAnyAuthority\\(([^)]+)\\)");
|
||||
Matcher hasAnyAuthorityMatcher = hasAnyAuthorityPattern.matcher(expression);
|
||||
while (hasAnyAuthorityMatcher.find()) {
|
||||
String[] perms = hasAnyAuthorityMatcher.group(1).split(",");
|
||||
for (String perm : perms) {
|
||||
authorities.add(perm.trim().replaceAll("'", ""));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 hasAnyRole('role1','role2') 格式
|
||||
Pattern hasAnyRolePattern = Pattern.compile("hasAnyRole\\(([^)]+)\\)");
|
||||
Matcher hasAnyRoleMatcher = hasAnyRolePattern.matcher(expression);
|
||||
while (hasAnyRoleMatcher.find()) {
|
||||
String[] roles = hasAnyRoleMatcher.group(1).split(",");
|
||||
for (String role : roles) {
|
||||
authorities.add(role.trim().replaceAll("'", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.spring.step3.security.properties;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "security-path")
|
||||
@Schema(name = "SecurityPathsProperties对象", description = "路径忽略和认证")
|
||||
public class SecurityConfigProperties {
|
||||
|
||||
@Schema(name = "noAuthPaths", description = "不用认证的路径")
|
||||
public List<String> noAuthPaths;
|
||||
|
||||
@Schema(name = "securedPaths", description = "需要认证的路径")
|
||||
public List<String> securedPaths;
|
||||
|
||||
@Schema(name = "允许的角色或权限", description = "允许的角色或权限")
|
||||
public List<String> adminAuthorities;
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.spring.step3.service.log;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.spring.step3.domain.dto.AuthLogDto;
|
||||
import com.spring.step3.domain.entity.AuthLogEntity;
|
||||
import com.spring.step3.domain.vo.AuthLogVo;
|
||||
import com.spring.step3.domain.vo.result.PageResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统授权日志表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author Bunny
|
||||
* @since 2025-07-19 14:26:58
|
||||
*/
|
||||
public interface AuthLogService extends IService<AuthLogEntity> {
|
||||
|
||||
/**
|
||||
* 分页查询系统授权日志表
|
||||
*
|
||||
* @return 系统授权日志表分页结果 {@link AuthLogVo}
|
||||
*/
|
||||
PageResult<AuthLogVo> getAuthLogPage(Page<AuthLogEntity> pageParams, AuthLogDto dto);
|
||||
|
||||
/**
|
||||
* 根据id查询系统授权日志表详情
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 系统授权日志表详情 AuthLogVo}
|
||||
*/
|
||||
AuthLogVo getAuthLogById(Long id);
|
||||
|
||||
/**
|
||||
* 添加系统授权日志表
|
||||
*
|
||||
* @param dto {@link AuthLogDto} 添加表单
|
||||
*/
|
||||
void addAuthLog(AuthLogDto dto);
|
||||
|
||||
/**
|
||||
* 更新系统授权日志表
|
||||
*
|
||||
* @param dto {@link AuthLogDto} 更新表单
|
||||
*/
|
||||
void updateAuthLog(AuthLogDto dto);
|
||||
|
||||
/**
|
||||
* 删除|批量删除系统授权日志表类型
|
||||
*
|
||||
* @param ids 删除id列表
|
||||
*/
|
||||
void deleteAuthLog(List<Long> ids);
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package com.spring.step3.service.log.impl;
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.spring.step3.domain.dto.AuthLogDto;
|
||||
import com.spring.step3.domain.entity.AuthLogEntity;
|
||||
import com.spring.step3.domain.vo.AuthLogVo;
|
||||
import com.spring.step3.domain.vo.result.PageResult;
|
||||
import com.spring.step3.mapper.AuthLogMapper;
|
||||
import com.spring.step3.service.log.AuthLogService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统授权日志表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author Bunny
|
||||
* @since 2025-07-19 14:26:58
|
||||
*/
|
||||
@DS("testJwt")
|
||||
@Service
|
||||
@Transactional
|
||||
public class AuthLogServiceImpl extends ServiceImpl<AuthLogMapper, AuthLogEntity> implements AuthLogService {
|
||||
|
||||
/**
|
||||
* 系统授权日志表 服务实现类
|
||||
*
|
||||
* @param pageParams 系统授权日志表分页查询page对象
|
||||
* @param dto 系统授权日志表分页查询对象
|
||||
* @return 查询分页系统授权日志表返回对象
|
||||
*/
|
||||
@Override
|
||||
public PageResult<AuthLogVo> getAuthLogPage(Page<AuthLogEntity> pageParams, AuthLogDto dto) {
|
||||
IPage<AuthLogVo> page = baseMapper.selectListByPage(pageParams, dto);
|
||||
|
||||
return PageResult.<AuthLogVo>builder()
|
||||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询系统授权日志表详情
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 系统授权日志表详情 AuthLogVo}
|
||||
*/
|
||||
public AuthLogVo getAuthLogById(Long id) {
|
||||
AuthLogEntity authLogEntity = getById(id);
|
||||
|
||||
AuthLogVo authLogVo = new AuthLogVo();
|
||||
BeanUtils.copyProperties(authLogEntity, authLogVo);
|
||||
|
||||
return authLogVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统授权日志表
|
||||
*
|
||||
* @param dto 系统授权日志表添加
|
||||
*/
|
||||
@Override
|
||||
public void addAuthLog(AuthLogDto dto) {
|
||||
AuthLogEntity authLog = new AuthLogEntity();
|
||||
BeanUtils.copyProperties(dto, authLog);
|
||||
|
||||
save(authLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统授权日志表
|
||||
*
|
||||
* @param dto 系统授权日志表更新
|
||||
*/
|
||||
@Override
|
||||
public void updateAuthLog(AuthLogDto dto) {
|
||||
AuthLogEntity authLog = new AuthLogEntity();
|
||||
BeanUtils.copyProperties(dto, authLog);
|
||||
|
||||
updateById(authLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除|批量删除系统授权日志表
|
||||
*
|
||||
* @param ids 删除id列表
|
||||
*/
|
||||
@Override
|
||||
public void deleteAuthLog(List<Long> ids) {
|
||||
removeByIds(ids);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
security-path:
|
||||
secured-paths:
|
||||
- "/api/**"
|
||||
no-auth-paths:
|
||||
- "/*/login"
|
||||
# - "/api/security/**"
|
||||
- "/api/permission/**"
|
||||
- "/api/role/**"
|
||||
- "/api/role-permission/**"
|
||||
- "/api/user/**"
|
||||
- "/api/user-role/**"
|
||||
admin-authorities:
|
||||
- "ADMIN"
|
|
@ -6,6 +6,8 @@ spring:
|
|||
name: spring-security
|
||||
profiles:
|
||||
active: dev
|
||||
include:
|
||||
- security
|
||||
devtools:
|
||||
livereload:
|
||||
port: 0
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.spring.step3.mapper.AuthLogMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.spring.step3.domain.entity.AuthLogEntity">
|
||||
<id column="id" property="id"/>
|
||||
<id column="event_type" property="eventType"/>
|
||||
<id column="username" property="username"/>
|
||||
<id column="user_id" property="userId"/>
|
||||
<id column="request_ip" property="requestIp"/>
|
||||
<id column="request_method" property="requestMethod"/>
|
||||
<id column="request_uri" property="requestUri"/>
|
||||
<id column="class_name" property="className"/>
|
||||
<id column="method_name" property="methodName"/>
|
||||
<id column="method_params" property="methodParams"/>
|
||||
<id column="required_authority" property="requiredAuthority"/>
|
||||
<id column="user_authorities" property="userAuthorities"/>
|
||||
<id column="decision_reason" property="decisionReason"/>
|
||||
<id column="exception_message" property="exceptionMessage"/>
|
||||
<id column="is_deleted" property="isDeleted"/>
|
||||
<id column="create_time" property="createTime"/>
|
||||
<id column="update_time" property="updateTime"/>
|
||||
<id column="create_user" property="createUser"/>
|
||||
<id column="update_user" property="updateUser"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, event_type,username,user_id,request_ip,request_method,request_uri,class_name,method_name,method_params,required_authority,user_authorities,decision_reason,exception_message,is_deleted, create_time, update_time, create_user, update_user
|
||||
</sql>
|
||||
|
||||
<!-- 分页查询系统授权日志表内容 -->
|
||||
<select id="selectListByPage" resultType="com.spring.step3.domain.vo.AuthLogVo">
|
||||
select
|
||||
base.*,
|
||||
create_user.username as create_username,
|
||||
update_user.username as update_username
|
||||
from sys_auth_log base
|
||||
left join sys_user create_user on create_user.id = base.create_user
|
||||
left join sys_user update_user on update_user.id = base.update_user
|
||||
<where>
|
||||
base.is_deleted = 0
|
||||
<if test="dto.eventType != null and dto.eventType != ''">
|
||||
and base.event_type like CONCAT('%',#{dto.eventType},'%')
|
||||
</if>
|
||||
<if test="dto.username != null and dto.username != ''">
|
||||
and base.username like CONCAT('%',#{dto.username},'%')
|
||||
</if>
|
||||
<if test="dto.userId != null and dto.userId != ''">
|
||||
and base.user_id like CONCAT('%',#{dto.userId},'%')
|
||||
</if>
|
||||
<if test="dto.requestIp != null and dto.requestIp != ''">
|
||||
and base.request_ip like CONCAT('%',#{dto.requestIp},'%')
|
||||
</if>
|
||||
<if test="dto.requestMethod != null and dto.requestMethod != ''">
|
||||
and base.request_method like CONCAT('%',#{dto.requestMethod},'%')
|
||||
</if>
|
||||
<if test="dto.requestUri != null and dto.requestUri != ''">
|
||||
and base.request_uri like CONCAT('%',#{dto.requestUri},'%')
|
||||
</if>
|
||||
<if test="dto.className != null and dto.className != ''">
|
||||
and base.class_name like CONCAT('%',#{dto.className},'%')
|
||||
</if>
|
||||
<if test="dto.methodName != null and dto.methodName != ''">
|
||||
and base.method_name like CONCAT('%',#{dto.methodName},'%')
|
||||
</if>
|
||||
<if test="dto.methodParams != null and dto.methodParams != ''">
|
||||
and base.method_params like CONCAT('%',#{dto.methodParams},'%')
|
||||
</if>
|
||||
<if test="dto.requiredAuthority != null and dto.requiredAuthority != ''">
|
||||
and base.required_authority like CONCAT('%',#{dto.requiredAuthority},'%')
|
||||
</if>
|
||||
<if test="dto.userAuthorities != null and dto.userAuthorities != ''">
|
||||
and base.user_authorities like CONCAT('%',#{dto.userAuthorities},'%')
|
||||
</if>
|
||||
<if test="dto.decisionReason != null and dto.decisionReason != ''">
|
||||
and base.decision_reason like CONCAT('%',#{dto.decisionReason},'%')
|
||||
</if>
|
||||
<if test="dto.exceptionMessage != null and dto.exceptionMessage != ''">
|
||||
and base.exception_message like CONCAT('%',#{dto.exceptionMessage},'%')
|
||||
</if>
|
||||
<if test="dto.isDeleted != null and dto.isDeleted != ''">
|
||||
and base.is_deleted like CONCAT('%',#{dto.isDeleted},'%')
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
</mapper>
|
Loading…
Reference in New Issue