在前面的博文中权限地址我是在代码中写死的,在实际开发中肯定不行。这篇博文实现动态配置url权限。
脚本
CREATE TABLE `menu` (
`id` int(11) NOT NULL,
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `menu` VALUES (1, '/admin/**');
INSERT INTO `menu` VALUES (2, '/dba/**');
INSERT INTO `menu` VALUES (3, '/user/**');
CREATE TABLE `menu_role` (
`id` int(11) NOT NULL,
`mid` int(11) DEFAULT NULL COMMENT '菜单id',
`rid` int(11) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `menu_role` VALUES (1, 1, 1);
INSERT INTO `menu_role` VALUES (2, 2, 1);
INSERT INTO `menu_role` VALUES (3, 1, 2);
INSERT INTO `menu_role` VALUES (4, 3, 3);
新增两张表,一张是菜单表,还有一张是菜单权限表,其他表请看我上一篇博文https://www.jianshu.com/p/ec5556e05cf3
实体:
Menu
import java.util.List;
public class Menu {
private Integer id;
private String path;
private List<Role> roles; //该菜单允许什么角色访问
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
MenuService
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gongj.secuitydb.entity.Menu;
import com.gongj.secuitydb.mapper.MenuMapper;
@Service
public class MenuService {
@Autowired
MenuMapper menuMapper;
public List<Menu> getAllMenu(){
return menuMapper.getAllMenu();
}
}
MenuMapper
import java.util.List;
import com.gongj.secuitydb.entity.Menu;
public interface MenuMapper {
List<Menu> getAllMenu();
}
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.gongj.secuitydb.mapper.MenuMapper">
<resultMap type="com.gongj.secuitydb.entity.Menu" id="BaseResultMap">
<id column="id" property="id"></id>
<result column="path" property="path"/>
<collection property="roles" ofType="com.gongj.secuitydb.entity.Role">
<id column="id" property="id"/>
<result column="rnameEn" property="nameEN"/>
<result column="rnameZh" property="nameZh"/>
</collection>
</resultMap>
<select id="getAllMenu" resultMap="BaseResultMap">
select m.*,r.id as rid,r.nameZh as rnameZh,r.nameEn as rnameEn from menu m left join menu_role mr
on m.id = mr.mid left join role r on r.id = mr .rid
</select>
</mapper>
自定义FilterInvocationSecurityMetadataSource
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import com.gongj.secuitydb.entity.Menu;
import com.gongj.secuitydb.entity.Role;
import com.gongj.secuitydb.service.MenuService;
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
AntPathMatcher pathMatcher = new AntPathMatcher();
@Autowired
MenuService menuService;
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation)o).getRequestUrl(); //用户请求地址
List<Menu> allMenu = menuService.getAllMenu(); //所有菜单
for (Menu menu : allMenu) {
//进行匹配,如果匹配成功,就返回该请求需要的角色
if(pathMatcher.match(menu.getPath(), requestUrl)) {
List<Role> roles = menu.getRoles();
String[] rolesStr = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
rolesStr[i] = roles.get(i).getNameEN();
}
return SecurityConfig.createList(rolesStr);
}
}
//如果没有匹配成功,就说明该请求不需要拥有角色就能访问,给它一个标识符。
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
自定义AccessDecisionManager
import java.util.Collection;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager{
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
if("ROLE_LOGIN".equals(configAttribute.getAttribute())) { //拥有标识符的接口需要进行登录才能访问
if(authentication instanceof AnonymousAuthenticationToken) { //说明你是匿名用户,没登录
throw new AccessDeniedException("请登录");
}else {
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authoritie : authorities) {
if(authoritie.getAuthority().equals(configAttribute.getAttribute())) {
return;
}
}
}
throw new AccessDeniedException("非法请求");
}
@Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
}
SecurityConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import com.gongj.secuitydb.service.UserSerivice;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserSerivice userService;
@Autowired
CustomAccessDecisionManager customAccessDecisionManager;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Bean
PasswordEncoder PasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(customAccessDecisionManager);
o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return o;
}
})
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
创建Controller进行测试
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SecurityController {
@GetMapping("/he")
public String he() {
return "hello Security";
}
@GetMapping("/admin/he")
public String admin() {
return "hello admin";
}
@GetMapping("/dba/he")
public String dba() {
return "hello db";
}
@GetMapping("/user/he")
public String user() {
return "hello user";
}
}
访问http://localhost:8081/he来到登录页,使用root用户登录
根据我们创建的表,可以得出root用户的角色是ROLE_dba和ROLE_admin,该角色拥有/admin/和/dba/菜单的访问权限,不能访问/user/**菜单。
访问http://localhost:8081/admin/he成功,可以进行访问。
image.png
访问http://localhost:8081/user/he访问user菜单下的接口是不能进行访问的, image.png
网友评论