自定义覆盖原校验方式

This commit is contained in:
bunny 2025-07-19 23:22:08 +08:00
parent 1bb9a251a7
commit d8a34d7fa6
25 changed files with 1165 additions and 195 deletions

View File

@ -1137,19 +1137,88 @@ public Result<String> lowerUser(String name) {
在方法中写入自己的校验逻辑。 在方法中写入自己的校验逻辑。
`MethodInvocation`类型是执行方法之前相当于是一个反射,可以获取到当前方法上的注解、方法名称、当前类等。
```java ```java
/**
* 处理方法调用后的授权检查
* check()方法接收的是MethodInvocationResult对象包含已执行方法的结果
* 用于决定是否允许返回某个方法的结果(后置过滤)
* 这是Spring Security较新的"后置授权"功能
*/
@Component @Component
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> { @RequiredArgsConstructor
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private final SecurityConfigProperties securityConfigProperties;
@Override @Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) { public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocation methodInvocation) {
return new AuthorizationDecision(true); 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 ```java
/** /**
* 处理方法调用的授权检查 * 处理方法调用的授权检查
* check()方法接收的是MethodInvocation对象,包含即将执行的方法调用信息 * check()方法接收的是MethodInvocationResult对象包含已执行方法的结果
* 用于决定是否允许执行某个方法 * 用于决定是否允许返回某个方法的结果(后置过滤)
* 这是传统的"前置授权"模式 * 这是Spring Security较新的"后置授权"功能
*/ */
@Component @Component
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> { @RequiredArgsConstructor
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
private final SecurityConfigProperties securityConfigProperties;
@Override @Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) { public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocationResult methodInvocationResult) {
return new AuthorizationDecision(true); 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. 禁用自带的 #### 3. 禁用自带的
> [!IMPORTANT]
>
> 这是一个非常关键的一步,如果需要实现自定义,必须要禁用原来的,替换之前的;否则不会生效。
需要加上注解`@EnableMethodSecurity(prePostEnabled = false)`。 需要加上注解`@EnableMethodSecurity(prePostEnabled = false)`。
```java ```java
@ -1535,3 +1644,371 @@ Bunny
[permission::read, role::read] [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);
}
}
```

View File

@ -11,7 +11,6 @@ import com.spring.step2.service.RoleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -48,7 +47,6 @@ public class RoleController {
return Result.success(pageResult); return Result.success(pageResult);
} }
@PermitAll
@Operation(summary = "获取全部角色列表", description = "获取全部角色列表") @Operation(summary = "获取全部角色列表", description = "获取全部角色列表")
@GetMapping("all") @GetMapping("all")
public Result<List<RoleVo>> getRoleList() { public Result<List<RoleVo>> getRoleList() {

View File

@ -12,7 +12,6 @@ import com.spring.step3.service.roles.PermissionService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -35,7 +34,6 @@ public class PermissionController {
private final PermissionService permissionService; private final PermissionService permissionService;
@PermitAll
@Operation(summary = "分页查询系统权限表", description = "分页系统权限表") @Operation(summary = "分页查询系统权限表", description = "分页系统权限表")
@GetMapping("{page}/{limit}") @GetMapping("{page}/{limit}")
public Result<PageResult<PermissionVo>> getPermissionPage( public Result<PageResult<PermissionVo>> getPermissionPage(
@ -49,7 +47,6 @@ public class PermissionController {
return Result.success(pageResult); return Result.success(pageResult);
} }
@PermitAll
@Operation(summary = "所有的权限列表", description = "获取所有的权限列表") @Operation(summary = "所有的权限列表", description = "获取所有的权限列表")
@GetMapping("all") @GetMapping("all")
public Result<List<PermissionVo>> getAllPermission() { public Result<List<PermissionVo>> getAllPermission() {
@ -57,7 +54,6 @@ public class PermissionController {
return Result.success(voList); return Result.success(voList);
} }
@PermitAll
@Operation(summary = "添加系统权限表", description = "添加系统权限表") @Operation(summary = "添加系统权限表", description = "添加系统权限表")
@PostMapping() @PostMapping()
public Result<String> addPermission(@Valid @RequestBody PermissionDto dto) { public Result<String> addPermission(@Valid @RequestBody PermissionDto dto) {
@ -65,7 +61,6 @@ public class PermissionController {
return Result.success(ResultCodeEnum.ADD_SUCCESS); return Result.success(ResultCodeEnum.ADD_SUCCESS);
} }
@PermitAll
@Operation(summary = "更新系统权限表", description = "更新系统权限表") @Operation(summary = "更新系统权限表", description = "更新系统权限表")
@PutMapping() @PutMapping()
public Result<String> updatePermission(@Valid @RequestBody PermissionDto dto) { public Result<String> updatePermission(@Valid @RequestBody PermissionDto dto) {
@ -73,7 +68,6 @@ public class PermissionController {
return Result.success(ResultCodeEnum.UPDATE_SUCCESS); return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
} }
@PermitAll
@Operation(summary = "删除系统权限表", description = "删除系统权限表") @Operation(summary = "删除系统权限表", description = "删除系统权限表")
@DeleteMapping() @DeleteMapping()
public Result<String> deletePermission(@RequestBody List<Long> ids) { public Result<String> deletePermission(@RequestBody List<Long> ids) {

View File

@ -11,7 +11,6 @@ import com.spring.step3.service.roles.RoleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -35,7 +34,6 @@ public class RoleController {
private final RoleService roleService; private final RoleService roleService;
@PermitAll
@Operation(summary = "分页查询系统角色表", description = "分页系统角色表") @Operation(summary = "分页查询系统角色表", description = "分页系统角色表")
@GetMapping("{page}/{limit}") @GetMapping("{page}/{limit}")
public Result<PageResult<RoleVo>> getRolePage( public Result<PageResult<RoleVo>> getRolePage(
@ -49,7 +47,6 @@ public class RoleController {
return Result.success(pageResult); return Result.success(pageResult);
} }
@PermitAll
@Operation(summary = "获取全部角色列表", description = "获取全部角色列表") @Operation(summary = "获取全部角色列表", description = "获取全部角色列表")
@GetMapping("all") @GetMapping("all")
public Result<List<RoleVo>> getRoleList() { public Result<List<RoleVo>> getRoleList() {
@ -57,7 +54,6 @@ public class RoleController {
return Result.success(roleVoList); return Result.success(roleVoList);
} }
@PermitAll
@Operation(summary = "添加系统角色表", description = "添加系统角色表") @Operation(summary = "添加系统角色表", description = "添加系统角色表")
@PostMapping() @PostMapping()
public Result<String> addRole(@Valid @RequestBody RoleDto dto) { public Result<String> addRole(@Valid @RequestBody RoleDto dto) {
@ -65,7 +61,6 @@ public class RoleController {
return Result.success(ResultCodeEnum.ADD_SUCCESS); return Result.success(ResultCodeEnum.ADD_SUCCESS);
} }
@PermitAll
@Operation(summary = "更新系统角色表", description = "更新系统角色表") @Operation(summary = "更新系统角色表", description = "更新系统角色表")
@PutMapping() @PutMapping()
public Result<String> updateRole(@Valid @RequestBody RoleDto dto) { public Result<String> updateRole(@Valid @RequestBody RoleDto dto) {
@ -73,7 +68,6 @@ public class RoleController {
return Result.success(ResultCodeEnum.UPDATE_SUCCESS); return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
} }
@PermitAll
@Operation(summary = "删除系统角色表", description = "删除系统角色表") @Operation(summary = "删除系统角色表", description = "删除系统角色表")
@DeleteMapping() @DeleteMapping()
public Result<String> deleteRole(@RequestBody List<Long> ids) { public Result<String> deleteRole(@RequestBody List<Long> ids) {

View File

@ -13,7 +13,6 @@ import com.spring.step3.service.roles.RolePermissionService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -36,7 +35,6 @@ public class RolePermissionController {
private final RolePermissionService rolePermissionService; private final RolePermissionService rolePermissionService;
@PermitAll
@Operation(summary = "分页查询角色权限关联表", description = "分页角色权限关联表") @Operation(summary = "分页查询角色权限关联表", description = "分页角色权限关联表")
@GetMapping("{page}/{limit}") @GetMapping("{page}/{limit}")
public Result<PageResult<RolePermissionVo>> getRolePermissionPage( public Result<PageResult<RolePermissionVo>> getRolePermissionPage(
@ -51,14 +49,11 @@ public class RolePermissionController {
} }
@GetMapping("permissions") @GetMapping("permissions")
@PermitAll
@Operation(summary = "根据角色id获取权限内容", description = "根据角色id获取权限内容")
public Result<List<RolePermissionVo>> getRolePermissionById(Long permissionId) { public Result<List<RolePermissionVo>> getRolePermissionById(Long permissionId) {
List<RolePermissionVo> voList = rolePermissionService.getRolePermissionById(permissionId); List<RolePermissionVo> voList = rolePermissionService.getRolePermissionById(permissionId);
return Result.success(voList); return Result.success(voList);
} }
@PermitAll
@Operation(summary = "添加角色权限关联表", description = "添加角色权限关联表") @Operation(summary = "添加角色权限关联表", description = "添加角色权限关联表")
@PostMapping() @PostMapping()
public Result<String> addRolePermission(@Valid @RequestBody RolePermissionDto dto) { public Result<String> addRolePermission(@Valid @RequestBody RolePermissionDto dto) {
@ -66,7 +61,6 @@ public class RolePermissionController {
return Result.success(ResultCodeEnum.ADD_SUCCESS); return Result.success(ResultCodeEnum.ADD_SUCCESS);
} }
@PermitAll
@Operation(summary = "为角色分配权限", description = "根据角色id分配权限") @Operation(summary = "为角色分配权限", description = "根据角色id分配权限")
@PostMapping("assign-permission") @PostMapping("assign-permission")
public Result<String> assignRolePermission(@Valid @RequestBody AssignRolePermissionDto dto) { public Result<String> assignRolePermission(@Valid @RequestBody AssignRolePermissionDto dto) {
@ -74,7 +68,6 @@ public class RolePermissionController {
return Result.success(); return Result.success();
} }
@PermitAll
@Operation(summary = "更新角色权限关联表", description = "更新角色权限关联表") @Operation(summary = "更新角色权限关联表", description = "更新角色权限关联表")
@PutMapping() @PutMapping()
public Result<String> updateRolePermission(@Valid @RequestBody RolePermissionDto dto) { public Result<String> updateRolePermission(@Valid @RequestBody RolePermissionDto dto) {
@ -82,7 +75,6 @@ public class RolePermissionController {
return Result.success(ResultCodeEnum.UPDATE_SUCCESS); return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
} }
@PermitAll
@Operation(summary = "删除角色权限关联表", description = "删除角色权限关联表") @Operation(summary = "删除角色权限关联表", description = "删除角色权限关联表")
@DeleteMapping() @DeleteMapping()
public Result<String> deleteRolePermission(@RequestBody List<Long> ids) { public Result<String> deleteRolePermission(@RequestBody List<Long> ids) {

View File

@ -27,6 +27,7 @@ import java.util.List;
* @since 2025-07-11 22:36:53 * @since 2025-07-11 22:36:53
*/ */
@Tag(name = "用户基本信息表", description = "用户基本信息表相关接口") @Tag(name = "用户基本信息表", description = "用户基本信息表相关接口")
@PermitAll
@RestController @RestController
@RequestMapping("/api/user") @RequestMapping("/api/user")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -48,7 +49,6 @@ public class UserController {
return Result.success(pageResult, ResultCodeEnum.LOAD_FINISHED); return Result.success(pageResult, ResultCodeEnum.LOAD_FINISHED);
} }
@PermitAll
@Operation(summary = "添加用户基本信息表", description = "添加用户基本信息表") @Operation(summary = "添加用户基本信息表", description = "添加用户基本信息表")
@PostMapping() @PostMapping()
public Result<String> addUser(@Valid @RequestBody UserDto dto) { public Result<String> addUser(@Valid @RequestBody UserDto dto) {
@ -56,7 +56,6 @@ public class UserController {
return Result.success(ResultCodeEnum.ADD_SUCCESS); return Result.success(ResultCodeEnum.ADD_SUCCESS);
} }
@PermitAll
@Operation(summary = "更新用户基本信息表", description = "更新用户基本信息表") @Operation(summary = "更新用户基本信息表", description = "更新用户基本信息表")
@PutMapping() @PutMapping()
public Result<String> updateUser(@Valid @RequestBody UserDto dto) { public Result<String> updateUser(@Valid @RequestBody UserDto dto) {
@ -64,7 +63,6 @@ public class UserController {
return Result.success(ResultCodeEnum.UPDATE_SUCCESS); return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
} }
@PermitAll
@Operation(summary = "删除用户基本信息表", description = "删除用户基本信息表") @Operation(summary = "删除用户基本信息表", description = "删除用户基本信息表")
@DeleteMapping() @DeleteMapping()
public Result<String> deleteUser(@RequestBody List<Long> ids) { public Result<String> deleteUser(@RequestBody List<Long> ids) {

View File

@ -12,7 +12,6 @@ import com.spring.step3.service.user.UserRoleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -35,7 +34,6 @@ public class UserRoleController {
private final UserRoleService userRoleService; private final UserRoleService userRoleService;
@PermitAll
@Operation(summary = "分页查询用户角色关联表", description = "分页用户角色关联表") @Operation(summary = "分页查询用户角色关联表", description = "分页用户角色关联表")
@GetMapping("{page}/{limit}") @GetMapping("{page}/{limit}")
public Result<PageResult<UserRoleVo>> getUserRolePage( public Result<PageResult<UserRoleVo>> getUserRolePage(
@ -49,7 +47,6 @@ public class UserRoleController {
return Result.success(pageResult); return Result.success(pageResult);
} }
@PermitAll
@Operation(summary = "根据用户id获取当前用户角色列表", description = "根据用户id获取当前用户角色列表") @Operation(summary = "根据用户id获取当前用户角色列表", description = "根据用户id获取当前用户角色列表")
@GetMapping("roles") @GetMapping("roles")
public Result<List<UserRoleVo>> getRoleListByUserId(Long userId) { public Result<List<UserRoleVo>> getRoleListByUserId(Long userId) {
@ -57,7 +54,6 @@ public class UserRoleController {
return Result.success(voList); return Result.success(voList);
} }
@PermitAll
@Operation(summary = "添加用户角色关联表", description = "添加用户角色关联表") @Operation(summary = "添加用户角色关联表", description = "添加用户角色关联表")
@PostMapping() @PostMapping()
public Result<String> addUserRole(@Valid @RequestBody UserRoleDto dto) { public Result<String> addUserRole(@Valid @RequestBody UserRoleDto dto) {
@ -65,7 +61,6 @@ public class UserRoleController {
return Result.success(ResultCodeEnum.ADD_SUCCESS); return Result.success(ResultCodeEnum.ADD_SUCCESS);
} }
@PermitAll
@Operation(summary = "为用户分配角色id", description = "根据用户id分配用户角色") @Operation(summary = "为用户分配角色id", description = "根据用户id分配用户角色")
@PostMapping("assign-role") @PostMapping("assign-role")
public Result<String> assignUserRole(@Valid @RequestBody AssignUserRoleDto dto) { public Result<String> assignUserRole(@Valid @RequestBody AssignUserRoleDto dto) {
@ -73,7 +68,6 @@ public class UserRoleController {
return Result.success(); return Result.success();
} }
@PermitAll
@Operation(summary = "更新用户角色关联表", description = "更新用户角色关联表") @Operation(summary = "更新用户角色关联表", description = "更新用户角色关联表")
@PutMapping() @PutMapping()
public Result<String> updateUserRole(@Valid @RequestBody UserRoleDto dto) { public Result<String> updateUserRole(@Valid @RequestBody UserRoleDto dto) {
@ -81,7 +75,6 @@ public class UserRoleController {
return Result.success(ResultCodeEnum.UPDATE_SUCCESS); return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
} }
@PermitAll
@Operation(summary = "删除用户角色关联表", description = "删除用户角色关联表") @Operation(summary = "删除用户角色关联表", description = "删除用户角色关联表")
@DeleteMapping() @DeleteMapping()
public Result<String> deleteUserRole(@RequestBody List<Long> ids) { public Result<String> deleteUserRole(@RequestBody List<Long> ids) {

View File

@ -3,19 +3,17 @@ package com.spring.step3.controller.test;
import com.spring.step3.domain.vo.result.Result; import com.spring.step3.domain.vo.result.Result;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Tag(name = "自定义方法前的校验", description = "自定义方法前的校验 SecurityPreAuthorization") @Tag(name = "自定义方法前的校验", description = "自定义方法前的校验 SecurityPreAuthorization")
@Slf4j
@RestController @RestController
@RequestMapping("/api/security/pre") @RequestMapping("/api/security/pre")
public class SecurityPreAuthorizationController { public class SecurityPreAuthorizationController {
@PreAuthorize("hasAuthority('role::read')") @PreAuthorize("hasPermission('role::read')")
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口") @Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
@GetMapping("role-user") @GetMapping("role-user")
public Result<String> roleUser() { public Result<String> roleUser() {
@ -30,7 +28,7 @@ public class SecurityPreAuthorizationController {
return Result.success(data); return Result.success(data);
} }
@PreAuthorize("hasAuthority('admin')") @PreAuthorize("'admin'")
@Operation(summary = "拥有 admin 的角色可以访问", description = "当前用户拥有 admin 角色可以访问这个接口") @Operation(summary = "拥有 admin 的角色可以访问", description = "当前用户拥有 admin 角色可以访问这个接口")
@GetMapping("lower-admin") @GetMapping("lower-admin")
public Result<String> lowerAdmin() { public Result<String> lowerAdmin() {

View File

@ -3,6 +3,7 @@ package com.spring.step3.controller.test;
import com.spring.step3.domain.vo.result.Result; import com.spring.step3.domain.vo.result.Result;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -13,8 +14,11 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/api/security/programmatically") @RequestMapping("/api/security/programmatically")
@PreAuthorize("hasAuthority('USER')")
public class SecurityProgrammaticallyController { public class SecurityProgrammaticallyController {
// @PreAuthorize("permitAll()")
@PermitAll
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口") @Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
@GetMapping("upper-user") @GetMapping("upper-user")
public Result<String> upperUser() { public Result<String> upperUser() {
@ -23,7 +27,7 @@ public class SecurityProgrammaticallyController {
} }
@PreAuthorize("@auth.decide(#name)") @PreAuthorize("@auth.decide(#name)")
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口") @Operation(summary = "auth.decide访问", description = "auth.decide访问")
@GetMapping("lower-user") @GetMapping("lower-user")
public Result<String> lowerUser(String name) { public Result<String> lowerUser(String name) {
return Result.success(name); return Result.success(name);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -3,6 +3,7 @@ package com.spring.step3.security.config;
import com.spring.step3.security.filter.JwtAuthenticationFilter; import com.spring.step3.security.filter.JwtAuthenticationFilter;
import com.spring.step3.security.handler.SecurityAccessDeniedHandler; import com.spring.step3.security.handler.SecurityAccessDeniedHandler;
import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint; import com.spring.step3.security.handler.SecurityAuthenticationEntryPoint;
import com.spring.step3.security.properties.SecurityConfigProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.List;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true) @EnableMethodSecurity(jsr250Enabled = true)
@RequiredArgsConstructor @RequiredArgsConstructor
public class SecurityWebConfiguration { 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 JwtAuthenticationFilter jwtAuthenticationFilter;
private final SecurityConfigProperties pathsProperties;
@Bean @Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http http
// 前端段分离不需要---禁用明文验证 // 前端段分离不需要---禁用明文验证
.httpBasic(AbstractHttpConfigurer::disable) // .httpBasic(AbstractHttpConfigurer::disable)
// 前端段分离不需要---禁用默认登录页 // 前端段分离不需要---禁用默认登录页
.formLogin(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable)
// 前端段分离不需要---禁用退出页 // 前端段分离不需要---禁用退出页
@ -45,18 +43,19 @@ public class SecurityWebConfiguration {
.sessionManagement(session -> .sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
) )
// 如果要对部分接口做登录校验 或者 项目中需要使用粗粒度的 校验
.authorizeHttpRequests(authorizeRequests -> .authorizeHttpRequests(authorizeRequests ->
// 访问路径为 /api 时需要进行认证 // 访问路径为 /api 时需要进行认证
authorizeRequests authorizeRequests
// 不认证登录接口 // // 不认证登录接口
.requestMatchers(noAuthPaths.toArray(String[]::new)).permitAll() // .requestMatchers(pathsProperties.noAuthPaths.toArray(String[]::new)).permitAll()
// 只认证 securedPaths 下的所有接口 // // 只认证 securedPaths 下的所有接口
// ======================================================================= // // =======================================================================
// 也可以在这里写多参数传入"/api/**","/admin/**" // // 也可以在这里写多参数传入"/api/**","/admin/**"
// 但是在 Spring过滤器中如果要放行不需要认证请求但是需要认证的接口必需要携带token // // 但是在 Spring过滤器中如果要放行不需要认证请求但是需要认证的接口必需要携带token
// 做法是在这里定义要认证的接口如果要做成动态可以放到数据库 // // 做法是在这里定义要认证的接口如果要做成动态可以放到数据库
// ======================================================================= // // =======================================================================
// .requestMatchers(securedPaths.toArray(String[]::new)).authenticated() // .requestMatchers(pathsProperties.securedPaths.toArray(String[]::new)).authenticated()
// 其余请求都放行 // 其余请求都放行
.anyRequest().permitAll() .anyRequest().permitAll()
) )
@ -66,7 +65,7 @@ public class SecurityWebConfiguration {
// 没有权限访问 // 没有权限访问
exception.accessDeniedHandler(new SecurityAccessDeniedHandler()); exception.accessDeniedHandler(new SecurityAccessDeniedHandler());
}) })
.addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
; ;
return http.build(); return http.build();

View File

@ -1,5 +1,11 @@
package com.spring.step3.security.event; 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 lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.event.EventListener; 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.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component; 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.lang.reflect.Method;
import java.util.Arrays;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor
public class AuthenticationEvents { public class AuthenticationEvents {
private final AuthLogService authLogService;
/** /**
* 监听拒绝授权内容 * 监听拒绝授权内容
* *
@ -24,29 +34,48 @@ public class AuthenticationEvents {
@EventListener @EventListener
public void onFailure(AuthorizationDeniedEvent<MethodInvocation> failure) { public void onFailure(AuthorizationDeniedEvent<MethodInvocation> failure) {
try { try {
// getSource getObject意思一样一种是传入泛型自动转换一种是要手动转换 // 当前执行的方法
Object source = failure.getSource();
// 直接获取泛型对象
MethodInvocation methodInvocation = failure.getObject(); MethodInvocation methodInvocation = failure.getObject();
// 方法名称
Method method = methodInvocation.getMethod(); Method method = methodInvocation.getMethod();
// 方法参数
Object[] args = methodInvocation.getArguments(); Object[] args = methodInvocation.getArguments();
log.warn("方法调用被拒绝: {}.{}, 参数: {}", // 用户身份
method.getDeclaringClass().getSimpleName(),
method.getName(),
Arrays.toString(args));
// 这里面的信息和接口 /api/security/current-user 内容一样
Authentication authentication = failure.getAuthentication().get(); Authentication authentication = failure.getAuthentication().get();
// 用户名
String username = authentication.getName();
// 决策结果
AuthorizationDecision decision = failure.getAuthorizationDecision();
AuthorizationDecision authorizationDecision = failure.getAuthorizationDecision(); // 获取请求上下文信息
// ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasAuthority('ADMIN')] ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
System.out.println(authorizationDecision);
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) { } catch (Exception e) {
log.info(e.getMessage()); log.error("记录授权失败日志异常", e);
} }
} }

View File

@ -4,8 +4,8 @@ import com.spring.step3.config.context.BaseContext;
import com.spring.step3.domain.vo.result.ResultCodeEnum; import com.spring.step3.domain.vo.result.ResultCodeEnum;
import com.spring.step3.exception.AuthenticSecurityException; import com.spring.step3.exception.AuthenticSecurityException;
import com.spring.step3.exception.MyAuthenticationException; 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.handler.SecurityAuthenticationEntryPoint;
import com.spring.step3.security.properties.SecurityConfigProperties;
import com.spring.step3.security.service.DbUserDetailService; import com.spring.step3.security.service.DbUserDetailService;
import com.spring.step3.security.service.JwtTokenService; import com.spring.step3.security.service.JwtTokenService;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
@ -35,6 +35,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenService jwtTokenService; private final JwtTokenService jwtTokenService;
private final DbUserDetailService userDetailsService; private final DbUserDetailService userDetailsService;
private final SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint; private final SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint;
private final SecurityConfigProperties pathsProperties;
@Override @Override
protected void doFilterInternal(@NotNull HttpServletRequest request, protected void doFilterInternal(@NotNull HttpServletRequest request,
@ -54,8 +55,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
return; return;
} }
// 验证Token // 验证 Token
validateAndSetAuthentication(request, response, filterChain); if (validToken(request, response, filterChain)) {
filterChain.doFilter(request, response);
return;
}
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} catch (AuthenticSecurityException e) { } catch (AuthenticSecurityException e) {
@ -74,36 +78,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
} }
} }
/** private boolean validToken(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws IOException, ServletException {
* 是否是不用验证的路径 // 验证Token
*/
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 {
String authHeader = request.getHeader("Authorization"); String authHeader = request.getHeader("Authorization");
// Token验证 // Token验证
if (authHeader == null || !authHeader.startsWith("Bearer ")) { if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response); return true;
return;
// throw new AuthenticSecurityException(ResultCodeEnum.LOGIN_AUTH); // throw new AuthenticSecurityException(ResultCodeEnum.LOGIN_AUTH);
} }
@ -129,5 +110,26 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
BaseContext.setUsername(username); BaseContext.setUsername(username);
BaseContext.setUserId(userId); 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);
} }
} }

View File

@ -20,7 +20,7 @@ public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
log.error("SecurityAccessDeniedHandler:{}", accessDeniedException.getLocalizedMessage()); 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); ResponseUtil.out(response, result);
} }
} }

View File

@ -1,6 +1,8 @@
package com.spring.step3.security.manger; package com.spring.step3.security.manger;
import com.spring.step3.domain.vo.result.Result; 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.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
@ -18,40 +20,11 @@ import java.util.function.Supplier;
* 这是Spring Security较新的"后置授权"功能 * 这是Spring Security较新的"后置授权"功能
*/ */
@Component @Component
@RequiredArgsConstructor
public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> { public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
/** private final SecurityConfigProperties securityConfigProperties;
* 这里两个实现方法按照Security官方要求进行实现
* <h4>类说明</h4>
* 下面的实现是对方法执行前进行权限校验的判断
* <pre>
* <code>AuthorizationManager &ltMethodInvocation></code>
* </pre>
* 下面的这个是对方法执行后对权限的判断
* <pre>
* <code>AuthorizationManager &ltMethodInvocationResult></code>
* </pre>
*
* <h4>注意事项</h4>
* 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的
* <pre>
* &#064;Configuration
* &#064;EnableMethodSecurity(prePostEnabled = false)
* class MethodSecurityConfig {
* &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor preAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
* }
*
* &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor postAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
* }
* }
* </pre>
*/
@Override @Override
public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocationResult methodInvocationResult) { public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocationResult methodInvocationResult) {
Authentication authentication = authenticationSupplier.get(); Authentication authentication = authenticationSupplier.get();
@ -67,17 +40,19 @@ public class PostAuthorizationManager implements AuthorizationManager<MethodInvo
} }
private boolean hasPermission(Authentication authentication, MethodInvocationResult methodInvocationResult) { private boolean hasPermission(Authentication authentication, MethodInvocationResult methodInvocationResult) {
// 1. 获取当前校验方法的返回值 // 获取当前校验方法的返回值
if (methodInvocationResult.getResult() instanceof Result<?> result) { if (methodInvocationResult.getResult() instanceof Result<?> result) {
// 拿到当前返回值中权限内容 // 拿到当前返回值中权限内容
List<String> auths = result.getAuths(); List<String> auths = result.getAuths();
// 允许全局访问的 角色或权限
List<String> adminAuthorities = securityConfigProperties.adminAuthorities;
// 判断返回值中返回方法全新啊是否和用户权限匹配 // 判断返回值中返回方法全新啊是否和用户权限匹配
return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority) return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority)
.anyMatch(auth -> .anyMatch(auth ->
// 这里是忽略了大小写匹配的 admin 权限如果包含 admin 无论大小写都可以放行 // 允许放行的角色或权限 匹配到的角色或权限
auth.equalsIgnoreCase("admin") adminAuthorities.contains(auth) || auths.contains(auth)
|| auths.contains(auth)
); );
} }

View File

@ -1,14 +1,21 @@
package com.spring.step3.security.manger; package com.spring.step3.security.manger;
import com.spring.step3.security.properties.SecurityConfigProperties;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInvocation; 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.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component; 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.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* 处理方法调用前的授权检查 * 处理方法调用前的授权检查
@ -17,40 +24,11 @@ import java.util.function.Supplier;
* 这是传统的"前置授权"模式 * 这是传统的"前置授权"模式
*/ */
@Component @Component
@RequiredArgsConstructor
public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> { public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
/** private final SecurityConfigProperties securityConfigProperties;
* 这里两个实现方法按照Security官方要求进行实现
* <h4>类说明</h4>
* 下面的实现是对方法执行前进行权限校验的判断
* <pre>
* <code>AuthorizationManager &ltMethodInvocation></code>
* </pre>
* 下面的这个是对方法执行后对权限的判断
* <pre>
* <code>AuthorizationManager &ltMethodInvocationResult></code>
* </pre>
*
* <h4>注意事项</h4>
* 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的
* <pre>
* &#064;Configuration
* &#064;EnableMethodSecurity(prePostEnabled = false)
* class MethodSecurityConfig {
* &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor preAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
* }
*
* &#064;Bean
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
* Advisor postAuthorize(MyAuthorizationManager manager) {
* return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
* }
* }
* </pre>
*/
@Override @Override
public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocation methodInvocation) { public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, MethodInvocation methodInvocation) {
Authentication authentication = authenticationSupplier.get(); Authentication authentication = authenticationSupplier.get();
@ -66,18 +44,63 @@ public class PreAuthorizationManager implements AuthorizationManager<MethodInvoc
} }
private boolean hasPermission(Authentication authentication, MethodInvocation methodInvocation) { private boolean hasPermission(Authentication authentication, MethodInvocation methodInvocation) {
// 1. 获取方法上的权限注解如果有 PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), PreAuthorize.class);
// 例如@PreAuthorize("hasRole('ADMIN')") 或其他自定义注解 if (preAuthorize == null) {
return true; // 没有注解默认放行
}
// 2. 获取用户权限 String expression = preAuthorize.value();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); // 解析表达式中的权限要求
List<String> requiredAuthorities = extractAuthoritiesFromExpression(expression);
// 3. 实现你的权限逻辑 // 获取配置的admin权限
// 这里简单示例检查方法名是否包含在权限中 List<String> adminAuthorities = securityConfigProperties.getAdminAuthorities();
String methodName = methodInvocation.getMethod().getName();
return authorities.stream() return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority) .map(GrantedAuthority::getAuthority)
// 这里是忽略了大小写匹配的 admin 权限如果包含 admin 无论大小写都可以放行 .anyMatch(auth ->
.anyMatch(auth -> auth.equalsIgnoreCase("admin") || auth.equals(methodName)); 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;
} }
} }

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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"

View File

@ -6,6 +6,8 @@ spring:
name: spring-security name: spring-security
profiles: profiles:
active: dev active: dev
include:
- security
devtools: devtools:
livereload: livereload:
port: 0 port: 0
@ -54,4 +56,4 @@ jwtToken:
# 主题 # 主题
subject: SecurityBunny subject: SecurityBunny
# 过期事件 7天 # 过期事件 7天
expired: 604800 expired: 604800

View File

@ -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>