- 添加动态权限校验
This commit is contained in:
yulinling 2025-06-16 22:50:12 +08:00
parent 8935c9f0c5
commit 9239ad51a6
6 changed files with 159 additions and 10 deletions

View File

@ -1,19 +1,20 @@
package asia.yulinling.workflow.config; package asia.yulinling.workflow.config;
import asia.yulinling.workflow.security.JwtAccessDeniedHandler; import asia.yulinling.workflow.security.*;
import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint;
import asia.yulinling.workflow.security.JwtAuthenticationFilter;
import asia.yulinling.workflow.security.JwtUserDetailsService;
import asia.yulinling.workflow.utils.JwtUtil; import asia.yulinling.workflow.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
@ -26,6 +27,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
public class SecurityConfig { public class SecurityConfig {
private final JwtUserDetailsService jwtUserDetailsService; private final JwtUserDetailsService jwtUserDetailsService;
private final JwtRbacAuthenticationService jwtRbacAuthenticationService;
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil,
@ -46,16 +48,21 @@ public class SecurityConfig {
) )
// 认证请求 // 认证请求
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/login").permitAll() .requestMatchers("/login").permitAll()
// .requestMatchers("/users", "/users/**").hasRole("ADMIN") // .requestMatchers("/users", "/users/**").hasRole("ADMIN")
.anyRequest().authenticated() .anyRequest().access((authenticationSupplier, requestAuthorizationContext) -> {
HttpServletRequest request = requestAuthorizationContext.getRequest();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new AuthorizationDecision(
jwtRbacAuthenticationService.hasPermission(request, authentication)
);
})
) )
// JWT过滤器 // JWT过滤器
.addFilterBefore( .addFilterBefore(
new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService), new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService),
UsernamePasswordAuthenticationFilter.class UsernamePasswordAuthenticationFilter.class
) );
.userDetailsService(jwtUserDetailsService);
return http.build(); return http.build();
} }

View File

@ -19,5 +19,11 @@ import java.util.List;
@Mapper @Mapper
@Component @Component
public interface PermissionMapper extends BaseMapper<Permission> { public interface PermissionMapper extends BaseMapper<Permission> {
/**
* 查找当前角色列表所有的权限
*
* @param roleIds 角色列表id
* @return 权限列表
*/
List<Permission> selectPermissionsByRoleId(@Param("roleIds") List<Long> roleIds); List<Permission> selectPermissionsByRoleId(@Param("roleIds") List<Long> roleIds);
} }

View File

@ -48,6 +48,11 @@ public class Permission {
*/ */
private String permission; private String permission;
/**
* 方法
*/
private String method;
/** /**
* 排序 * 排序
*/ */

View File

@ -0,0 +1,125 @@
package asia.yulinling.workflow.security;
import asia.yulinling.workflow.mapper.PermissionMapper;
import asia.yulinling.workflow.mapper.RoleMapper;
import asia.yulinling.workflow.model.entity.Permission;
import asia.yulinling.workflow.model.entity.Role;
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;
import java.util.*;
import java.util.stream.Collectors;
/**
* <p>
* 自定义权限服务类
* </p>
*
* @author YLL
* @since 2025/6/16
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class JwtRbacAuthenticationService {
private final PermissionMapper permissionMapper;
private final RequestMappingHandlerMapping requestMappingHandlerMapping;
private final RoleMapper roleMapper;
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
checkRequest(request);
Object userInfo = authentication.getPrincipal();
boolean hasPermission = false;
if (userInfo instanceof UserDetails) {
UserPrincipal userPrincipal = (UserPrincipal) userInfo;
Long userId = userPrincipal.getId();
List<Role> roles = roleMapper.selectRoleByUserId(userId);
List<Long> roleIds = roles.stream().map(Role::getId).toList();
List<Permission> permissions = permissionMapper.selectPermissionsByRoleId(roleIds);
List<Permission> pagePerms = permissions.stream()
.filter(permission -> Objects.equals(permission.getType(), 1))
.filter(permission -> !StringUtils.isEmpty(permission.getUrl()))
.filter(permission -> !StringUtils.isEmpty(permission.getMethod()))
.toList();
for (Permission permission : pagePerms) {
AntPathMatcher antPathMatcher = new AntPathMatcher();
if (antPathMatcher.match(permission.getUrl(), request.getRequestURI())) {
hasPermission = true;
break;
}
}
return hasPermission;
} else {
return false;
}
}
private void checkRequest(HttpServletRequest request) {
String method = request.getMethod();
Map<String, List<String>> urlMapping = getAllUrlMapping();
log.info("方法" + method + "url" + urlMapping.toString());
for (String url : urlMapping.keySet()) {
AntPathMatcher antPathMatcher = new AntPathMatcher();
if (antPathMatcher.match(urlMapping.get(url).toString(), method)) {
if (!antPathMatcher.match(url, method)) {
throw new SecurityException("请求不支持");
}
}
return;
}
throw new SecurityException("请求不存在");
}
/**
* 获取所有url对应的方法
*
* @return Map<String, List < String>>
* {
* "/user/{id}": "GET",
* "/user/": "POST"
* }
*/
public Map<String, List<String>> getAllUrlMapping() {
Map<String, List<String>> urlMapping = new HashMap<>();
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
handlerMethods.forEach((mapping, handlerMethod) -> {
PathPatternsRequestCondition pathPatternsCondition = mapping.getPathPatternsCondition();
if (pathPatternsCondition == null) return;
Set<PathPattern> urlTemplates = pathPatternsCondition.getPatterns();
RequestMethodsRequestCondition methodsCondition = mapping.getMethodsCondition();
List<String> httpMethods = methodsCondition.getMethods().stream()
.map(Enum::toString)
.collect(Collectors.toList());
urlTemplates.forEach(url -> {
urlMapping.put(url.toString(), httpMethods);
});
});
log.info("urlMapping :{}", urlMapping);
return urlMapping;
}
}

View File

@ -1,6 +1,6 @@
# 服务端配置 # 服务端配置
server.port=8080 server.port=8080
server.servlet.context-path=/demo #server.servlet.context-path=/demo
# mysql配置 # mysql配置
spring.datasource.url=jdbc:mysql://122.152.201.90:9912/workflow?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 spring.datasource.url=jdbc:mysql://122.152.201.90:9912/workflow?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.username=root spring.datasource.username=root

View File

@ -1,6 +1,6 @@
BEGIN; BEGIN;
INSERT INTO `wk_permission` INSERT INTO `wk_permission`
VALUES (1072806379288399872, '测试页面', '/test', 1, 'page:test', NULL, 1, 0); VALUES (1072806379288399872, '测试页面', '/test', 1, 'page:test', 'GET', 1, 0);
INSERT INTO `wk_permission` INSERT INTO `wk_permission`
VALUES (1072806379313565696, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072806379288399872); VALUES (1072806379313565696, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072806379288399872);
INSERT INTO `wk_permission` INSERT INTO `wk_permission`
@ -15,6 +15,10 @@ INSERT INTO `wk_permission`
VALUES (1072806379384868864, '在线用户页面-踢出', '/**/api/monitor/online/user/kickout', 2, VALUES (1072806379384868864, '在线用户页面-踢出', '/**/api/monitor/online/user/kickout', 2,
'btn:monitor:online:kickout', 'btn:monitor:online:kickout',
'DELETE', 2, 1072806379342925824); 'DELETE', 2, 1072806379342925824);
INSERT INTO `wk_permission`
VALUES (1072806379384868865, '用户列表', '/users', 1,
'page:test',
'GET', 1, 0);
COMMIT; COMMIT;
BEGIN; BEGIN;
@ -41,6 +45,8 @@ INSERT INTO `wk_role_permission`
VALUES (1072806379238068224, 1072806379288399872); VALUES (1072806379238068224, 1072806379288399872);
INSERT INTO `wk_role_permission` INSERT INTO `wk_role_permission`
VALUES (1072806379238068224, 1072806379313565696); VALUES (1072806379238068224, 1072806379313565696);
INSERT INTO `wk_role_permission`
VALUES (1072806379208708096, 1072806379384868865);
COMMIT; COMMIT;
BEGIN; BEGIN;