Shiro入门Demo

一、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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • Apache Shiro Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权...
    罗志贇阅读 3,210评论 1 49
  • 文章转载自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus阅读 3,385评论 0 3
  • 前言 Spring boot 是什么,网上的很多介绍,这里博客就不多介绍了。如果不明白Spring boot是什么...
    xuezhijian阅读 17,890评论 13 39
  • 1.简介 Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供...
    H_Man阅读 3,150评论 4 48
  • 一:基础概念 什么是权限管理 权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经...
    QGUOFENG阅读 550评论 0 0