zoukankan      html  css  js  c++  java
  • Shiro 自定义登陆、授权、拦截器

    Shiro

    • 登陆、授权、拦截
    • 按钮权限控制

    一、目标

    • Maven+Spring+shiro
    • 自定义登陆、授权
    • 自定义拦截器
    • 加载数据库资源构建拦截链
    使用总结:
    1、需要设计的数据库:用户、角色、权限、资源
    2、可以通过,角色,权限,两个拦截器同时确定是否能访问
    3、角色与权限的关系,role1=permission1,permission2,多级的权限:sys:permission1,拥有高级权限同时用于低级权限。
    4、perms["permission1"] 为权限
    5、拦截器机制介绍了拦截角色还是权限
    6、角色与权限 是两个概念
    7、权限-资源,一对一。资源分为上下级,因此权限分为父权限,子权限。创建资源的时候,创建权限。权限里资源的别名
    8、角色-权限,一对多。角色里权限的别名
    9、按钮是通过权限来控制的
    10、防止有父级资源可以访问,子级资源不能访问的情况,不适用 sys:add 权限写法

    二、代码

    1、Pom.xml

     1     <properties>
     2         <spring.version>4.3.4.RELEASE</spring.version>
     3     </properties>
     4         <dependency>
     5             <groupId>junit</groupId>
     6             <artifactId>junit</artifactId>
     7             <version>4.9</version>
     8         </dependency>
     9         <dependency>
    10             <groupId>commons-logging</groupId>
    11             <artifactId>commons-logging</artifactId>
    12             <version>1.1.3</version>
    13         </dependency>
    14         <dependency>
    15             <groupId>org.apache.shiro</groupId>
    16             <artifactId>shiro-core</artifactId>
    17             <version>1.2.2</version>
    18         </dependency>
    19         <dependency>
    20             <groupId>org.apache.shiro</groupId>
    21             <artifactId>shiro-spring</artifactId>
    22             <version>1.2.2</version>
    23         </dependency>
    24         <dependency>
    25             <groupId>javax.servlet</groupId>
    26             <artifactId>javax.servlet-api</artifactId>
    27             <version>3.0.1</version>
    28             <scope>provided</scope>
    29         </dependency>
    30         <dependency>
    31             <groupId>org.springframework</groupId>
    32             <artifactId>spring-web</artifactId>
    33             <version>${spring.version}</version>
    34         </dependency>
    35         <dependency>
    36             <groupId>org.apache.shiro</groupId>
    37             <artifactId>shiro-ehcache</artifactId>
    38             <version>1.2.2</version>
    39         </dependency>
    40         <dependency>
    41             <groupId>org.springframework</groupId>
    42             <artifactId>spring-context</artifactId>
    43             <version>${spring.version}</version>
    44         </dependency>
    45         <dependency>
    46             <groupId>org.apache.shiro</groupId>
    47             <artifactId>shiro-web</artifactId>
    48             <version>1.2.2</version>
    49         </dependency>
    50         <dependency>
    51             <groupId>net.sf.ehcache</groupId>
    52             <artifactId>ehcache</artifactId>
    53             <version>2.10.1</version>
    54     </dependency>            

    2、web.xml

      Servlet拦截访问,使用注解更方便,需要删除项目中的servlet使用javax.servlet-api 3.0 包

     1 package com.cyd.shiro;
     2 
     3 import java.io.IOException;
     4 
     5 import javax.servlet.ServletException;
     6 import javax.servlet.annotation.WebServlet;
     7 import javax.servlet.http.HttpServlet;
     8 import javax.servlet.http.HttpServletRequest;
     9 import javax.servlet.http.HttpServletResponse;
    10 
    11 import org.apache.shiro.SecurityUtils;
    12 import org.apache.shiro.authc.AuthenticationException;
    13 import org.apache.shiro.authc.IncorrectCredentialsException;
    14 import org.apache.shiro.authc.UnknownAccountException;
    15 import org.apache.shiro.authc.UsernamePasswordToken;
    16 import org.apache.shiro.subject.Subject;
    17 import org.apache.shiro.web.util.SavedRequest;
    18 import org.apache.shiro.web.util.WebUtils;
    19 import org.junit.Test;
    20 
    21 @WebServlet(name = "loginServlet", urlPatterns = "/loginController")
    22 public class LoginServlet extends HttpServlet {
    23     @Override
    24     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    25         req.getRequestDispatcher("login.jsp").forward(req, resp);
    26     }
    27 
    28     @Override
    29     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    30         System.out.println(LoginServlet.class.toString());
    31         String error = null;
    32         String username = req.getParameter("username");
    33         String password = req.getParameter("password");
    34         Subject subject = SecurityUtils.getSubject();
    35         UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    36         try {
    37             subject.login(token); 
    38         } catch (UnknownAccountException e) { 
    39             error = "用户名/密码错误";
    40         } catch (IncorrectCredentialsException e) {
    41             error = "用户名/密码错误";
    42         } catch (AuthenticationException e) {
    43             // 其他错误,比如锁定,如果想单独处理请单独catch处理
    44             error = "其他错误:" + e.getMessage();
    45         }
    46         if (error != null) {// 出错了,返回登录页面
    47             req.setAttribute("error", error);
    48             req.getRequestDispatcher("login.jsp").forward(req, resp);
    49         } else {// 登录成功
    50             //跳转到拦截登陆前的地址
    51             SavedRequest request=WebUtils.getSavedRequest(req);
    52             String url =request.getRequestURI();
    53             req.getRequestDispatcher(url.substring(url.lastIndexOf('/'))).forward(req, resp);
    54         }
    55     }
    56     
    57 }

    3、Spring-shiro.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:util="http://www.springframework.org/schema/util"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util 
        http://www.springframework.org/schema/util/spring-util-4.2.xsd">
    
        <context:component-scan base-package="com.cyd.shiro.*"></context:component-scan>
    
        <!-- Shiro的Web过滤器 -->
        <bean id="shiroFilter" class="com.cyd.shiro.ExtendShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <property name="loginUrl" value="/login.jsp" />
            <!-- <property name="successUrl" value="/index.jsp" /> -->
            <property name="unauthorizedUrl" value="/unauthorized.jsp" />
            <property name="filters">
                <util:map>
                    <!-- <entry key="onperms" value-ref="URLPermissionsFilter" /> -->
                    <entry key="onrole" value-ref="ExtendRolesAuthorizationFilter" />
                </util:map>
            </property> 
            <property name="filterChainDefinitions">
                <value>
                    /unauthorized.jsp = anon
                    /logoutController=anon
                    /login.jsp=authc
                </value>
            </property>
        </bean>
    
        <!-- 安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="myRealm" />
            <property name="cacheManager" ref="cacheManager" />
        </bean>
        <!-- 自定义认证,授权 -->
        <bean id="myRealm" class="com.cyd.shiro.AdminRealm"></bean>
    
        <!-- 注册ehcache,不然每次访问都要登陆 -->
        <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
        </bean>
        <!-- 自定义鉴权拦截器 -->
        <bean id="URLPermissionsFilter" class="com.cyd.shiro.URLPermissionsFilter" />
        <bean id="ExtendRolesAuthorizationFilter" class="com.cyd.shiro.ExtendRolesAuthorizationFilter" />
    
    </beans>

    4、Ehcache.xml 缓存

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
            maxElementsInMemory="10000"    
            eternal="false"
            timeToIdleSeconds="600"
            timeToLiveSeconds="600"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
    
    <!-- 
        内存中最多可以存储多少个数据 
        是否永久有效
        空闲时间
        存活时间
        内存空间不够是否存储到磁盘
        磁盘最大存储个数
        服务器重启,磁盘数据是否需要
        线程
        淘汰策略(最近最少使用)  
     -->  
    </ehcache>

    5、登陆Servlet

    package com.cyd.shiro;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.util.SavedRequest;
    import org.apache.shiro.web.util.WebUtils;
    
    @WebServlet(name = "loginServlet", urlPatterns = "/loginController")
    public class LoginServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.getRequestDispatcher("login.jsp").forward(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println(LoginServlet.class.toString());
            String error = null;
            String username = req.getParameter("username");
            String password = req.getParameter("password");
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            try {
                subject.login(token); 
            } catch (UnknownAccountException e) { 
                error = "用户名/密码错误";
            } catch (IncorrectCredentialsException e) {
                error = "用户名/密码错误";
            } catch (AuthenticationException e) {
                // 其他错误,比如锁定,如果想单独处理请单独catch处理
                error = "其他错误:" + e.getMessage();
            }
            if (error != null) {// 出错了,返回登录页面
                req.setAttribute("error", error);
                req.getRequestDispatcher("login.jsp").forward(req, resp);
            } else {// 登录成功
                //跳转到拦截登陆前的地址
                SavedRequest request=WebUtils.getSavedRequest(req);
                String url =request.getRequestURI();
                req.getRequestDispatcher(url.substring(url.lastIndexOf('/'))).forward(req, resp);
            }
        }
        
    }

    6、自定义登陆、授权。

      根据需求自定义登陆异常。从数据库查询出当前用户拥有的权限并授权

      

     1 package com.cyd.shiro;
     2 
     3 import java.util.HashSet;
     4 import java.util.LinkedList;
     5 import java.util.List;
     6 import java.util.Set;
     7 
     8 import org.apache.shiro.authc.AuthenticationException;
     9 import org.apache.shiro.authc.AuthenticationInfo;
    10 import org.apache.shiro.authc.AuthenticationToken;
    11 import org.apache.shiro.authc.SimpleAuthenticationInfo;
    12 import org.apache.shiro.authc.UnknownAccountException;
    13 import org.apache.shiro.authz.AuthorizationInfo;
    14 import org.apache.shiro.authz.SimpleAuthorizationInfo;
    15 import org.apache.shiro.realm.AuthorizingRealm;
    16 import org.apache.shiro.subject.PrincipalCollection;
    17 import org.springframework.beans.factory.annotation.Autowired;
    18 
    19 import com.cyd.helloworld.SysRoles;
    20 import com.cyd.helloworld.SysUsers;
    21 import com.cyd.shiro.admin.SysUsersService;
    22 
    23 public class AdminRealm extends AuthorizingRealm {
    24     
    25     @Autowired
    26     private SysUsersService    sysusersservice;
    27     // 认证登陆
    28     @Override
    29     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    30         System.out.println("do doGetAuthenticationInfo");
    31         String username = (String) token.getPrincipal();
    32         SysUsers user = sysusersservice.getSysUsers(username);
    33         if (user == null) {
    34             throw new UnknownAccountException();// 没找到帐号
    35         }
    36         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), // 用户名
    37                 user.getPassWorld(), // 密码
    38                 getName() // realm name
    39         );
    40         return authenticationInfo;
    41     }
    42 
    43     // 用户授权
    44     @Override
    45     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    46         System.out.println("do doGetAuthorizationInfo");
    47         String username = (String)principals.getPrimaryPrincipal();  
    48         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    49         //从数据库加载当前用户的角色,例如:[admin]
    50         authorizationInfo.setRoles(new HashSet<String>(sysusersservice.getSysRoles(username)));
    51         //从数据库加载当前用户可以访问的资源,例如:[index.jsp, abc.jsp]
    52         authorizationInfo.setStringPermissions(new HashSet<String>(sysusersservice.getSysResource(username)));
    53         
    54         return authorizationInfo;  
    55     }
    56 }

    7、自定义拦截器。

      重写拦截器是因为shiro 验证是否有权限访问是需要当前用户拥有拦截器链的所有权限。一般需求只需要拥有部分权限即可。

           角色验证拦截,hasRole和hasAllRoles 验证是否有权限。

     1 package com.cyd.shiro;
     2 
     3 import java.io.IOException;
     4 import java.util.Set;
     5 
     6 import javax.servlet.ServletRequest;
     7 import javax.servlet.ServletResponse;
     8 
     9 import org.apache.shiro.subject.Subject;
    10 import org.apache.shiro.util.CollectionUtils;
    11 import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
    12 
    13 /**
    14  * 通过角色验证权限
    15  * @author chenyd
    16  * 2017年11月21日
    17  */
    18 public class ExtendRolesAuthorizationFilter extends RolesAuthorizationFilter{
    19     
    20     @Override
    21     public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
    22         
    23         System.out.println(ExtendRolesAuthorizationFilter.class.toString());
    24         Subject subject = getSubject(request, response);
    25         String[] rolesArray = (String[]) mappedValue;
    26 
    27         if (rolesArray == null || rolesArray.length == 0) {
    28             //no roles specified, so nothing to check - allow access.
    29             return true;
    30         }
    31         //AbstractFilter
    32         Set<String> roles = CollectionUtils.asSet(rolesArray);
    33         
    34         boolean flag=false;
    35         for(String role: roles){
    36             if(subject.hasRole(role)){
    37                 flag=true;
    38                 break;
    39             }
    40         }
    41         return flag;
    42     }
    43 }

           url拦截校验,isPermitted和isPermittedAll验证是否有权限访问,

     1 package com.cyd.shiro;
     2 
     3 import java.io.IOException;
     4 
     5 import javax.servlet.ServletRequest;
     6 import javax.servlet.ServletResponse;
     7 import javax.servlet.http.HttpServletRequest;
     8 
     9 import org.apache.shiro.subject.Subject;
    10 import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
    11 /**
    12  * 通过字符串验证权限
    13  * @author chenyd
    14  * 2017年11月21日
    15  */
    16 public class URLPermissionsFilter extends PermissionsAuthorizationFilter {
    17 
    18     /**
    19      * mappedValue 访问该url时需要的权限
    20      * subject.isPermitted 判断访问的用户是否拥有mappedValue权限
    21      * 重写拦截器,只要符合配置的一个权限,即可通过
    22      */
    23     @Override
    24     public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
    25             throws IOException {
    26         System.out.println(URLPermissionsFilter.class.toString());
    27         Subject subject = getSubject(request, response);
    28         // DefaultFilterChainManager
    29         // PathMatchingFilterChainResolver
    30         String[] perms = (String[]) mappedValue;
    31         boolean isPermitted = false;
    32         if (perms != null && perms.length > 0) {
    33             for (String str : perms) {
    34                 if (subject.isPermitted(str)) {
    35                     isPermitted = true;
    36                 }
    37             }
    38         }
    39 
    40         return isPermitted;
    41     }
    42 }

    8、加载数据库资源构建拦截器链

     1 package com.cyd.shiro;
     2 
     3 import java.util.Map;
     4 
     5 import org.apache.shiro.config.Ini;
     6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
     7 import org.apache.shiro.util.CollectionUtils;
     8 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
     9 import org.springframework.beans.factory.annotation.Autowired;
    10 
    11 import com.cyd.shiro.admin.SysUsersService;
    12 
    13 public class ExtendShiroFilterFactoryBean extends ShiroFilterFactoryBean{
    14     
    15     @Autowired
    16     private SysUsersService    sysusersservice;
    17     //PathMatchingFilter
    18     @Override
    19     public void setFilterChainDefinitions(String definitions) {
    20         //数据库中获取权限,{/index.jsp=authc,onrole["admin2","admin"], /abc.jsp=authc,onrole["admin2","admin"]}
    21         Map<String, String> otherChains = sysusersservice.getFilterChain();
    22         Ini ini = new Ini();
    23         ini.load(definitions);
    24         Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
    25         if (CollectionUtils.isEmpty(section)) {
    26             section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
    27         }
    28         section.putAll(otherChains);
    29         setFilterChainDefinitionMap(section);
    30     }
    31     
    32 }

    三、  学习笔记

    1、INI文件配置

    [users]  #提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2  
    
    zhang=123,admin
    
    [roles]  #提供了角色及权限之间关系的配置,角色=权限1,权限2  
    
    admin=index.jsp
    
    [urls] 
    
    #配置拦截器链,/** 为拦截器链名称(filterChain),authc,roles[admin],perms["index.jsp"]拦截器列表名
    
    /login.jsp=anon
    
    /loginController=anon
    
    /unauthorized.jsp=anon
    
    /**=authc,roles[admin],perms["index.jsp"] 

    2、拦截器链

      Shiro的所有拦截器链名定义在源码DefaultFilter中。

    anon             例子/admins/**=anon 没有参数,表示可以匿名使用。 
    authc 例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数  
    roles

     例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,  

     并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],  

     每个参数通过才算通过,相当于hasAllRoles()方法。  

    perms

    例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,  

    例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,  

    想当于isPermitedAll()方法。

    rest

    例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,  

     其中method为post,get,delete等。

    port

    例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,  其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。

    authcBasic                                 

    例如/admins/user/**=authcBasic没有参数表示httpBasic认证

    ssl

    例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

    user

    例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查  

    注:anon,authcBasic,auchc,user是认证过滤器,  

      perms,roles,ssl,rest,port是授权过滤器  

    3、拦截器链源码类关系图

      

    ①   NameableFilter有一个name属性,定义每一个filter的名字。

    ②   OncePerRequestFilter保证客户端请求后该filter的doFilter只会执行一次。

      doFilterInternal非常重要,在shiro整个filter体系中的核心方法及实质入口。另外,shiro是通过在request中设置一个该filter特定的属性值来保证该filter只会执行一次的。

    ③   AdviceFilter中主要是对doFilterInternal做了更细致的切分。

      springmvc中的Interceptor,doFilterInternal会先调用preHandle做一些前置判断,如果返回false则filter链不继续往下执行,

    ④   AccessControlFilter中的对onPreHandle方法做了进一步细化。

      isAccessAllowed方法和onAccessDenied方法达到控制效果。这两个方法都是抽象方法,由子类去实现。到这一层应该明白。isAccessAllowed和onAccessDenied方法会影响到onPreHandle方法,而onPreHandle方法会影响到preHandle方法,而preHandle方法会达到控制filter链是否执行下去的效果。所以如果正在执行的filter中isAccessAllowed和onAccessDenied都返回false,则整个filter控制链都将结束,不会到达目标方法(客户端请求的接口),而是直接跳转到某个页面(由filter定义的,将会在authc中看到)。

    ⑤   FormAuthenticationFiltershiro提供的登录的filter,

      saveRequestAndRedirectToLogin保存request并拦截到登陆页面,登陆成功后可从WebUtils.getSavedRequest(req);中取出。

    四、未实现的功能

    • 动态URL权限控制。当修改权限时,重新加载拦截器链。
    • 密码加密
    • 记住我
    • 在线人数控制
    • 集成验证码 

    五、参考链接

  • 相关阅读:
    软件测试的几种基本方法
    什么是软件测试及软件测试基本原则
    HTTP状态码大全
    jsp 九大内置对象和其作用详解
    快速搞定常用的ES6新特性
    javascript 闭包的学习
    js 中location 的学习
    js 中事件的学习
    js 小菜鸟的学习
    mongodb的返回(3)
  • 原文地址:https://www.cnblogs.com/litblank/p/7883167.html
Copyright © 2011-2022 走看看