Explorar o código

add:权限基本写完(后续需要每个中台接口加@PreAuthorize)

lyx hai 10 horas
pai
achega
e428a6f579

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSysRole.java

@@ -32,6 +32,10 @@ public class LifeSysRole implements Serializable {
     @TableField("role_name")
     private String roleName;
 
+    @ApiModelProperty(value = "角色名称")
+    @TableField("role_name_en")
+    private String roleNameEn;
+
     @ApiModelProperty(value = "显示顺序")
     @TableField("role_sort")
     private Integer roleSort;

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysRoleMapper.java

@@ -1,9 +1,15 @@
 package shop.alien.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.LifeSysRole;
 
+import java.util.List;
+
 /**
  * 平台角色信息表 Mapper 接口
  *
@@ -12,5 +18,16 @@ import shop.alien.entity.store.LifeSysRole;
  */
 @Mapper
 public interface LifeSysRoleMapper extends BaseMapper<LifeSysRole> {
+    /**
+     * 根据用户ID查询角色
+     *
+     * @param queryWrapper 查询条件
+     * @return 角色列表
+     */
+    @Select("select lsr.*\n" +
+            "from life_sys_role lsr\n" +
+            "left join life_sys_user_role lsur on lsur.role_id = lsr.role_id\n" +
+            "${ew.customSqlSegment}")
+    List<LifeSysRole> getRoleByUserId(@Param(Constants.WRAPPER) QueryWrapper<LifeSysRole> queryWrapper);
 }
 

+ 4 - 0
alien-store/pom.xml

@@ -288,6 +288,10 @@
             <artifactId>okhttp</artifactId>
             <version>4.12.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 33 - 0
alien-store/src/main/java/shop/alien/store/config/CustomAccessDeniedHandler.java

@@ -0,0 +1,33 @@
+package shop.alien.store.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class CustomAccessDeniedHandler implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
+            throws IOException {
+        response.setStatus(HttpStatus.FORBIDDEN.value());
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.setCharacterEncoding("UTF-8");
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("code", 403);
+        result.put("msg", "权限不足,无法访问该接口");
+        result.put("detail", e.getMessage());
+
+        new ObjectMapper().writeValue(response.getWriter(), result);
+    }
+}

+ 74 - 0
alien-store/src/main/java/shop/alien/store/config/SecurityConfig.java

@@ -0,0 +1,74 @@
+package shop.alien.store.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import shop.alien.store.filter.PreAuthFilter;
+
+import javax.annotation.Resource;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Resource
+    private CustomAccessDeniedHandler customAccessDeniedHandler;
+
+    @Resource
+    private PreAuthFilter preAuthFilter;
+
+    /**
+     * 核心:跳过前端静态资源的Security过滤(不走过滤器链,性能最优)
+     * 适用于前端打包后的静态资源(JS/CSS/图片/字体等)
+     */
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+        web.ignoring()
+                // 前端静态资源路径(根据实际项目调整)
+                .antMatchers("/static/**", "/css/**", "/js/**", "/images/**", "/fonts/**")
+                // 若前端部署在后端,放行打包后的dist目录(Vue/React打包后)
+                .antMatchers("/dist/**", "/public/**")
+                // 放行Swagger(若有)
+                .antMatchers("/swagger-ui/**", "/v3/api-docs/**","/doc.html","/webjars/**");
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+                // 1. 禁用CSRF(前后端分离无Cookie,无需保护)
+                .csrf().disable()
+                // 2. 禁用Session(无状态,仅授权)
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+                .and()
+                // 3. 关闭所有默认认证入口(表单登录、HTTP Basic)
+                .formLogin().disable()
+                .httpBasic().disable()
+                // 4. 禁用注销
+                .logout().disable()
+                // 5. 配置授权规则
+                .authorizeRequests()
+//                // 公开接口:无需权限
+//                .antMatchers("/doc.html").permitAll()
+//                // 管理员接口:需ROLE_ADMIN角色
+//                .antMatchers("/**").hasRole("ADMIN")
+//                // 普通用户接口:需ROLE_USER角色
+//                .antMatchers("/user/**").hasRole("USER")
+                // 其他接口:需任意认证(即请求头带X-User-Roles)
+                .anyRequest().permitAll()
+                .and()
+                // 6. 添加自定义预认证过滤器(核心:解析权限)
+                .addFilterBefore(preAuthFilter, UsernamePasswordAuthenticationFilter.class)
+                // 7. 配置授权异常处理器
+                .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
+
+        // 允许跨域
+        http.cors();
+    }
+
+}

+ 82 - 0
alien-store/src/main/java/shop/alien/store/filter/PreAuthFilter.java

@@ -0,0 +1,82 @@
+package shop.alien.store.filter;
+
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import shop.alien.entity.store.LifeSysMenu;
+import shop.alien.entity.store.LifeSysRole;
+import shop.alien.mapper.LifeSysMenuMapper;
+import shop.alien.store.service.impl.LifeSysRoleServiceImpl;
+import shop.alien.util.common.JwtUtil;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+public class PreAuthFilter extends OncePerRequestFilter {
+    // 自定义权限请求头(前端传递角色/权限)
+    private static final String USER_TOKEN_HEADER = "Authorization";
+    private static final String PLATFORM_TYPE_HEADER = "X-platform-type";
+
+    @Resource
+    private LifeSysMenuMapper lifeSysMenuMapper;
+    @Resource
+    private LifeSysRoleServiceImpl lifeSysRoleServiceImpl;
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        // 1. 从请求头获取角色信息(前端传递,比如:ROLE_ADMIN,ROLE_USER)
+        String token = request.getHeader(USER_TOKEN_HEADER);
+        String platformType = request.getHeader(PLATFORM_TYPE_HEADER);
+
+        /**
+         * 如果不是平台端请求,直接放行,加admin权放行
+         */
+        if(null != platformType || "platform".equalsIgnoreCase(platformType)) {
+            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+                    "notPlatformUser", // 无认证场景,用户名可自定义
+                    null, // 无需密码,置空
+                    AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", "ROLE_ADMIN")));// 权限列表
+            chain.doFilter(request, response);
+            return;
+        }
+
+        if (token == null) {
+            // 无权限头:视为匿名用户(仅能访问公开接口)
+            chain.doFilter(request, response);
+            return;
+        }
+
+        // 2. 解析角色为权限集合(Spring Security 角色需以ROLE_开头)
+        JSONObject tokenInfo = JwtUtil.getTokenInfo(token);
+        String userId = tokenInfo.getString("userId");
+        String userName = tokenInfo.getString("userName");
+        List<LifeSysMenu> menuByUserId = lifeSysMenuMapper.getMenuByUserId(Long.parseLong(userId));
+        List<String> permsList = menuByUserId.stream().map(LifeSysMenu::getPerms).collect(Collectors.toList());
+        List<LifeSysRole> roleByUserId = lifeSysRoleServiceImpl.getRoleByUserId(Long.parseLong(userId));
+        List<String> roleList = roleByUserId.stream().map(x->"ROLE_"+x.getRoleNameEn().toUpperCase()).collect(Collectors.toList());
+        permsList.addAll(roleList);
+        // 3. 构建认证令牌(用户名可固定/自定义,密码置空,核心是权限)
+        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+                userName, // 无认证场景,用户名可自定义
+                null, // 无需密码,置空
+                AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", permsList)));// 权限列表
+        // 4. 设置请求上下文
+        authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+        // 5. 将认证信息存入SecurityContext(标记用户已"认证",仅用于授权)
+        SecurityContextHolder.getContext().setAuthentication(authToken);
+
+        // 继续执行过滤器链
+        chain.doFilter(request, response);
+    }
+}

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysRoleService.java

@@ -73,5 +73,13 @@ public interface LifeSysRoleService extends IService<LifeSysRole> {
      * @return List<LifeSysRole>
      */
     List<LifeSysRole> getAllNormalRoles();
+
+     /**
+      * 根据用户ID查询角色
+      *
+      * @param userId 用户ID
+      * @return List<LifeSysRole> 角色列表
+      */
+    List<LifeSysRole> getRoleByUserId(Long userId);
 }
 

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeSysRoleServiceImpl.java

@@ -1,6 +1,7 @@
 package shop.alien.store.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -131,5 +132,12 @@ public class LifeSysRoleServiceImpl extends ServiceImpl<LifeSysRoleMapper, LifeS
         
         return lifeSysRoleMapper.selectList(queryWrapper);
     }
+
+    @Override
+    public List<LifeSysRole> getRoleByUserId(Long userId) {
+        QueryWrapper<LifeSysRole> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("lsur.user_id", userId);
+        return lifeSysRoleMapper.getRoleByUserId(queryWrapper);
+    }
 }
 

+ 0 - 0
logging.path_IS_UNDEFINED/DEBUG.log


+ 1 - 0
pom.xml

@@ -377,6 +377,7 @@
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-starter-security</artifactId>
+                <version>${spring-boot.version}</version>
             </dependency>
 
             <!--Other End-->