一.权限管理
1.1 真实权限配置
1.1.1 拦截真实权限
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService permissionService;
/**
* 这个方法就会返回咱们的权限数据(它是有顺序)
*/
public Map<String,String> creatMap(){
//注意:这里的map是有顺序的
Map<String,String> map = new LinkedHashMap<>();
map.put("/login","anon");
//静态资源放行
map.put("/*.css","anon");
map.put("/*.js","anon");
map.put("/easyui/**","anon");
map.put("/css/**","anon");
map.put("/images/**","anon");
map.put("/js/**","anon");
//map.put("/json/**","anon");
//获取所有权限,拦截真实权限
List<Permission> perms = permissionService.findAll();
//遍历所有权限
perms.forEach(p->{
/*
* 配置了自定义权限过滤器的话,必须改key值
* <entry key="aisellPerms" value-ref="aisellPermissionsAuthorizationFilter"/>
* */
map.put(p.getUrl(),"aisellPerms["+p.getSn()+"]");
});
//拦截所有,登录成功后才可访问
map.put("/**","authc");
return map;
}
}
1.1.2 当前用户赋权
PermissionRepository通过用户id获取权限
public interface PermissionRepository extends BaseRepository<Permission,Long>{
/*
* 通过用户获取对应权限
* 返回的是一个Set<String>权限集合
* JPQL关连法则: 1.关连前面的类的别名.属性 2.不需要你消除笛卡尔积
* */
@Query("select p.sn from Employee e join e.roles r join r.permissions p where e.id = ?1")
Set<String> findSnByUser(Long userid);
}
IPermissionService定义findSnByUser方法
public interface IPermissionService extends IBaseService<Permission,Long>{
Set<String> findSnByUser();
}
PermissionServiceImpl重写findSnByUser方法
- 先获取当前用户(主体里面存了)
- 通过当前用户的id查询用户权限信息
@Service
public class PermissionServiceImpl extends BaseServiceImpl<Permission,Long> implements IPermissionService{
@Autowired
private PermissionRepository permissionRepository;
@Override
public Set<String> findSnByUser() {
//获取当前用户主体
Employee loginUser = UserContext.getUser();
return permissionRepository.findSnByUser(loginUser.getId());
}
}
AisellRealm中获取并设置权限
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
/*
* 授权(之后的权限数据获取都在这里面)
* */
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//创建授权对象
SimpleAuthorizationInfo authorizationInfo= new SimpleAuthorizationInfo();
//获取角色并设置角色
/*Set<String> roles = findRoles();
authorizationInfo.setRoles(roles);*/
//获取权限并设置权限(从数据库中查询该用户所有权限)
Set<String> permissions = permissionService.findSnByUser();
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
二.ajax请求权限拦截
2.1 自定义权限
- 创建一个过滤器AisellPermissionsAuthorizationFilter 继承PermissionsAuthorizationFilter
- 重写onAccessDenied方法(只有当你没有权限的时候才会进入这个方法)
/*
* 自定义权限过滤器 继承PermissionsAuthorizationFilter类
* */
public class AisellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
/*
* 只有当你没有权限的时候才会进入这个方法
* */
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//1.转换成Http协议的对象
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
//设置响应头为json
resp.setContentType("application/json;charset=UTF-8");
//2.获取请求头: XMLHttpRequest
String xhr = req.getHeader("X-Requested-With");
if("XMLHttpRequest".equals(xhr)){
//3.代表现在是Ajax请求,我们得做ajax处理
// 返回一个json:{success:false,msg:xxx}
resp.getWriter().print("{\"success\":false,\"msg\":\"你没有权限\"}");
}else {
//普通请求和以前一样处理
//获取没有权限时,访问的路径
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
//有路径就跳转到该路径页面
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
//没有该路径就报401错误
WebUtils.toHttp(response).sendError(401);
}
}
}
return false;
}
}
2.2 权限配置
<!-- shiro真正的权限过滤器 注意:这个id必须和shiro过滤器的名称相同-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
...
<!--告诉Shiro我这里有一个自定义过滤器-->
<property name="filters">
<map>
<entry key="aisellPerms" value-ref="aisellPermissionsAuthorizationFilter"/>
</map>
</property>
</bean>
...
<!--配置自定义的权限过滤器-->
<bean id="aisellPermissionsAuthorizationFilter" class="com.luo.aisell.web.shiro.AisellPermissionsAuthorizationFilter"/>
2.3 权限使用
必须使用先前配置文件中配置的名称aisellPerms,不然找不到
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService permissionService;
/**
* 这个方法就会返回咱们的权限数据(它是有顺序)
*/
public Map<String,String> creatMap(){
//注意:这里的map是有顺序的
Map<String,String> map = new LinkedHashMap<>();
...
//获取所有权限,拦截真实权限
List<Permission> perms = permissionService.findAll();
//遍历所有权限
perms.forEach(p->{
/*
* 配置了自定义权限过滤器的话,必须改key值
* <entry key="aisellPerms" value-ref="aisellPermissionsAuthorizationFilter"/>
* */
map.put(p.getUrl(),"aisellPerms["+p.getSn()+"]");
});
//拦截所有,登录成功后才可访问
map.put("/**","authc");
return map;
}
}
三.shiro标签认识
3.1 引入标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
3.2 拿到主体的标签
<shiro:principal property="username" />
3.3 权限判断的标签
判断是否有employee:delete权限,有权限就显示删除链接
<shiro:hasPermission name="employee:delete">
<a href="#" data-method="delete" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
</shiro:hasPermission>
四.菜单管理(根据权限显示菜单)
3.1 菜单对象
完成菜单的基础CRUD后,配置菜单类关系
- 多对一,通过子菜单拿父菜单
- @Transient(临时属性JPA不会管理它)一对多,不需要JPA管理(因为会拿到所有多方),得我们自己添加数据
- @JsonIgnore双向关连生成json会死循环,需要一方放弃生成
- getText方法 兼容easyui,前台格式name对应text
@Entity
@Table(name = "menu")
public class Menu extends BaseDomain{
private String name;
private String url;
private String icon;
//必须儿子可以找父亲 多对一
@ManyToOne
@JoinColumn(name = "parent_id")
@JsonIgnore//忽略
private Menu parent;
/**
* 不要配置单向一对多(性能不好)
* 你现在敢配置一对多嘛?配置了一对多,关系则由JPA来管理!
* 它会自动从1方去拿到所有多方!!!
* 1.这里是否需要儿子的List字段 -》 一定要的
* 2.这个儿子的List是否要JPA管理 -》 不可以
* 加上这个字段(属性),但是不交给JPA管理【咱们手动管理这个字段】
*/
@Transient//临时,有些字段不需要和数据库有关系,我
private List<Menu> children = new ArrayList<>();
//兼容easyui,显示树菜单
public String getText() {
return name;
}
//getter.setter.toString...
}
3.2 获取父菜单
3.2.1 MenuRepository
- findParentMenus:拿到所有父菜单(url为null的是父菜单)
- findMenusByUser:通过用户拿到菜单,这里为List注意去重
public interface MenuRepository extends BaseRepository<Menu,Long>{
//通过用户拿到菜单 这里为List注意去重
@Query("select distinct m from Employee e join e.roles r join r.permissions p join p.menu m where e.id=?1")
List<Menu> findMenusByUser(Long userId);
/*
* url为null的是父菜单
* */
@Query("select o from Menu o where o.url is null")
List<Menu> findParentMenus();
}
3.2.2 service与controller的支持
IMenuService定义获取所有父菜单的方法
public interface IMenuService extends IBaseService<Menu,Long>{
List<Menu> findParentMenus();
}
MenuServiceImpl重写findParentMenus方法
- 创建一个装父菜单的容器
- 获取当前用户,通过用户获取权限菜单
- 遍历当前用户的菜单
- 通过子菜单获取父菜单,判断父菜单容器里是否已经存在该父菜单
- 没有的话,把它放进去
- 将该子菜单添加到父菜单的children中
@Service
public class MenuServiceImpl extends BaseServiceImpl<Menu,Long> implements IMenuService{
@Autowired
private MenuRepository menuRepository;
@Override
public List<Menu> findParentMenus() {
//创建一个装父菜单的容器
List<Menu> parentMenus = new ArrayList<>();
//获取当前用户
Employee loginUser = UserContext.getUser();
System.out.println(loginUser);
//通过用户获取权限
System.out.println(loginUser.getId());
List<Menu> menus = menuRepository.findMenusByUser(loginUser.getId());
System.out.println(menus);
//遍历当前用户的菜单
menus.forEach(m->{
//通过子菜单找父菜单
Menu parentMenu = m.getParent();
//判断父菜单容器里是否已经存在该父菜单
if(!parentMenus.contains(parentMenu)){
//没有的话,把它放进去
parentMenus.add(parentMenu);
}
//将该子菜单添加到父菜单的children中
parentMenu.getChildren().add(m);
});
return parentMenus;
}
}
controller层
@RequestMapping("/findParentMenus")
@ResponseBody
public List<Menu> findParentMenus(){
return menuService.findParentMenus();
}
3.2.3 主页面进行访问
$("#menu").tree({
method:'get',
url:'/menu/findParentMenus'
...
})
原理分析图: