使用shiro完成权限判断

一、权限判断

  • 获取到所有权限进行判断,应该从数据库中获取
public class FilterChainDefinitionMapFactory {
    @Autowired
    private IPermissionService iPermissionService;
public LinkedHashMap<String,String>  builderFilterChainDefinitionMap(){
    LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
    //anon  不需要登陆就可以访问   authc需要登陆才可以访问  per[xx:xx]需要授权才可以
    linkedHashMap.put("/s/login.jsp", "anon");
    linkedHashMap.put("/login", "anon");
    linkedHashMap.put("*.js","anon");
    linkedHashMap.put("*.css","anon");
    linkedHashMap.put("/css/**","anon");
    linkedHashMap.put("/js/**","anon");
    linkedHashMap.put("/easyui/**","anon");
    linkedHashMap.put("/images/**","anon");
    //linkedHashMap.put("/dept/index", "perms[dept:index]");
    //linkedHashMap.put("/employee/index", "perms[employee:index]");
   List<Permission> list = iPermissionService.findAll();
    System.out.println(list);
    list.forEach(e->
            linkedHashMap.put(e.getUrl(), "permsFilter["+e.getSn()+"]")
    );
    linkedHashMap.put("/**", "authc");
    return linkedHashMap;
}
}

  • 怎么拿到登录用户
@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //先拿用户传过来的令牌
         UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
         //拿到令牌的用户
        String username = token.getUsername();
        Employee loginUser = iEmployeeService.findByUsername(username);
        if (loginUser==null){
            return null;
        }
        //加盐验证
        //要想使用,还需要在xml配置加密次数,和加密算法。。才可以正确解密验证
        ByteSource salt = ByteSource.Util.bytes("itsource");
        System.out.println(loginUser.getPassword()+"=====================测试=======================");
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(),salt,getName());
        return simpleAuthenticationInfo;
    }

修改页面展示:
主页面的用户现在变成了这个样子
在这里插入图片描述

修改代码:
<shiro:user>
    欢迎[ <shiro:principal property="username"  />]登录,<a href="${pageContext.request.contextPath}/logout">退出</a>
</shiro:user>

二、完成了登陆验证,将用户存入session

  • 为了以后方便,创建一个工具类专门存取用户
/**
 * 该工具可以将当前主体对象存入到session中去
 * */
public class UserContext {
    public static final String LOGINUSER = "loginUser";
    public static void setUser(){
        /*先得到全局的授权主体*/
        Subject subject = SecurityUtils.getSubject();
        //得到session
        Session session = subject.getSession();
        /*将该主题对象存入到session*/
        Employee loginUser = (Employee)subject.getPrincipal();
        session.setAttribute(LOGINUSER, loginUser);
    }
    public static Employee getUser(){
        /*先拿到主体employ对象*/
        Subject subject = SecurityUtils.getSubject();
        //拿到session
        Session session = subject.getSession();
        //拿到session中的对象
        Employee employee = (Employee)session.getAttribute(LOGINUSER);
        return employee;
    }
}
  • LoginController:登录成功后把用户放到Session中
 @RequestMapping(value="/login",method = RequestMethod.POST)
    @ResponseBody
    public SuccessBoolean login(String username,String password){
        System.out.println("===========================jll");
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        if (!subject.isAuthenticated()){
            try {
                subject.login(token);
            } catch (UnknownAccountException e) {
                System.out.println("用户名不存在");
                return new SuccessBoolean(false,"用户名不存在");
            }
            catch (IncorrectCredentialsException e) {
                System.out.println("密码错误");
                e.printStackTrace();
                return new SuccessBoolean(false,"用户名不存在或者密码错误");
            }
            catch (AuthenticationException e) {
                System.out.println("未知错误");
                e.printStackTrace();
                return new SuccessBoolean(false,"未知错误");
            }
        }
        //使用工具存入session中
        UserContext.setUser();
        //如果登陆验证失败,要访问首页还是会被拦截下来
        return new SuccessBoolean();
    }

三、根据用户id拿到权限

  • 完成权限的判断
    其它完成权限,就是通过当前登录用户拿到所有权限,然后去判断当前用户是否有咱们当前访问的这个路径的权限!

PermissionRepository

 @Query("select p.sn from Employee o join o.roles r join r.permissions p where o.id=?1")
    Set<String> findPermissionSnByUserID(Long id);

IPermissionService

在这里插入代码片

PermissionServiceImpl

@Override
    public Set<String> findPermissionSnByUserID(Long id) {
        Set<String> permissions = permissionRepository.findPermissionSnByUserID(id);
        return permissions;
    }
  • 自定义Realm:进入权限判断
@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //拿到主角。。登陆方法验证传过来的是什么  这里就是什么。。
        Employee loginUser = (Employee)principalCollection.getPrimaryPrincipal();
        //核心管理器会调用doGetAuthorizationInfo方法得到这个用户的权限和角色
        //设置到权限的对象并且返回
        Set<String> permissions = iPermissionService.findPermissionSnByUserID(loginUser.getId());
        //将权限改成从数据库中查找出来。。进行判断
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setStringPermissions(permissions);
        //返回权限对象
        return authorizationInfo;
    }

四、测试,并且完成自定义过滤器

以上功能完成后,我们就可以测试咱们的用户是否有相应的权限!
测试
当没有删除权限时,删除报错
在这里插入图片描述
需要的结果是,点击删除,删除失败,后台返回没有权限的结果
在这里插入图片描述
原因,在请求是Ajax请求时,这个删除是Ajax,后台响应返回的就是一个没有权限时的我们自己设置的页面,他把整个全返回了,我们仅仅需要一个失败原因
在这里插入图片描述
想要的是json的结果字符串。。

咱们所有的请求可以分为两大类,一个是跳转页面,xxx/index 还有一类是Ajax请求期望返回的是{“success”:false,”message”:”没有权限”}

区分处理是否是Ajax请求,普通跳转页面的请求 ,就跳转没有权限的页面,如果是ajax请求返回{“success”:false,”message”:”没有权限”}

怎么判断Ajax 判断请求头里面是否有X-Requested-With
跳转页面请求头
在这里插入图片描述

Ajax请求多个一个请求头X-Requested-With XMLHttpRequest
在这里插入图片描述* 所以自定义拦截器
因为shiro自带的拦截器不区分请求,所以不能达到需要的请求需要结果

/**
 * 将自定义过滤器交给shiro,改变shiro的默认过滤器配置
 * */
 //继承PermissionsAuthorizationFilter ,覆写方法
public class AISellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        Subject subject = this.getSubject(request, response);
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        if (subject.getPrincipal() == null) {
            this.saveRequestAndRedirectToLogin(request, response);
        } else {
        //根据请求确定是什么请求
            String xRequestedWith = httpServletRequest.getHeader("X-Requested-With");
            if (xRequestedWith != null &&"XMLHttpRequest".equals(xRequestedWith)) {
                //3.在这里就代表是ajax请求
                //表示ajax请求 {"success":false,"message":"没有权限"}
                //返回结果
                httpServletResponse.setContentType("text/json; charset=UTF-8");
                httpServletResponse.getWriter().print("{\"success\":false,\"msg\":\"没有权限\"}");
            }else {
                String unauthorizedUrl = this.getUnauthorizedUrl();
                if (StringUtils.hasText(unauthorizedUrl)) {
                    WebUtils.issueRedirect(request, response, unauthorizedUrl);
                } else {
                    WebUtils.toHttp(response).sendError(401);
                }
            }
            }
        return false;
    }
}
  • 把过滤器交给shiro,配置applicationContext-shiro.xml
<!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- 登录的url,如果没有登录,你访问的路径会跳到这个页面 -->
    <property name="loginUrl" value="/login"/>
    <!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
    <property name="successUrl" value="/main"/>
    <!-- 没有权限时跳转到这个位置 -->
    <property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
    <!-- 这个配置我们可以直接给一个map(动态的可以从代码中获取) -->
    <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
    <!-- 引用自定义的权限过滤器 -->
    <!--引用自定义过滤器    在map,权限工厂配置路径-->
        <property name="filters" >
            <map>
                <entry key="permsFilter" value-ref="aiSellPermsFilter"></entry>
            </map>
        </property>

<!-- 这个bean是帮助咱们获取相应的值:它会到一个工厂bean中通过对应的方法拿到相应的值 -->
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="builderFilterChainDefinitionMap"></bean>
    <!-- 配置可以创建 -->
    <bean id="filterChainDefinitionMapFactory" class="com.lirui.web.shiro.FilterChainDefinitionMapFactory">
    </bean>

    <bean id="aiSellPermsFilter" class="com.lirui.web.shiro.AISellPermissionsAuthorizationFilter" />

  • 修改自动权限设置的工具类FilterChainDefinitionMapFactory
    修改权限
    linkedHashMap.put(e.getUrl(), “permsFilter[”+e.getSn()+"]")
public class FilterChainDefinitionMapFactory {
    @Autowired
    private IPermissionService iPermissionService;

public LinkedHashMap<String,String>  builderFilterChainDefinitionMap(){
    LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
    /*
    /s/login.jsp = anon
    /login = anon
    /dept/index = perms[dept:index]
    /employee/index = perms[employee:index]
    /** = authc
     * */
    //anon  不需要登陆就可以访问   authc需要登陆才可以访问  per[xx:xx]需要授权才可以
    linkedHashMap.put("/s/login.jsp", "anon");
    linkedHashMap.put("/login", "anon");
    linkedHashMap.put("*.js","anon");
    linkedHashMap.put("*.css","anon");
    linkedHashMap.put("/css/**","anon");
    linkedHashMap.put("/js/**","anon");
    linkedHashMap.put("/easyui/**","anon");
    linkedHashMap.put("/images/**","anon");
    //linkedHashMap.put("/dept/index", "perms[dept:index]");
    //linkedHashMap.put("/employee/index", "perms[employee:index]");
   List<Permission> list = iPermissionService.findAll();
    System.out.println(list);
    list.forEach(e->
            linkedHashMap.put(e.getUrl(), "permsFilter["+e.getSn()+"]")
    );
    linkedHashMap.put("/**", "authc");
    return linkedHashMap;
}
}

五、菜单读取

没有url的就是父菜单,根目录。。
用户->角色->权限->菜单
用户有哪些权限,就应该有对应的菜单
(这里咱们可以分析数据库理解设计)
用户拥有对应的权限就拥有对应的菜单(二级菜单),如果此菜单有父菜单(一级菜单)也同时拥有

  • Domain设计
@Entity
@Table(name="menu")
public class Menu extends BaseDomain {

     private String name;//菜单名称
     private String url; //路径
     private String icon; //图标

    /**
     * JsonIgnore:生成JSON的时候忽略这个属性
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="parent_id")
    @JsonIgnore //这里生成json的时候要忽略,否则会造成功能相互调用
    private Menu parent;

    /**
     * 还要配置一个一对多
     *  这个字段不要交给JPA管理【到时候自己写代码管理】
     *  数据库的menu表中就应该有一个children,而且还是List类型
     *  Transient:临时属性(JPA不管这个属性,和数据库没有关系)
     */
    @Transient
    private List<Menu> children = new ArrayList<>();

    …
   public String getText(){ //EasyUI的树需要一个text属性
      return name;
    }
}

因为前台的名字是叫Text
public String getText(){ //EasyUI的树需要一个text属性
return name;}

多个权限可以对应一个菜单
比如系统管理菜单下面可以有员工的管理等等
数据管理菜单也可以管理员工。。等等
所以权限和菜单是多对一关系

  • Permission类
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="menu_id")
private Menu menu;

MenuRepository
员工和角色多对多,一个员工可能有多个角色
一个角色可能多个权限,每一个权限对应一个menu 每一个权限permission对象中就有一个menu对象 每一个menu对象都有一个parent字段,
一个人通过permission去查menu,查到的menu只有子菜单,
Permission 中的 menu 全是子菜单 只要有子菜单就一定有父菜单

比如一个user,有员工管理权力8,查询得到的是id为8的menu对象,这个menu一定有一个父菜单字段parent,6号

在这里插入图片描述在这里插入图片描述

  • MenuRepository
@Query("select p.sn from Employee o join o.roles r join r.permissions p where o.id=?1")
    Set<String> findPermissionSnByUserID(Long id);
  • IMenuService
List<Menu> findByUserId();
  • MenuServiceImpl
@Override
    public List<Menu> findByUserId() {
        Employee loginUser = UserContext.getUser();
        //拿到所有的菜单menu对象
        List<Menu> menuList = menuRepository.findByUserId(loginUser.getId());
        //准备一个父菜单容器  仅仅是存入子菜单返回给前台
        List<Menu> parentList = new ArrayList<>();
        //遍历查询的所以菜单
        for (int i = 0;i<menuList.size();i++){
        //得到单个的子菜单对象menu,里面有parent,有空的children集合没用,通过他得到父菜单
        Menu menuChild = menuList.get(i);
        //得到父菜单,这个父菜单的children集合就有用了!
        //判断父菜单集合中是否有这个父菜单,没有就先放进父菜单容器再添加,有的话就直接添加子菜单
        Menu menuParent = menuChild.getParent();
        if (!parentList.contains(menuParent)){
            //先添加到父容器集合
            parentList.add(menuParent);
        }
        //添加子菜单到这个父菜单
        /*因为当次循环的是这个子菜单,所以添加到的就一定是他自己的父菜单中*/
            menuParent.getChildren().add(menuChild);
        }
        //返回父容器集合给前台  里面有子菜单。。
        System.out.println(parentList.get(1).getChildren());
        return parentList;
    }

思路梳理
Menu中父菜单和子菜单,父菜单要找得到子菜单
设计思路。
两种思路都是只返回几个父对象,然后前台使用父对象的集合中的子对象,
前台接收父菜单,通过父菜单获取子菜单,形成菜单结构
1.如果父菜单和子菜单多对一,一对多。不行,为什么?
2.因为查询到了user的menu,的却可以直接拿到父菜单和父菜单对应的子菜单,然后得到父菜单,但是返回了父菜单,因为是双向,就可以直接找到所有子菜单了
那就相当于不验证直接返回所有菜单
2.所以,不能配置双向,父菜单中的集合我们自己控制,打上注解@transient
4 每一个menu对象都有一个childrenList集合在内部,直接查询出来是子菜单,是空的
查出user的所有menu的时候,findbyuserID方法是返回所有menu给前台
5.我们应该自己管理返回结果,应该返回那几个父菜单,将子菜单存进去。
6.所以service层覆写方法,现在用子菜单得到他的父菜单menu对象,然后准备一个要返回的父菜单,如果这个父菜单有了,就不存入,然后将这个子菜单存到这个父菜单中
返回给前台

六、 UtilController

用于返回treeMenu菜单

//拿到当前用户的菜单
    @RequestMapping("/loginUserMenus")
    @ResponseBody
    public List<Menu> loginUserMenus(){
        List<Menu> menuList = iMenuService.findByUserId();
        return menuList;
    }

修改jsp页面的url
在这里插入图片描述

  • 查看返回数据时报错
    在这里插入图片描述
    原因
    相互找,循环,让子菜单不要去找父菜单
    只要是双向关系就要出问题
    @JsonIgnore //这里生成json的时候要忽略,否则会造成功能相互调用
    在这里插入图片描述

七、页面按钮权限控制

一个用户如果有查看员工的权限,但是它却没有删除的权限,我们连按钮都不应该让他看到!

<shiro:hasPermission name="employee:save">
    <a href="#"  data-method="add" class="easyui-linkbutton" iconCls="icon-add" plain="true">添加</a>
</shiro:hasPermission>
<shiro:hasPermission name="employee:update">
    <a href="#"  data-method="edit" class="easyui-linkbutton" iconCls="icon-edit" plain="true">修改</a>
</shiro:hasPermission>
<shiro:hasPermission name="employee:delete">
    <a href="#"  data-method="del" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
</shiro:hasPermission>
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
web.xml配置 因为我们是与spring进行集成的,而spring的基本就是web项目的xml文件。所以我们在web.xml中配置shiros的过滤拦截。正常情况下,我们需要将shiro的filter配置在所有的filter前面,当然和encodingFilter这个filter是不区分前后的。因为两者互相不影响的。spring-shiro.xml 这里我们将来看看spring-shiro.xml的配置,这里我采取倒叙的方式讲解,我觉的倒叙更加的有助于我们理解代码。首先我们还记得在web.xml中配置的那个filter吧,名字shiroFilter,对spring-shiro.xml配置文件就是通过这个filter展开的。首先我们在web.xml配置的过滤器实际上是配置ShiroFilterFactoryBean,所以在这里需要将ShiroFilterFactoryBean定义为shiroFilter <!-- Shiro的核心安全接口,这个属性是必须的 --> <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.html"页面 --> <!-- 登录成功后要跳转的连接 --> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --> <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> /statics/**=anon /login.html=anon /sys/schedule.html=perms[sys:schedule:save] /sys/login=anon /captcha.jpg=anon /**=authc

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值