CRM项目03-shiro02

2020-05-20  本文已影响0人  建国同学

一、基于 Shiro 的权限加载

Controller 方法上贴 Shiro 提供的权限注解

(@RequiresPermissions)

@RequiresRoles("ADMIN")
//权限表达式可以配置多个,默认是&&,改为or相当于||, 要求同时拥有多个权限才能访问该方法
@RequiresPermissions(value = {"role:list","角色列表"},logical = Logical.OR) 

开启 Shiro 注解扫描器

需要applicationContext.xml配置<aop:config>才会创建处理对象
shiro.xml

 <!-- 开启 Shiro 注解扫描器,对贴了 shiro 注解的类进行代理,利用代理的方式来实现权限拦截的功能
    需要applicationContext.xml配置<aop:config>才会创建..advisor这样的处理bean的对象 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

生成权限信息方法

 @Override
    public void reload(){
        // 获取数据库中所有的权限表达式
        List<String> expressions = permissionMapper.selectAllExpression();// role:delete   role:list

        // 从spring 容器中获取所有的controller
        Map<String, Object> beans = ctx.getBeansWithAnnotation(Controller.class);
        // 获取所有的value值
        Collection<Object> values = beans.values();
        // 遍历循环获取每一个controller
        for (Object controller : values){
            // 判断实例是否是Cglib的代理对象
            if (!AopUtils.isCglibProxy(controller)) {
                continue; // 执行下一次循环
            }
            // 获取controller 的字节码对象
            // isCglibProxy已解决=>SuperClass获取该类的父类代理类,就可以获取到代理的注解,其他类获取到父类Object,就拿不到注解,annotation=null
            Class<?> clazz = controller.getClass().getSuperclass();
            // 获取controller 的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            // 遍历出每一个方法
            for (Method method:methods) {
                // 判断是否有贴自定义的权限注解
                RequiresPermissions annotation = method.getAnnotation(RequiresPermissions.class);
                // 如果有贴,封装成对象,并插入数据库
                if(annotation != null){
                    // 获取权限表达式
                    String expression = annotation.value()[0];
                    // 通过反射拼接权限表达式
                    /*String expression = clazz.getSimpleName();  // DepartmentController
                    expression = expression.replace("Controller",""); // Department
                    expression = StringUtils.uncapitalize(expression);  // department
                    expression = expression + ":" + method.getName(); // department:list*/

                    // 判断该表达式是否已经存在数据库中,不存在就插入
                    if(!expressions.contains(expression)) {
                        Permission permission = new Permission();
                        permission.setName(annotation.value()[1]); // 中文的权限名称
                        permission.setExpression(expression); // 英文 权限表达式
                        permissionMapper.insert(permission);
                    }
                }
                // 如果没有就不处理
            }
        }

    }

二、Shiro 基于web 环境的授权

hiro.java

获取授权进行处理

    /**
     * 授权,代码中需要 判断授权 hasxxx的时候才会执行
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("=======================");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 获取当前登录用户
        Subject subject = SecurityUtils.getSubject();
        // 获取主体的身份信息
        Employee employee = (Employee) subject.getPrincipal();// new SimpleAuthenticationInfo(传第一个参数)
        // 判断是否是管理员
        if (employee.isAdmin()) {
            info.addRole("ADMIN");// 方便后续判断是否管理员可以直接用submit来判断
            info.addStringPermission("*:*"); // *:* 表示所有权限,即不做任何限制
        } else {
            // 根据用户的id查询该用户拥有的角色
            List<Role> roles = roleMapper.selectByEmpId(employee.getId());
            ArrayList<String> roleSnList = new ArrayList<>();
            for (Role role : roles) {
                roleSnList.add(role.getSn()); // 放入角色的编码
            }
            info.addRoles(roleSnList);
            // 根据用户的id查询该用户拥有的权限
            List<String> permission = permissionMapper.selectByEmpId(employee.getId());
            info.addStringPermissions(permission);
        }
        return info;
    }

登录控制器

LoginController.java

 @RequestMapping("/login")
    @ResponseBody
    public JsonResult login(String username, String password){
        try {
            // 封装令牌
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            // 利用shiro的api来进行登录
            SecurityUtils.getSubject().login(token);
            return new JsonResult(); // 默认为true
            
        }catch (UnknownAccountException e){
            e.printStackTrace();
            return new JsonResult(false,"账号不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            return new JsonResult(false,"密码错误");
        }catch (DisabledAccountException e){
            e.printStackTrace();
            return new JsonResult(false,"账号已禁用,请联系管理员");
        }catch (Exception e){
            e.printStackTrace();
            return new JsonResult(false,"登录异常,请联系管理员");
        }
    }

  /* @RequestMapping("/logout")
        在 shiro过滤器中配置
       /logout.do=logout
   */

异常捕获

ContrllerExceptionHandler.java

/**
 * 对控制器进行处理
 * 利用aop
 */
// mvc.xml 的扫描控制器 上一定要扫到
@ControllerAdvice
public class ContrllerExceptionHandler {

    /**
     * 当前的方法用于捕获指定的异常
     */
    @ExceptionHandler(RuntimeException.class) // 规定为运行时异常
    public String handler(RuntimeException e, HttpServletResponse response, HandlerMethod handlerMethod) throws IOException {
        // 打印错误信息方便debug
        e.printStackTrace();
        // 判断如果是ajax对应的方法(判断有没有ResponseBody注解),有就返回JsonResult
        if(handlerMethod.hasMethodAnnotation(ResponseBody.class)){
            response.setContentType("application/json;charset=utf-8"); // 为了适配浏览器,显式的告诉浏览器数据类型和编码方式
            response.getWriter().print(JSON.toJSONString(new JsonResult(false, "操作失败")));
            return null;
        }else {
            // 如果不是,就返回错误的视图页面
            return "common/error";
        }
    }

    /**
     * 捕获没有权限的异常
     * @param e
     * @param response
     * @param handlerMethod
     * @return
     * @throws IOException
     */
    @ExceptionHandler(UnauthorizedException.class) // 规定为运行时异常
    public String handlerUnauthorized(RuntimeException e, HttpServletResponse response, HandlerMethod handlerMethod) throws IOException {
        // 打印错误信息方便debug
        e.printStackTrace();
        // 判断如果是ajax对应的方法(判断有没有ResponseBody注解),有就返回JsonResult
        if(handlerMethod.hasMethodAnnotation(ResponseBody.class)){
            response.setContentType("application/json;charset=utf-8"); // 为了适配浏览器,显式的告诉浏览器数据类型和编码方式
            response.getWriter().print(JSON.toJSONString(new JsonResult(false, "您没有该权限")));
            return null;
        }else {
            // 如果不是,就返回错误的视图页面
            return "common/nopermission";
        }
    }


}

三、 Shiro 标签

依赖

<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>1.0.0</version>
</dependency>

注册 shiro 的标签

MyFreeMarkerConfig.java

public class MyFreeMarkerConfig extends FreeMarkerConfigurer {
    @Override
    public void afterPropertiesSet() throws IOException, TemplateException {
        //继承之前的属性配置,这不不能省
        super.afterPropertiesSet();
        Configuration cfg = this.getConfiguration();
        cfg.setSharedVariable("shiro", new ShiroTags());//shiro 标签
    }
}

配置到当前环境

将 shiro 的标签设置成当前环境中使用的配置对象
mvc.xml

<!-- 注册FreeMarker配置类(配置自定义的配置类,带有shiro标签的功能) -->
    <bean class="cn.wolfcode.shiro.MyFreeMarkerConfig">
        <!-- 配置 freemarker 的文件编码 -->
        <property name="defaultEncoding" value="UTF-8"/>
        <!-- 配置 freemarker 寻找模板的路径 -->
        <property name="templateLoaderPath" value="/WEB-INF/views/"/>
    </bean>

shiro 的 freemarker 常用标签:

  1. authenticated 标签:已认证通过的用户。
    <@shiro.authenticated> </@shiro.authenticated>
  2. notAuthenticated 标签:未认证通过的用户。与 authenticated 标签相对。
    <@shiro.notAuthenticated></@shiro.notAuthenticated>
  3. principal 标签:输出当前用户信息,通常为登录帐号信息
    <@shiro.principal property="name" />
    后台是直接将整个员工对象作为身份信息的
    return new SimpleAuthenticationInfo(employee,employee.getPassword(),ByteSource.Util.bytes(username),"CrmRealm");
<@shiro.authenticated>
    <@shiro.principal property="name"/>
</@shiro.authenticated>
  1. hasRole 标签:验证当前用户是否属于该角色 ,
    <@shiro.hasRolename=”admin”>Hello admin!</@shiro.hasRole>
  2. hasAnyRoles 标签:验证当前用户是否属于这些角色中的任何一个,角色之间逗号分隔 ,
    <@shiro.hasAnyRoles name="admin,user,operator">Hello admin! </@shiro.hasAnyRoles>
  3. hasPermission 标签:验证当前用户是否拥有该权限 ,
    <@shiro.hasPermissionname="/order:*">订单/@shiro.hasPermission>
                <ul class="treeview-menu">
                    <@shiro.hasPermission name="department:list">
                        <li name="department"><a href="/department/list.do"><i class="fa fa-circle-o"></i> 部门管理</a></li>
                    </@shiro.hasPermission>
                    <@shiro.hasPermission name="employee:list">
                        <li name="employee"><a href="/employee/list.do"><i class="fa fa-circle-o"></i> 员工管理</a></li>
                    </@shiro.hasPermission>
                    <@shiro.hasPermission name="permission:list">
                        <li name="permission"><a href="/permission/list.do"><i class="fa fa-circle-o"></i> 权限管理</a></li>
                    </@shiro.hasPermission>
                    <@shiro.hasPermission name="role:list">
                        <li name="role"><a href="/role/list.do"><i class="fa fa-circle-o"></i> 角色管理</a></li>
                    </@shiro.hasPermission>
                    <li name="classinfo"><a href="/classinfo/list.do"><i class="fa fa-circle-o"></i> 班级管理</a></li>
                </ul>

四、集成 EhCache

在请求中一旦进行权限的控制都去到 Realm 中的 doGetAuthorizationInfo 方法进行授权,我们授权信息应该要从数据库中查询的。 如果每次授权都要去查询数据库就太频繁了,性能不好,所以需要用到缓存

依赖

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-ehcache</artifactId>
  <version>1.2.2</version>
</dependency>
<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache-core</artifactId>
  <version>2.6.8</version>
</dependency>

配置缓存管理器

shiro.xml

     <!-- 安全管理器 分发调度-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 配置数据源 -->
        <property name="realm" ref="crmRealm"/>
        <!-- 注册缓存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
    </bean>


    <!-- 缓存管理器 权限 角色 shiro有关的数据会被缓存 (清除缓存:注销,清除自己的   关闭服务器,清除所有)-->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- 设置配置文件 -->
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>

添加 ehcache 配置文件

shiro-ehcache.xml

<ehcache>
    <defaultCache
            maxElementsInMemory="1000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

五、用户账号状态

上一篇下一篇

猜你喜欢

热点阅读