Shiro入门Demo

作者: 海天一树X | 来源:发表于2017-10-02 14:52 被阅读0次

    一、shiro简介

    Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:
    认证 - 用户身份识别,常被称为用户“登录”;
    授权 - 访问控制;
    密码加密 - 保护或隐藏数据防止被偷窥;
    会话管理 - 每用户相关的时间敏感的状态。

    对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。
    相对于其他安全框架(比如spring-security),Shiro要简单的多。

    二、Shiro的架构介绍

    首先,来了解一下Shiro的三个核心组件:Subject, SecurityManager 和 Realms。

    2-1.png

    (1)Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。 Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

    (2)SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

    (3)Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

    除前文所讲Subject、SecurityManager 、Realm三个核心组件外,Shiro主要组件还包括:

    2-2.png

    (4)Authenticator :认证就是核实用户身份的过程。这个过程的常见例子是大家都熟悉的“用户/密码”组合。多数用户在登录软件系统时,通常提供自己的用户名(当事人)和支持他们的密码(证书)。如果存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为通过认证。

    (5)Authorizer :授权实质上就是访问控制 - 控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。

    (6)SessionManager :在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 - 从小型后台独立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用这些容器,开发者现在也可以选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。

    (7)CacheManager :对Shiro的其他组件提供缓存支持。

    三、项目代码

    1 pom.xml中添加与shiro相关的jar包

            <!-- Spring 整合Shiro需要的依赖 -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-web</artifactId>
                <version>1.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>1.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.2.1</version>
            </dependency>
    

    2 web.xml中添加与shiro相关的配置

        <!-- shiro过滤器 -->
        <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>*.html</url-pattern>  
            <url-pattern>*.json</url-pattern>
        </filter-mapping>
    

    3 spring-mvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:p="http://www.springframework.org/schema/p"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
      xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd" default-lazy-init="true">
         
         <!-- 扫描controller(controller层注入) -->
         <context:component-scan base-package="com.z.controller"/>  
         
         <!-- 对模型视图添加前后缀 -->
         <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/view/" p:suffix=".jsp"/>
         
    </beans>
    

    4 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"  
        xsi:schemaLocation="http://www.springframework.org/schema/beans   
                            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"  
        default-lazy-init="true">  
      
        <description>Shiro Configuration</description>  
     
        <!-- Shiro's main business-tier object for web-enabled applications -->  
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
            <property name="realm" ref="myShiroRealm" />  
            <property name="cacheManager" ref="cacheManager" />  
        </bean>  
      
        <!-- 項目自定义的Realm -->  
        <bean id="myShiroRealm" class="com.z.shiro.realm.ShiroRealm">  
            <property name="cacheManager" ref="cacheManager" />  
        </bean>  
      
        <!-- Shiro Filter -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <property name="loginUrl" value="/login.html" />
            <property name="successUrl" value="/loginsuccess.html" />
            <property name="unauthorizedUrl" value="/error.html" />
            <property name="filterChainDefinitions">
                <value>
                    /checkLogin.json = anon
                    /** = authc
                </value>
            </property>
        </bean>  
      
        <!-- 用户授权信息Cache -->  
        <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />  
      
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  
      
        <!-- 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>  
        
    </beans>  
    

    这个文件中的anon表示不需要验证,authc表示需要验证。
    /checkLogin.json = anon表示checkLogin.json不需要验证。
    /** = authc表示所有的请求(除checkLogin.json外)都需要验证。

    5 java代码
    (1)controller

    package com.z.controller;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.alibaba.druid.support.json.JSONUtils;
    import com.z.util.EncryptUtil;
    
    @Controller
    public class IndexController {
        @RequestMapping("/index.html")
        public ModelAndView getIndex(HttpServletRequest request) throws Exception {
            ModelAndView mav = new ModelAndView("index");
            return mav;
        }
        
        @RequestMapping("/login.html")
        public ModelAndView login() throws Exception {
            ModelAndView mav = new ModelAndView("login");
            return mav;
        }
        
        @RequestMapping("/loginsuccess.html")
        public ModelAndView loginsuccess() throws Exception {
            ModelAndView mav = new ModelAndView("loginsuccess");
            return mav;
        }
        
        @RequestMapping(value = "newpage1.html")
        public String shownewpage1() {
            Subject currentUser = SecurityUtils.getSubject();
            if(currentUser.hasRole("administrator")){
                return "newPage";
            }else{
                return "pagenofound";
            }
        }
        
        @RequestMapping("/newpage2.html")
        public String shownewpage2() {
            Subject currentUser = SecurityUtils.getSubject();
            if(currentUser.isPermitted("newPage2.html")){
                return "newPage";
            }else{
                return "pagenofound";
            }
        }
        
        @RequestMapping("/error.html")
        public String error() {
            return "error";
        }
    
        @RequestMapping(value="/checkLogin.json",method=RequestMethod.POST)  
        @ResponseBody 
        @RequiresRoles("admin")
        public String checkLogin(String username,String password) {  
            Map<String, Object> result = new HashMap<String, Object>();
            
            try{
                UsernamePasswordToken token = new UsernamePasswordToken(username, EncryptUtil.encryptMD5(password));  
                Subject currentUser = SecurityUtils.getSubject();  
                if (!currentUser.isAuthenticated()){
                    token.setRememberMe(true);  
                    currentUser.login(token);//验证角色和权限  
                } 
            }catch(Exception ex){
                ex.printStackTrace();       
            }
            
            result.put("success", true);
            return JSONUtils.toJSONString(result);  
        } 
        
        @RequestMapping(value="/logout.json",method=RequestMethod.POST)    
        @ResponseBody    
        public String logout() {   
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("success", true);
            Subject currentUser = SecurityUtils.getSubject();       
            currentUser.logout();    
            return JSONUtils.toJSONString(result);
        } 
        
        @RequestMapping(value="/newpage1.json",method=RequestMethod.POST)    
        @ResponseBody    
        public String newpage1() {   
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("success", true);   
            return JSONUtils.toJSONString(result);
        } 
        
        @RequestMapping(value="/newpage2.json",method=RequestMethod.POST)    
        @ResponseBody    
        public String newpage2() {   
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("success", true);   
            return JSONUtils.toJSONString(result);
        } 
    }
    

    (2)Realm

    package com.z.shiro.realm;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    import com.z.util.EncryptUtil;
    
    public class ShiroRealm extends AuthorizingRealm {
        private static final String USERNAME = "admin";  
        private static final String PASSWORD = "123456";  
        
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken authcToken) throws AuthenticationException {
            UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
            
            if(token.getUsername().equals(USERNAME)){
                return new SimpleAuthenticationInfo(USERNAME, EncryptUtil.encryptMD5(PASSWORD), getName());  
            }else{
                throw new AuthenticationException();  
            }
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 
            Set<String> roleNames = new HashSet<String>();  
            Set<String> permissions = new HashSet<String>();  
            roleNames.add("administrator");
            permissions.add("newPage.html"); 
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);  
            info.setStringPermissions(permissions);  
            
            return info;  
        }
    
    }
    

    注意,controller中的currentUser.hasRole("administrator")和currentUser.isPermitted("newPage2.html")会调用Realm中的doGetAuthorizationInfo()。

    6 jsp代码
    (1)index.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
      </head>
      
      <body>
        This is my JSP page. <br>
      </body>
    </html>
    

    (2)login.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <script src="<%=request.getContextPath()%>/js/jquery-1.8.1.min.js"></script>
      </head>
      
      <body>
        username: <input type="text" id="username"><br><br>  
        password: <input type="password" id="password"><br><br>
        <button id="loginbtn">登录</button>
      </body>
      
     <script type="text/javascript">
     $('#loginbtn').click(function() {
        var param = {
            username : $("#username").val(),
            password : $("#password").val()
        };
        $.ajax({ 
            type: "post", 
            url: "<%=request.getContextPath()%>" + "/checkLogin.json", 
            data: param, 
            dataType: "json", 
            success: function(data) { 
                if(data.success == false){
                    alert(data.errorMsg);
                }else{
                    //登录成功
                    window.location.href = "<%=request.getContextPath()%>" +  "/loginsuccess.html";
                }
            },
            error: function(data) { 
                alert("请求登录失败...."); 
            }
        });
     });
     </script>
    </html>
    

    (3)loginsuccess.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <script src="<%=request.getContextPath()%>/js/jquery-1.8.1.min.js"></script>
      </head>
      
      <body>
        登录成功...........
        <br>
        <br>
        <button id="logout">退出</button>
        <br>
        <br>
        <button id="newpage1">newpage 1</button>
        <br>
        <br>
        <button id="newpage2">newpage 2</button>
      </body>
      
      <script type="text/javascript">
      $('#logout').click(function() {
        $.ajax({ 
            type: "post", 
            url: "<%=request.getContextPath()%>" + "/logout.json", 
            data: {}, 
            dataType: "json", 
            success: function(data) { 
                if(data.success == false){
                    alert(data.errorMsg);
                }else{
                    alert("logout success"); 
                    //登录成功
                    window.location.href = "<%=request.getContextPath()%>" +  "/login.html";
                }
            },
            error: function(data) { 
                alert("invoke failure...."); 
            }
        });
      });
      
       $('#newpage1').click(function() {
        $.ajax({ 
            type: "post", 
            url: "<%=request.getContextPath()%>" + "/newpage1.json", 
            data: {}, 
            dataType: "json", 
            success: function(data) { 
                if(data.success == false){
                    alert(data.errorMsg);
                }else{
                    window.location.href = "<%=request.getContextPath()%>" +  "/newpage1.html";
                }
            },
            error: function(data) { 
                alert("调用失败...."); 
            }
        });
      });
      
       $('#newpage2').click(function() {
        $.ajax({ 
            type: "post", 
            url: "<%=request.getContextPath()%>" + "/newpage2.json", 
            data: {}, 
            dataType: "json", 
            success: function(data) { 
                if(data.success == false){
                    alert(data.errorMsg);
                }else{
                    window.location.href = "<%=request.getContextPath()%>" +  "/newpage2.html";
                }
            },
            error: function(data) { 
                alert("调用失败...."); 
            }
        });
      });
      </script>
    </html>
    

    (4)newPage.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
      </head>
      
      <body>
        This is a new page. <br>
      </body>
    </html>
    

    (5)pagenofound.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
      </head>
      
      <body>
        Page not found! <br>
      </body>
    </html>
    

    四、运行结果

    1 在浏览器中输入http://localhost:8080/shiro_demo/login.html ,被Shiro拦截后展示的不是index.jsp页面,而是login.jsp页面

    4-1.png

    2 输入用户名“admin”和密码“123456”后,登录

    4-2.png

    3 点击“newpage 1”按纽,因为有授权,进入newPage.jsp页面

    4-3.png

    4 点击“newpage 2”按纽,因为没有授权,无法进入newPage.jsp页面,而是进入pagenofound.jsp页面

    4-4.png

    5 点击“退出”按纽,成功退出登录

    4-5.png

    五、github项目托管

    shiro_spring源码



    更多内容请关注微信公众号

    wechat.jpg

    相关文章

      网友评论

        本文标题:Shiro入门Demo

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