diff --git a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java index 3520cb5..6c9c677 100644 --- a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java +++ b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java @@ -1,19 +1,20 @@ package asia.yulinling.workflow.config; -import asia.yulinling.workflow.security.JwtAccessDeniedHandler; -import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint; -import asia.yulinling.workflow.security.JwtAuthenticationFilter; -import asia.yulinling.workflow.security.JwtUserDetailsService; +import asia.yulinling.workflow.security.*; import asia.yulinling.workflow.utils.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 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.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @@ -26,6 +27,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic public class SecurityConfig { private final JwtUserDetailsService jwtUserDetailsService; + private final JwtRbacAuthenticationService jwtRbacAuthenticationService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, @@ -46,16 +48,21 @@ public class SecurityConfig { ) // 认证请求 .authorizeHttpRequests(auth -> auth - .requestMatchers("/login").permitAll() + .requestMatchers("/login").permitAll() // .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过滤器 .addFilterBefore( new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService), UsernamePasswordAuthenticationFilter.class - ) - .userDetailsService(jwtUserDetailsService); + ); return http.build(); } diff --git a/src/main/java/asia/yulinling/workflow/mapper/PermissionMapper.java b/src/main/java/asia/yulinling/workflow/mapper/PermissionMapper.java index d5ac382..41ba2b7 100644 --- a/src/main/java/asia/yulinling/workflow/mapper/PermissionMapper.java +++ b/src/main/java/asia/yulinling/workflow/mapper/PermissionMapper.java @@ -19,5 +19,11 @@ import java.util.List; @Mapper @Component public interface PermissionMapper extends BaseMapper { + /** + * 查找当前角色列表所有的权限 + * + * @param roleIds 角色列表id + * @return 权限列表 + */ List selectPermissionsByRoleId(@Param("roleIds") List roleIds); } diff --git a/src/main/java/asia/yulinling/workflow/model/entity/Permission.java b/src/main/java/asia/yulinling/workflow/model/entity/Permission.java index e0dc556..3904536 100644 --- a/src/main/java/asia/yulinling/workflow/model/entity/Permission.java +++ b/src/main/java/asia/yulinling/workflow/model/entity/Permission.java @@ -48,6 +48,11 @@ public class Permission { */ private String permission; + /** + * 方法 + */ + private String method; + /** * 排序 */ diff --git a/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java b/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java new file mode 100644 index 0000000..5c9ebea --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java @@ -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; + +/** + *

+ * 自定义权限服务类 + *

+ * + * @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 roles = roleMapper.selectRoleByUserId(userId); + List roleIds = roles.stream().map(Role::getId).toList(); + List permissions = permissionMapper.selectPermissionsByRoleId(roleIds); + + List 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> 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> + * { + * "/user/{id}": "GET", + * "/user/": "POST" + * } + */ + public Map> getAllUrlMapping() { + Map> urlMapping = new HashMap<>(); + + Map handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); + + handlerMethods.forEach((mapping, handlerMethod) -> { + PathPatternsRequestCondition pathPatternsCondition = mapping.getPathPatternsCondition(); + if (pathPatternsCondition == null) return; + Set urlTemplates = pathPatternsCondition.getPatterns(); + RequestMethodsRequestCondition methodsCondition = mapping.getMethodsCondition(); + List httpMethods = methodsCondition.getMethods().stream() + .map(Enum::toString) + .collect(Collectors.toList()); + urlTemplates.forEach(url -> { + urlMapping.put(url.toString(), httpMethods); + }); + }); + + log.info("urlMapping :{}", urlMapping); + return urlMapping; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8a3c9a8..00f6261 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ # 服务端配置 server.port=8080 -server.servlet.context-path=/demo +#server.servlet.context-path=/demo # 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.username=root diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql index 7487ad8..9ecdf7f 100644 --- a/src/main/resources/db/data.sql +++ b/src/main/resources/db/data.sql @@ -1,6 +1,6 @@ BEGIN; 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` VALUES (1072806379313565696, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072806379288399872); INSERT INTO `wk_permission` @@ -15,6 +15,10 @@ INSERT INTO `wk_permission` VALUES (1072806379384868864, '在线用户页面-踢出', '/**/api/monitor/online/user/kickout', 2, 'btn:monitor:online:kickout', 'DELETE', 2, 1072806379342925824); +INSERT INTO `wk_permission` +VALUES (1072806379384868865, '用户列表', '/users', 1, + 'page:test', + 'GET', 1, 0); COMMIT; BEGIN; @@ -41,6 +45,8 @@ INSERT INTO `wk_role_permission` VALUES (1072806379238068224, 1072806379288399872); INSERT INTO `wk_role_permission` VALUES (1072806379238068224, 1072806379313565696); +INSERT INTO `wk_role_permission` +VALUES (1072806379208708096, 1072806379384868865); COMMIT; BEGIN;