Security配置动态权限未生效
SQL表结构
当前使用
ROLE_admin
登录,却能够访问到"/employee/advanced/hello"问题代码
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();
}
}