当前位置: 首页>后端>正文

CRM项目03-shiro02

一、基于 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
   */

异常捕获

  • @ControllerAdvice:Controller 增强器
    通常和@ExceptionHandler 注解配合使用,其中最常用的是@ExceptionHandler,对 Controller 中
    的异常作特定处理。
  • @ExceptionHandler:异常处理器
    贴在方法上,当 Controller 中配置指定异常时,会执行贴了该注解的方法。

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>
  • maxElementsInMemory: 缓存最大个数。
  • eternal:对象是否永久有效,一但设置了,timeout 将不起作用。
  • timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
  • timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当 eternal=false 对象不是永久有效时使用,默认是 0.,也就是对象存活时间无穷大。
  • memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。默认策略是 LRU(最近最少使用)。你可以设置为 FIFO(先进先出)或是 LFU(较少使用)。
  • clearOnFlush:内存数量最大时是否清除。
  • overowToDisk:当内存中对象数量达到 maxElementsInMemory 时,Ehcache 将会对象写到磁盘中。

五、用户账号状态

CRM项目03-shiro02,第1张

https://www.xamrdz.com/backend/3p71940013.html

相关文章: