项目阶段二-权限与菜单

权限与菜单

1.真实权限与权限拦截

以Permission为例:

1.1 真实权限

底层(数据库获得真实权限 员工->角色->权限):

持久层:
/*真实权限(思路:拓展底层,从而完成服务层,必要时抽取工具类,Realm中获取)
     * 根据当前用户从数据库获得其对应权限
     * 可知在自定义Realm中添加权限的类型为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 id);
抽取工具类:

获得当前用户

public class UserContext {

    public static Employee getUser(){
        //获得当前用户
        Subject subject = SecurityUtils.getSubject();
        //获得当前登录用户
        //Object principal = subject.getPrincipal();//可知道用户的主体就是底层查询中要使用的员工:直接强转
        Employee loginEmployee = (Employee)subject.getPrincipal();
        return loginEmployee;
    }
}
服务层:

获得当前用户权限

	//根据当前用户查到对应权限(通过当前用户的ID查询当前用户的)
    //细节1. 获得当前用户(登录后shiro会储存一个主体,这个主体是可以在任何地方拿到的对象,我们知道主体恰巧就是要使用的员工)
    //细节2.由于获得当前对象常用所以可以抽取
    @Override
    public Set<String> findSnByLoginUser() {
        Employee loginUser = UserContext.getUser();
        return permissionRepository.findSnByUser(loginUser.getId());
    }
真实权限:

准备获取权限Map的工厂:FilterChainDefinitionMapFactory

/**
 * 获取权限Map的工厂
 */
public class FilterChainDefinitionMapFactory {
    /*权限*/
    @Autowired
    private IPermissionService permissionService;
    /**
     * 这个方法会返回咱们的权限数据(它是有顺序)  anon之后authc
     *
     *   /login = anon
     *   /s/permission.jsp = perms[employee:index]
     *   /department/index = perms[department:index]
     *   /** = authc
     */
    public Map<String,String> createMap(){
        //有顺序,所以必须准备一个有顺序的map  LinkedHashMap
        Map<String,String> map = new LinkedHashMap<>();
        //添加放行数据
        map.put("/login","anon");
        //(就像游客放行,最基本的东西展示)把静态资源(js,css,图片等 一般在开发时,会直接创建文件夹专门放静态资源,管理更方便)放行
        map.put("*.js","anon");
        map.put("*.css","anon");
        map.put("/css/**","anon");
        map.put("/js/**","anon");
        map.put("/easyui/**","anon");
        map.put("/images/**","anon");
        /*真实权限
        * 、添加权限拦截数据,添加了拦截,无权限无法访问
        *
        *       这里就不能被访问的
        * */
        //数据库获取权限 信息
        List<Permission> allPerm = permissionService.findAll();
        //遍历、设置权限与资源 (并使用自定义过滤器aisellPerms)
        allPerm.forEach(p-> {
            /* 默认使用的过滤器描述:
            //perms 可以 它是默认找过滤器:PermissionsAuthorizationFilter(所有的跳转拦截,ctrl+shift+t搜素查看源码)
            map.put(p.getUrl(),"perms["+p.getSn()+"]"); */

            map.put(p.getUrl(),"aisellPerms["+p.getSn()+"]");
            // AuthorizationFilter也可以 但是使用PermissionsAuthorizationFilter来自定义过滤器(东西更多,拓展)
        });
//        map.put("/employee/index","perms[employee:index]");
//        map.put("/department/index","perms[department:index]");
        //拦截所有
        map.put("/**","authc");
        return map;
    }
}
applicationContext-shiro配置文件:

关联Map的工厂

<!--
        shiro真实的权限过滤器(这个名字必须和xml中的shiro过滤器的名字一致,才能知道是同一个)
        拦截请求
     -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 引入了权限管理器 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 如果没有登录,进入此路径 -->
        <property name="loginUrl" value="/login"/>
        <!-- 登录成功,会进入的页面(没什么用,后面会直接跳转) -->
        <property name="successUrl" value="/s/success.jsp"/>
        <!--如果没有权限,会进入的页面-->
        <property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
        <!--过滤器描述
             filterChainDefinitions:过滤器描述(它是有顺序!!!anon在authc(会拦截所有)之前)
                 anon:不登录也可以访问(游客可以访问的页面)
                 authc:必须登录才可以访问
           -->
        <!--
        <property name="filterChainDefinitions">
            <value>
                /login = anon
                /s/permission.jsp = perms[employee:index]
                /department/index = perms[department:index]
                /** = authc
            </value>
        </property>
        -->
        <!--过滤器描述: 使用Map与java关联(ref) ,将过滤器描述移到Java代码中-->
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
        
         <!--将滤器描述(权限数据)移到Java代码中存入java代码中的Map中
        Map map = new FilterChainDefinitionMapFactory().createMap();
    -->
    <!--把工厂bean中的createMap方法返回的Map对象(储存过滤器描述) 也变成一个bean -->
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="createMap" />
    <!--创建工厂bean-->
    <bean id="filterChainDefinitionMapFactory" class="com.hujie.aisell.web.shiro.FilterChainDefinitionMapFactory" />
当前用户真实权限:

自定义realm AuthorizationInfo 授权:

​ 注入IPermissionService

	@Autowired
    private IPermissionService permissionService;

​ 使用真实权限

rotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //创建一个授权对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //模拟 获取角色(后面会根据真实的登录用户去查找对应的角色)
//        Set<String> role = findRole();
        //设置角色
//        authorizationInfo.setRoles(role);//提示可见要传ser<String> 所以注意模拟时的数据
        /*//模拟拿到 与 设置权限 (后面会根据实际情况角色去查找对应的权限)
        Set<String> permission = findPermission();*/
        //真实权限
        Set<String> perms = permissionService.findSnByLoginUser();
    	//提示可见要传set<String> 所以注意模拟时的数据
        authorizationInfo.setStringPermissions(perms);
        return authorizationInfo;
    }
1.2Ajax请求&普通请求&响应

Ajax请求&普通请求区别:

Ajax请求有Ajax原生关键对象:XMLHttpRequest

问题:shiro的Ajax请求&普通请求 响应可以更好

解决:自定义权限过滤器中自定义Ajax请求相应,达到:给出提示(json字符串) 或 根据权限决定是否屏蔽某功能按钮(对应页面引入shiro标签)

方案一:自定义权限过滤器
public class AisellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {

    //没有权限时进入
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
//        System.out.println("没有权限时候进入");

        Subject subject = this.getSubject(request, response);
        if (subject.getPrincipal() == null) {
            this.saveRequestAndRedirectToLogin(request, response);
        } else {
            /*通过浏览器中查看请求(网络中消息头),可见普通请求和Ajax请求的区别  (Ajax原生关键对象:XMLHttpRequest)
            * 首先request没有获得请求头的方法:getHeader(),所以转为Http协议
            * */
            //1.转换成Http协议的对象
            HttpServletRequest req = (HttpServletRequest)request;
            HttpServletResponse resp = (HttpServletResponse)response;
            //设置SpringMVC的响应头为json(前端jQuery才能识别到,才能自动转换)
            resp.setContentType("application/json;charset=UTF-8");
            //2.获取请求头: XMLHttpRequest
            String xhr = req.getHeader("X-Requested-With");
            /* 判断是Ajax请求还是普通请求
            * 对Ajax请求进行处理(返回的Ajax数据能利用SpringMVC、Spring处理Ajax对象的特点,帮我处理,从而给出使用者提示)
            * 这就是自定义此权限过滤器的重点
            **/
            if("XMLHttpRequest".equals(xhr)){
                //3.代表现在是Ajax请求对ajax处理
                // 问题:让此能返回一个标准的json字符串:{success:false,msg:xxx} 以便自动处理,达到:给出提示 (现在就是Ajax出错)
                // 难点:现在返回的是Boolean类型,转换类型返回数据不符合实际  解决:使用最原始的方法:流(拿到流,传出去)
                // 注意:1.转义符使用  2.就直接这样返回还不行:因为响应头,必须设置响应头在前端jQuery才能帮我们转换成json对象(给出提示)
                //                      所以在上面还要设置SpringMVC的相应头
                resp.getWriter().print("{\"success\":false,\"msg\":\"抱歉,没有您权限!\"}");
                // 可见:不管是前端传后端,还是后端传前端都是传的字符串 只是前端(jQuety)后端(SpringMVC)自动转换了
            }else {
                //否则为:普通请求
                //拿到没有权限的页面路径
                String unauthorizedUrl = this.getUnauthorizedUrl();
                if (StringUtils.hasText(unauthorizedUrl)) {
                    //如果有路径,就会跳到没有权限的位置
                    WebUtils.issueRedirect(request, response, unauthorizedUrl);
                } else {
                    //如果没有路径,就会报401的错误
                    WebUtils.toHttp(response).sendError(401);
                }
            }
        }

        return false;
    }
}

applicationContext-shiro配置文件:

<!--自定义过滤器 注意:key使用该过滤器的关键词,一般在过滤器描述中-->
        <property name="filters">
            <map>
                <entry key="aisellPerms" value-ref="myAisellFilter"></entry>
            </map>
        </property>
    </bean>
    <!--配置自定义过滤器-->
    <bean id="myAisellFilter" class="com.hujie.aisell.web.shiro.AisellPermissionsAuthorizationFilter"></bean>
权限Map的工厂
 	//数据库获取权限 信息
        List<Permission> allPerm = permissionService.findAll();
        //遍历、设置权限与资源 (并使用自定义过滤器aisellPerms)
        allPerm.forEach(p-> {
            /* 默认使用的过滤器描述:
            //perms 可以 它是默认找过滤器:PermissionsAuthorizationFilter(所有的跳转拦截,ctrl+shift+t搜素查看源码)
            map.put(p.getUrl(),"perms["+p.getSn()+"]"); */
            //使用自定义过滤器aisellPerms
            map.put(p.getUrl(),"aisellPerms["+p.getSn()+"]");
            // AuthorizationFilter也可以 但是使用PermissionsAuthorizationFilter来自定义过滤器(东西更多,拓展)
        });
方案二:页面按钮权限控制

“删除“权限为例

<%”--引入shiro标签--%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

<%--工具栏
(添加 查询 删除。。) 功能就联系到事件的绑定(js)
现在起:我们对应每一个部分(如员工的、部门的)都单独写js,注意引入当前对应的js;
    大致步骤:1.引入EasyUI的组件(如下toolbar、searchForm)
              2.js文件中实现功能, 并添加 data-method="xxx"
             ctrl+e 打开最近打开的文件
--%>
<div id="toolbar" style="padding:5px;height:auto">
    <div style="margin-bottom:5px">
        <a href="#" data-method="add" class="easyui-linkbutton" iconCls="icon-add" plain="true">添加</a>
        <a href="#" data-method="update" class="easyui-linkbutton" iconCls="icon-edit" plain="true">修改</a>
        
        <%--判断权限。如果有此权限(name="employee:delete"):有才能看到此按钮--%>
        <shiro:hasPermission name="employee:delete">
        <a href="#" data-method="delete" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
        </shiro:hasPermission>
        
</div>

2.菜单

(基础:menu实体类的CRUD,以及前端页面等)

期待效果:

​ 显示菜单结构(按实际用户权限显示):有权限就显示对应菜单,没有就不显示

思路:

​ 运用数据库总真实数据创建主页面菜单部分

分析:

​ 从前面模仿菜单时的数据:json:

[{ "id":1,"text":"基本管理","children":[{"text":"员工管理","url":"/employee/index"},{ "text":"部门管理","url":"/department/index"}]

​ 可以理解拆分为父菜单、子菜单(看成一对多关系)

方案一:

​ 通过父菜单获取子菜单 问题:无法将权限结合起来(同父菜单下所以子菜单都会被查出来,比较麻烦)

方案二:

​ 换角度:配置子菜单和父菜单多对一的关系,通过子菜单来获取父菜单;

​ 观察json,可以先准备装父类的容器,拿到所有子类菜单(children)后 确定父菜单,

​ 再:判断父菜单后执行是否将父菜单放入容器(不判断的话:同一父菜单下 有多个子菜单存在的情况就会出错)

2.1方案二详细:
配置子菜单、父菜单 多对一
 	/**
     * 多对一,子菜单可以找到父菜单
     * JsonIgnore: SpringMVC传json就不会使用这个字段了
     */
    @ManyToOne
    @JoinColumn(name = "parent_id")
    @JsonIgnore
    private Menu parent;

    /**
     * 父亲可以找到儿子
     * 不配一对多,但是这个字段必需要有(有这个值才能出树型结构)
     * 这里面的值就由我们自己手动添加
     * @Transient:临时属性 -> domain里有些字段可能不需要和数据库有关系
     */
    @Transient
    private List<Menu> children = new ArrayList<>();
底层:
public interface MenuRepository extends BaseRepository<Menu,Long>{
    //注意字段对应实体类,特别是get、set方法要提供
    @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);

    //父菜单 判断为空
    @Query("select o from Menu o where o.url is null")
    List<Menu> findParentMenus();
}
controller:
 @RequestMapping("/findParentMenus")
    @ResponseBody
    public List<Menu> findParentMenus() {
        return menuService.findParentMenus();
    }
结构数据的显示(主页面):

​ 创建菜单树部分(更换路径)

 $(function () {
            //创建菜单树
            $('#menuTree').tree({
                /*菜单树数据来源*/
                url:'/menu/findParentMenus'

​ 且:实体类Menu提供getText()方法(用于显示)

//方法不至这一种,添加getText()方法 显得比更改数据库....更简单
public String getText() {
        return name;
    }
重点:service(业务)层:
@Service
public class MenuServiceImpl extends BaseServiceImpl<Menu,Long> implements IMenuService{

    @Autowired
    private MenuRepository menuRepository;

    /*
    * 思路:在底层已经能拿到当前用户对应的子菜单
    * 有json[{ "id":1,"text":"基本管理","children":[{"text":"员工管理","url":"/employee/index"},{ "text":"部门管理","url":"/department/index"}]
    * 当前就拿到"children":的内容,需要拼接外面的部门,即准备装父类菜单的容器(准备空的List)
    *
    * 所以第一步:先准备装父类的容器     第二步;循环菜单(拿到子菜单)       第三步:通过子菜单拿到父菜单
    *       第四步:判断父菜单后执行是否将父菜单放入容器(不判断的话:同一父菜单下 有多个子菜单存在的情况就会出错)
    *       所以:在:不管; 不在:父菜单放入容器  之后将子菜单放入父菜单(children)
    * */
    @Override
    public List<Menu> findParentMenus() {
        //1.创建一个装父菜单的容器(L)
        List<Menu> parentMenus = new ArrayList<>();
        //2.拿到这个用户的菜单ist
        //拿到当前用户
        Employee loginUser = UserContext.getUser();
        //拿到子菜单
        List<Menu> menus = menuRepository.findMenusByUser(loginUser.getId());
        //3.遍历子菜单
        menus.forEach(m->{
            // 3.1 通过子菜单获取父菜单
            Menu parentMenu = m.getParent();
            // 3.2 判断集合中是否有这个父菜单
            if(!parentMenus.contains(parentMenu)){
                //没有父菜单,就将父菜单放进去
                parentMenus.add(parentMenu);
            }
            parentMenu.getChildren().add(m);
        });

        return parentMenus;
    }
}

效果:
在这里插入图片描述

发布了8 篇原创文章 · 获赞 0 · 访问量 120
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 1024 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览