美文网首页
shiro 框架的使用

shiro 框架的使用

作者: Jason_M_Ho | 来源:发表于2018-12-12 15:35 被阅读0次

    网上有很多文章介绍如何使用shiro,如果你是第一次接触shiro,各种文章可能看了好多篇也还是一头雾水。笔者当初也是这样,想找一个简单的入门教程看看,了解一下shiro是什么,能做什么,以及怎么操作,结果看了很多也不太明白,走了很多弯路。因为网上的文章大多是以过来者的身份介绍,在学习一个新东西的时候,如果你折腾了好长时间,过来了,就能明白那些文章在讲什么,哪些是重点,哪些是坑;如果你折腾了好长时间,还是云里雾里,那么恭喜你,因为你看到了这篇文章,我会用初学者的眼光,带你在最短时间内抓住shiro的本质。

    在学新东西的时候,要问三个问题:这个新东西是什么?能做什么?我怎么操作使用它?
    如果有余力,最好再问一个为什么,看一看它的源码,理解它的原理,这样从原理到实现细节的方方面面就都掌握了。

    Apache Shiro是一个强大且易用的Java安全框架,能做身份验证、授权、密码学和会话管理。
    请参考shiro官网介绍

    Shiro 最主要的两个部分就是认证和授权,Shiro通过过滤拦截请求实现认证或授权。

    shiro 主要框架图


    shiro.png

    Subject

    Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权。

    SecurityManager

    SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。

    SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

    Authenticator

    Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

    Authorizer

    Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

    realm

    Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。realm不仅仅是从数据源取数据,在realm中还有认证、授权校验的相关的代码。

    sessionManager

    sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

    SessionDAO

    SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

    CacheManager

    CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

    Cryptography

    Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

    从应用程序的角度看,它的工作流程是这样的:

    shiro_flow.jpeg

    简单的来说,它的工作流程是:应用代码被包装成 subject,然后 SecurityManager 通过Realm获取用户权限数据对其实现登录和授权的校验,经过授权就可以正常访问了。

    Shiro认证与授权在Web中的实现

    第一步:添加jar包
    第二步:配置web.xml shiro过滤器
    第三步:自定义Realm ,继承 AuthorizingRealm ,重写 AuthorizationInfo(授权) ,重写 AuthenticationInfo(认证)
    第四步:配置spring-shiro.xml
    第五步:在spring-mvc.xml中配置权限的控制 异常的跳转
    第六步:在controller中使用

    • 第一步:添加jar包,版本1.3.2,最近的稳定版本
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-web</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.3.2</version>
            </dependency>
    
    • 第二步:配置web.xml shiro过滤器;targetFilterLifecycle 为 true 代表由 spring 控制该 filter 的生命周期。shiro 源码中的 demo 配的是/*,因为我只想控制用户访问后端接口的权限,静态资源可以随便访问,所以后端接口都用的api开头,让shiro过滤器只对api开头的请求生效。
    <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
            <init-param>
                <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>/api/*</url-pattern>
        </filter-mapping>
    
    • 第三步:自定义Realm ;shiro 中 realm 是进行认证和授权的组件,自带了几种实现,比如 jdbcRealm 和 iniRealm,实际项目中肯定都是自己实现 realm, 这里自定义 MyRealm 继承 AuthorizingRealm,分别实现认证和授权的方法。
    public class MyRealm extends AuthorizingRealm {
       //用来做测试的数据,实际开发中都是从数据库中取的
        private static final String USERNAME = "admin";
        private static final String PASSWORD = "123456";
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
          //principals.getPrimaryPrincipal()获得的就是当前用户名,权限取值在实际中是从数据库中取出的
            Set<String> permissions = new HashSet<String>();
            permissions.add("sys:page1");
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.setStringPermissions(permissions);
            return info;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String username = (String) token.getPrincipal();
            String password = new String((char[]) token.getCredentials());
            //账号错误
            if(!username.equals(USERNAME)) {
                throw new AuthenticationException("账号不正确");
            }
    
            //密码错误
            if(!password.equals(PASSWORD)) {
                throw new IncorrectCredentialsException("密码不正确");
            }
    
    //getName()返回该realm的名字,代表该认证信息的来源是该realm
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
            return info;
        }
    }
    

    doGetAuthenticationInfo是认证的方法,当用户登陆的时候会调用
    doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用

    • 第四步:配置spring-shiro.xml
      配置 ShiroFilterFactoryBean ,注意 id 一定要和 web.xml 中 的 filter-name 一致,否则 proxyFilter 找不到实际 filter。
      shiro 的内置过滤器有anon、authc、logout、perms、roles等等,可以分成两类,一类会调用 realm 中的认证方法,一类会调用 realm中 的授权方法,更详细的请自行百度。
    <!--配置全局权限过滤器-->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <!--设置没有登录时的跳转地址-->
            <property name="loginUrl" value="/static/html/login.html"/>
            <!--设置权限不足时的跳转地址-->
            <property name="unauthorizedUrl" value="/static/html/error.html"/>
            <!--对哪个后端接口使用哪个过滤器进行配置,等号后边是shiro内置过滤器的名字-->
            <property name="filterChainDefinitions">
                <value>
                    <!--匿名访问,/api/login是登陆接口,当然可以随便访问-->
                    /api/login = anon
                    <!--用户退出登录的接口,后端不需要实现该接口,logout拦截到/api/logout的url后,就自动清除登录状态回到首页了-->
                    <!--因为在web.xml中设置的url-parttern是/api/*,随意只有api开头的url才会被拦截-->
                    /api/logout = logout
                    <!--其他所有接口都需要认证,也就是需要之前输入过账号密码登录过-->
                    /** = authc
                </value>
            </property>
        </bean>
    
    • 在 securityManager 中注入自定义的 realm
    <!--非web环境使用DefaultSecurityManager-->
       <bean id="myRealm" class="cn.shirodemo.shiro.MyRealm"/>
    
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="myRealm"/>
        </bean>
    
    • AOP式方法级权限检查
       <!-- AOP式方法级权限检查  -->
      <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
          <property name="proxyTargetClass" value="true" />
      </bean>
      <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
          <property name="securityManager" ref="securityManager"/>
      </bean>
    
    • 第五步:在 spring-mvc.xml 中配置异常的跳转
    <!-- 未认证或未授权时跳转必须在springmvc里面配,spring-shiro里的shirofilter配不生效 -->
       <bean   class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
           <property name="exceptionMappings">
               <props>
                   <!--没有授权的异常 -->
                   <prop key="org.apache.shiro.authz.UnauthorizedException">
                       <!--捕获该异常时跳转的路径 -->
                       /error
                   </prop>
                   <!--没有认证的异常 -->
                   <prop key="org.apache.shiro.authz.UnauthenticatedException">
                       <!--捕获该异常时跳转的路径 -->
                       /error
                   </prop>
               </props>
           </property>
       </bean>
    
    • 第六步:在controller中测试使用的验证登入
    @RequestMapping(value="/login.do",method=RequestMethod.POST)
    @ResponseBody
    public String login(String username,String password) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("success", true);
    
        Subject subjectecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        subject.login(token);
        
        return JSONUtils.toJSONString(result);
    }
    

    现在只需要在相应方法前加上@RequiresPermissions就可以进行权限控制了

    @RequiresPermissions("sys:page1")
    @RequestMapping(value="/toPage1.do",method= RequestMethod.POST)
    @ResponseBody
    public String toPage1() {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("success", true);
        return JSONUtils.toJSONString(result);
    }
    
    
    • 最后补充一点,web 项目的前台页面如何用 shiro 进行配置呢。

    • 如果是jsp页面,在 jsp 中引入 shiro 标签
      <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>

    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    
    <shiro:authenticated> <a href="xxx.jsp">xxxxxx</a> </shiro:authenticated> 
    
    <shiro:hasRole name="admin"> <a href="xxx.jsp">xxxxxx</a> </shiro:hasRole> 
    
    <shiro:hasPermission name="user:create"> <a href="xxx.jsp">xxxxxx</a> </shiro:hasPermission> 
    
    
    1. guest标签 :验证当前用户是否为“访客”,即未认证(包含未记住)的用户。
    2. user标签 :认证通过或已记住的用户。
    3. authenticated标签 :已认证通过的用户。不包含已记住的用户。
    4. notAuthenticated标签 :未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
    5. principal 标签 :输出当前用户信息,通常为登录帐号信息。
    6. hasRole标签 :验证当前用户是否属于该角色。
    7. lacksRole标签 :与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
    8. hasAnyRole标签 :验证当前用户是否属于以下任意一个角色。
    9. hasPermission标签 :验证当前用户是否拥有指定权限。
    10. lacksPermission标签 :与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。
    • 如果前台页面是 html,且使用了 thymeleaf 的模板引擎
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
    

    配置一下 thymeleaf

    <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
      <property name="templateResolver" ref="templateResolver" />
      <property name="additionalDialects">
        <set>
          <bean class="at.pollux.thymeleaf.shiro.dialect.ShiroDialect"/>
        </set>
      </property>
    </bean>
    
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    

    这样就可以在页面中使用了

    <p shiro:hasRole="admin"></p>
    <p shiro:hasPermission="userInfo:add"></p>
    
    • 如果前台页面是 html,没有使用模板怎么办?
      这样就无法使用shiro标签了,不过这里有个解决思路,前台页面通过ajax获取后台某个角色或是权限的信息,然后在前台用改变标签样式display:none的方法让相应内容隐显。

    相关文章

      网友评论

          本文标题:shiro 框架的使用

          本文链接:https://www.haomeiwen.com/subject/gwqphqtx.html