压下去 2022-10-14 15:42 采纳率: 77.8%
浏览 66
已结题

spring-security动态权限问题

Security配置动态权限未生效


SQL表结构

img


当前使用ROLE_admin登录,却能够访问到"/employee/advanced/hello"

img

问题代码

HelloController

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

    @GetMapping("/employee/basic/hello")
    public String hello2(){
        return "/employee/basic/hello";
    }

    @GetMapping("/employee/advanced/hello")
    public String hello3(){
        return "/employee/advanced/hello";
    }
}

MenuService

public interface IMenuService extends IService<Menu> {
    //根据用户id查询菜单列表
    List<Menu> getMenusByAdminId();

    //根据角色获取菜单列表
    List<Menu> getMenusWithRole();
}

MenuServiceImpl

@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {
    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //根据用户id查询菜单列表
    @Override
    public List<Menu> getMenusByAdminId() {
        Integer adminId = ((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
        ValueOperations<String,Object> valueOperations = redisTemplate.opsForValue();
        //redis中获取菜单数据
        List<Menu> menus = (List<Menu>) valueOperations.get("menu_" + adminId);
        //如果为空,去数据库获取
        if(CollectionUtils.isEmpty(menus)){
            menus = menuMapper.getMenusByAdminId(adminId);
            //将数据设置到redis中
            valueOperations.set("menu_"+adminId,menus);
        }
        return menus;
    }

    @Override
    public List<Menu> getMenusWithRole() {
        return menuMapper.getMenusWithRole();
    }
}

MenuMapper

public interface MenuMapper extends BaseMapper<Menu> {
    //根据用户id查询菜单列表
    List<Menu> getMenusByAdminId(Integer id);

    //根据角色获取菜单列表
    List<Menu> getMenusWithRole();
}

MenuMapper.xml

<?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.example.server.mapper.MenuMapper">
    <resultMap id="BaseResultMap" type="com.example.server.pojo.Menu">
        <id column="id" property="id"/>
        <result column="url" property="url"/>
        <result column="path" property="path"/>
        <result column="component" property="component"/>
        <result column="name" property="name"/>
        <result column="enabled" property="enabled"/>
        <result column="keepAlive" property="keepAlive"/>
        <result column="requireAuth" property="requireAuth"/>
        <result column="parentId" property="parentId"/>
        <result column="enabled" property="enabled"/>
    </resultMap>

    <resultMap id="Menus" type="com.example.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.example.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <resultMap id="MenusWithRole" type="com.example.server.pojo.Menu" extends="BaseResultMap">
        <collection property="roles" ofType="com.example.server.pojo.Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
            <result column="rnameZh" property="nameZh"/>
        </collection>
    </resultMap>

    
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT
            m1.*,
            m2.id AS id2,
            m2.url AS url2,
            m2.path AS path2,
            m2.component AS component2,
            m2.`name` AS name2,
            m2.iconCls AS iconCls2,
            m2.keepAlive AS keepAlive2,
            m2.requireAuth AS requireAuth2,
            m2.parentId AS parentId2,
            m2.enabled AS enabled2
        FROM
            t_menu m1,
            t_menu m2,
            t_admin_role ar,
            t_menu_role mr
        WHERE
            m1.id = m2.parentId
          AND mr.rid = ar.rid
          AND ar.adminId = #{id}
          AND m2.enabled = TRUE
    </select>
    
    <select id="getMenusWithRole" resultMap="MenusWithRole">
        SELECT
            m.*,
            r.id AS rid,
            r.`name` AS rname,
            r.nameZh AS rnameZH
        FROM
            t_menu m,
            t_menu_role mr,
            t_role r
        WHERE
            m.id = mr.mid
          AND mr.rid = r.id
        ORDER BY
            m.id
    </select>
</mapper>

CustomFilter

//判断请求Url,分析所需要的角色
@Component
//FilterInvocationSecurityMetadataSource(过滤器调用安全数据源):分析请求需要的角色,并将需要的角色放在Collection中
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
    @Autowired
    private IMenuService menuService;
    //正则匹配工具
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //获取请求url
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        List<Menu> menus = menuService.getMenusWithRole();
        for(Menu menu:menus){
            //判断请求url与菜单角色是否匹配
            if(antPathMatcher.match(menu.getUrl(),requestUrl)){
                String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                return SecurityConfig.createList(str);
            }
        }
        //没匹配的url默认登录即可访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }
}

CustomUrlDecisionManager

@Component
//权限控制,判断用户角色
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            //当前url所需要的角色
            String needRole = configAttribute.getAttribute();
            //判断当前角色是否登录即可访问的角色,此角色在CustomFilter中设置
            if("ROLE_LOGIN".equals(needRole)){
                //判断是否登录
                if(authentication instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("尚未登录,请登录!");
                }else{
                    return;
                }
            }
            //判断角色是否为url所需角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if(authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }
}

SpringSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)    // 启用方法级别的权限认证
public class SpringSecurityConfig {

    @Autowired
    @Lazy
    private IAdminService adminService;

    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    @Autowired
    private CustomFilter customFilter;

    @Autowired
    private CustomUrlDecisionManager customUrlDecisionManager;

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationFilter(){
        return new JwtAuthenticationTokenFilter();
    }

    @Bean
    public UserDetailsService userDetailsService(){
        return username -> {
            Admin admin = adminService.getAdminByUserName(username);
            if(admin!=null){
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }
            throw new UsernameNotFoundException("用户名或密码不正确");
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                //基于token,不需要csrf
                .csrf().disable()
                //基于token,不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests((authz) -> authz
                        //放开认证
                        .antMatchers("/login","/logout")
                        .permitAll()
                        .antMatchers("/doc.html","/doc.html/**","/webjars/**","/v2/**","/swagger-resources","/swagger-resources/**","/swagger-ui.html","/swagger-ui.html/**","/kaptcha")
                        .permitAll()
                        //任何请求都需认证
                        .anyRequest().authenticated()
                        //动态权限配置
                        .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                            @Override
                            public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                                object.setAccessDecisionManager(customUrlDecisionManager);
                                object.setSecurityMetadataSource(customFilter);
                                return object;
                            }
                        })
                )
                .headers()
                .cacheControl();
                // 添加 JWT 过滤器,JWT 过滤器在用户名密码认证过滤器之前
                http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
                //添加自定义未授权和未登录返回结果
                http.exceptionHandling()
                        .accessDeniedHandler(restfulAccessDeniedHandler)
                        .authenticationEntryPoint(restAuthorizationEntryPoint);

                //自定义登录逻辑
                http.userDetailsService(userDetailsService());

        return http.build();
    }
}
  • 写回答

2条回答 默认 最新

  • a1767028198 2022-10-14 16:13
    关注

    代码搞个压缩包丢网盘,给你看看

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 10月16日
  • 已采纳回答 10月16日
  • 创建了问题 10月14日

悬赏问题

  • ¥15 永磁直线电机的电流环pi调不出来
  • ¥15 用stata实现聚类的代码
  • ¥15 请问paddlehub能支持移动端开发吗?在Android studio上该如何部署?
  • ¥20 docker里部署springboot项目,访问不到扬声器
  • ¥15 netty整合springboot之后自动重连失效
  • ¥15 悬赏!微信开发者工具报错,求帮改
  • ¥20 wireshark抓不到vlan
  • ¥20 关于#stm32#的问题:需要指导自动酸碱滴定仪的原理图程序代码及仿真
  • ¥20 设计一款异域新娘的视频相亲软件需要哪些技术支持
  • ¥15 stata安慰剂检验作图但是真实值不出现在图上