• springmvc简单集成shiro


    前言:
      有天和同事聊天, 谈起权限管理, 他说他有个同事用shiro用的很溜. 正好现在有个管理平台项目, 有权限控制的需求, 因此想借此机会研究一番.
      本文主要简单讲解一下对shiro的一些认识, 比较浅显, 同时用一个小的演示demo, 来简单实践一下.

    shiro简介:
      shiro是apache旗下的一个开源安全组件, 它比spring security更容易集成和使用.
      官网地址为: Shiro.
      其整个架构图如下:
      
      我觉得核心的一下几个概念:
      Subject:主题, 可以理解为用户的超级session, 通过其可以进行认证/授权/权限验证.
      SecurityManager:他是 Shiro 的心脏;所有组件都交互都通过SecurityManager进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
      Realm:它维护了认证/授权的具体实现.
      至于SessionManager, CacheManager, 这些都属于更进一步的研究, 暂时放放.

    通过权限系统设计思想:
      其实权限系统发展到现在, 由两种主流的做法: 基于角色的权限控制/基于资源的权限控制.
      数据模型图大致如下:
      
      通常把资源和权限整合为'新权限', 数据模型图如下:
      
      注: 该图来源于博文: 从权限到shiro框架.
      而shiro显然也顺从这种设计思路, 它的权限校验, 主要有role/permission两种方式.

    // 1) 校验资源权限
    SecurityUtils.getSubject().checkPermission("blog:write");
    // 2) 校验角色 
    SecurityUtils.getSubject().checkRole("admin");
    

      

    整合实践:
      整合的springmvc demo参考自官网文档 https://shiro.apache.org/spring.html.
      添加maven依赖:

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.0</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>

      编写AuthorizingRealm的自定义实现类:

    public class DemoRealm extends AuthorizingRealm {
    
        // *) 校验权限时, 被回调
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            // 可以添加业务代码
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // *) 添加role
            info.addRole("admin");
    
            // *) 添加权限(资源+权限)
            info.addStringPermission("blog:write");
            info.addStringPermission("blog:read");
            return info;
    
        }
    
        // *) 认证时被调用
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
                throws AuthenticationException {
    
            // 可以添加业务代码
    
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),
                    authenticationToken.getCredentials(),
                    "simple"
            );
    
            return info;
        }
    
    }

      在web.xml中添加filter

    <!-- 添加spring相关的配置文件&属性, 和shiro无关, 但需确保容器的相关bean优于shiroFilter之前创建 -->
    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>/WEB-INF/conf/applicationContext*.xml</param-value>
    </context-param>
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- shiro默认的filter -->
    <filter>
    	<description>shiro</description>
    	<filter-name>shiroFilter</filter-name>
    	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    	<init-param>
    		<param-name>targetFilterLifecycle</param-name>
    		<param-value>false</param-value>
    	</init-param>
    </filter>
    <filter-mapping>
    	<filter-name>shiroFilter</filter-name>
    	<url-pattern>/*</url-pattern>
    </filter-mapping>

      然后在applicationContext.xml添加shiro的相关bean, 这个最简化的情形.

        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
        </bean>
    
        <!-- 配置进行授权和认证的 Realm -->
        <bean id="myRealm" class="com.springapp.mvc.shiro.DemoRealm" />
    
        <!-- 配置 Shiro 的 SecurityManager Bean. -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="myRealm"/>
        </bean>
    
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

      编写测试用例:

    @Controller
    @RequestMapping("/")
    public class HelloController {
    
    	@RequestMapping(value="/login", method={RequestMethod.POST, RequestMethod.GET})
    	@ResponseBody
    	public String login(
    			@RequestParam("username") String username,
    			@RequestParam("password") String password
    	) {
    		// 登陆, 既完成shiro的认证流程
    		Subject subject = SecurityUtils.getSubject();
    		AuthenticationToken token = new UsernamePasswordToken("username", "password");
    		subject.login(token);
    		return "ok";
    	}
    
    	@RequestMapping(value = "/test1", method = {RequestMethod.GET, RequestMethod.POST})
    	@ResponseBody
    	public String test1() {
    		// *) 权限存在, 校验会回调授权实现
    		SecurityUtils.getSubject().checkPermission("blog:write");
    		return "test1";
    	}
    
    	@RequestMapping(value = "/test2", method = {RequestMethod.GET, RequestMethod.POST})
    	@ResponseBody
    	public String test2() {
    		// *) 权限不存在, 校验会回调授权实现
    		SecurityUtils.getSubject().checkPermission("blog:write1");
    		return "test2";
    	}
    
    }
    

      

    测试:
      就是单纯的测试shiro的认证什么时候进行, 授权又是什么时候被回调.
      case 1: 测试认证过程
      直接rest api调用/login, 观察到自定义的DemoRealm的doGetAuthenticationInfo方法被回调, 此时认证成功.
      case 2: 不经过认证, 访问受权限保护的资源
      无论调用/test1, /test2都返回UnauthenticatedException的异常.
      case 3: 经过认证后, 访问受权限保护的资源
      访问/test1, pass
      访问/test2, nopass
      结果符合预期, 而且观察到DemoRealm的doGetAuthorizationInfo方法被回调, 用户的权限列表默认没缓存(官方推荐ehcache做缓存).

    引入注解:
      除了硬编码, 权限资源的check, 还可以引入注解来实现.
      在之前的applicationContext.xml中, 添加如下配置, 使得注解生效.

        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
               <property name="securityManager" ref="securityManager"/>
        </bean>

      然后在测试代码中, 添加如下测试接口(注解@RequiresPermissions/@RequiresRoles).

    @RequestMapping(value = "/test3", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    @RequiresPermissions("blog:write3")
    public String test3() {
    	return "test3";
    }
    
    @RequestMapping(value = "/test4", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    @RequiresRoles({"admin4"})
    public String test4() {
    	return "test4";
    }

      测试的结果都符合预期, 都被完美的拦截下来了.

    更进一步:
      对于面向rest api服务的权限控制需求, 引入注解后, 代码基本上显得非常漂亮了, 简单的配置注解即可完美进行资源访问控制.
      为了更好的屏蔽异常, 可以引入ControllerAdvice/Interceptor, 用于拦截内部异常, 转换为友好的信息提示.
      具体可参看之前写过的文章:
      1. REST API的异常处理.
      2. Interceptor机制和实践.

    遇到的问题记录:
      在具体集成springmvc和shiro的过程中, 也遇到了一些坑.
      1. 遇到shiroFilter实例(ShiroFilterFactoryBean)创建失败, 导致servlet层面的Filter(ShiroFilter)初始化失败.

    No bean named 'shiroFilter' is defined

      主要原因是要保证ShiroFilter(Serlvet层面)的实例要晚于spring容器的实例初始化(既通过listener, 优先加载applicationContext.xml).

    后记:
      以前后端管理平台是基于MVC的架构构建(即后端也处理路由和页面渲染), 现在流行架构是MVVM(前后端完全分离, 前端做路由和渲染, 后端纯数据服务), 因此对shiro的使用和研究, 不再涉及页面标签这块.

  • 相关阅读:
    百度搜索技巧
    Redis汇总
    分享一个经验,代码打开mysql链接,执行存储过程时,提示:Table 'mysql.proc' doesn't exist
    WPF 重写微调自带的样式,ListView、DataGrid、TreeView等所有控件的默认样式
    WPF 图片抗锯齿,尤其是小图片更为严重
    通过鼠标事件,从鼠标点击的坐标寻找指定的控件
    重写TreeView,多层级节点下批量显示图片,图片支持缩略图和文件名列表切换,支持调用者动态匹配选中,支持外界拖入图片并添加到对应节点下
    重写TreeView,自定义图标,生成通行的下划线,取消默认获得焦点失去焦点的效果,并支持拖拽节点到外界
    四、C#入门—表达式与运算符
    三、C#入门—数据类型
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/9336635.html
Copyright © 2020-2023  润新知