一.shiro的簡介
Apache Shiro(發音爲“shee-roh”,日語“堡壘(Castle)”的意思)是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理功能,可爲任何應用提供安全保障 - 從命令行應用、移動應用到大型網絡及企業應用。
Shiro爲解決下列問題(我喜歡稱它們爲應用安全的四要素)提供了保護應用的API:
- 認證 - 用戶身份識別,常被稱爲用戶“登錄”;
- 授權 - 訪問控制;
- 密碼加密 - 保護或隱藏數據防止被偷窺;
- 會話管理 - 每用戶相關的時間敏感的狀態。
從2003年至今,框架選擇方面的情況已經改變了不少,但今天仍有令人信服的理由讓你選擇Shiro。其實理由相當多,Apache Shiro:
- 易於使用 - 易用性是這個項目的最終目標。應用安全有可能會非常讓人糊塗,令人沮喪,並被認爲是“必要之惡”【譯註:比喻應用安全方面的編程。】。若是能讓它簡化到新手都能很快上手,那它將不再是一種痛苦了。
- 廣泛性 - 沒有其他安全框架可以達到Apache Shiro宣稱的廣度,它可以爲你的安全需求提供“一站式”服務。
- 靈活性 - Apache Shiro可以工作在任何應用環境中。雖然它工作在Web、EJB和IoC環境中,但它並不依賴這些環境。Shiro既不強加任何規範,也無需過多依賴。
- Web能力 - Apache Shiro對Web應用的支持很神奇,允許你基於應用URL和Web協議(如REST)創建靈活的安全策略,同時還提供了一套控制頁面輸出的JSP標籤庫。
- 可插拔 - Shiro乾淨的API和設計模式使它可以方便地與許多的其他框架和應用進行集成。你將看到Shiro可以與諸如Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin這類第三方框架無縫集成。
核心概念:Subject,SecurityManager和Realms
Subject
Subject一詞是一個安全術語,其基本意思是“當前的操作用戶”。稱之爲“用戶”並不準確,因爲“用戶”一詞通常跟人相關。在安全領域,術語“Subject”可以是人,也可以是第三方進程、後臺帳戶(Daemon Account)或其他類似事物。它僅僅意味着“當前跟軟件交互的東西”。但考慮到大多數目的和用途,你可以把它認爲是Shiro的“用戶”概念。在代碼的任何地方,你都能輕易的獲得Shiro Subject,
SecurityManager
Subject的“幕後”推手是SecurityManager。Subject代表了當前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。它是Shiro框架的核心,充當“保護傘”,引用了多個內部嵌套安全組件,它們形成了對象圖。但是,一旦SecurityManager及其內部對象圖配置好,它就會退居幕後,應用開發人員幾乎把他們的所有時間都花在Subject API調用上。
那麼,如何設置SecurityManager呢?嗯,這要看應用的環境。例如,Web應用通常會在Web.xml中指定一個Shiro Servlet Filter,這會創建SecurityManager實例,如果你運行的是一個獨立應用,你需要用其他配置方式,但有很多配置選項。
一個應用幾乎總是隻有一個SecurityManager實例。它實際是應用的Singleton(儘管不必是一個靜態Singleton)。跟Shiro裏的幾乎所有組件一樣,SecurityManager的缺省實現是POJO,而且可用POJO兼容的任何配置機制進行配置 - 普通的Java代碼、Spring XML、YAML、.properties和.ini文件等。基本來講,能夠實例化類和調用JavaBean兼容方法的任何配置形式都可使用。
爲此,Shiro藉助基於文本的INI配置提供了一個缺省的“公共”解決方案。INI易於閱讀、使用簡單並且需要極少依賴。你還能看到,只要簡單地理解對象導航,INI可被有效地用於配置像SecurityManager那樣簡單的對象圖。注意,Shiro還支持Spring XML配置及其他方式,但這裏只我們只討論INI。
Realms
Shiro的第三個也是最後一個概念是Realm。Realm充當了Shiro與應用安全數據間的“橋樑”或者“連接器”。也就是說,當切實與像用戶帳戶這類安全相關數據進行交互,執行認證(登錄)和授權(訪問控制)時,Shiro會從應用配置的Realm中查找很多內容。
從這個意義上講,Realm實質上是一個安全相關的DAO:它封裝了數據源的連接細節,並在需要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(或)授權。配置多個Realm是可以的,但是至少需要一個。
Shiro內置了可以連接大量安全數據源(又名目錄)的Realm,如LDAP、關係數據庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果缺省的Realm不能滿足需求,你還可以插入代表自定義數據源的自己的Realm實現
shiro架構
除前面所講Subject、SecurityManager 、Realm三個核心組件外,Shiro主要組件還包括:
- Authenticator :認證就是覈實用戶身份的過程。這個過程的常見例子是大家都熟悉的“用戶/密碼”組合。多數用戶在登錄軟件系統時,通常提供自己的用戶名(當事人)和支持他們的密碼(證書)。如果存儲在系統中的密碼(或密碼錶示)與用戶提供的匹配,他們就被認爲通過認證。
- Authorizer :授權實質上就是訪問控制 - 控制用戶能夠訪問應用中的哪些內容,比如資源、Web頁面等等。
- SessionManager :在安全框架領域,Apache Shiro提供了一些獨特的東西:可在任何應用或架構層一致地使用Session API。即,Shiro爲任何應用提供了一個會話編程範式 - 從小型後臺獨立應用到大型集羣Web應用。這意味着,那些希望使用會話的應用開發者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用這些容器,開發者現在也可以選擇使用在任何層統一一致的會話API,取代Servlet或EJB機制。
- CacheManager :對Shiro的其他組件提供緩存支持。
- Cryptography 提供安全的支持
ini配置權限信息(參考http://shiro.apache.org/configuration.html)
# =======================
# Shiro INI configuration
# =======================
[main]
authc.loginUrl=/login.html
#認證(登錄失敗)不通過 跳轉到loginUrl
roles.loginUrl=/login.html
#授權(沒有某個權限) 不通過 跳轉到unauthorizedUrl
roles.unauthorizedUrl=/un.html
perms.loginUrl=/login.html
#授權(沒有某個權限) 不通過 跳轉到unauthorizedUrl
perms.unauthorizedUrl=/un.html
[users]
# 設置用戶信息
# 語法是 username = password, roleName1, roleName2, …, roleNameN
jiaozi = 123456,role1
[roles]
# 角色信息和角色擁有的權限
#語法是 rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN
#權限的語法 * 表示所有權限 一般語法是 權限類型.權限動作.權限的資源id 比如 user:delete:1 表示擁有刪
#除1號用戶的權限 user:delete:*表示刪除所有用戶權限
admin = *
role1 = user:query:*, user:delete:1
[urls]
# web中的url過濾 訪問這個頁面時 要求你登錄的賬號 必須擁有某些權限
/login.html = anon
/suc.jsp = perms[user:delete:2]
創建maven項目 導入架包
<!-- 導入shiro的架包 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
測試驗證權限過程
package cn.et;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestShiro {
public static void main(String[] args) {
//從ini中讀取權限信息構建 SecurityUtils對象
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:my.ini");
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//獲取當前的用戶
Subject currentUser = SecurityUtils.getSubject();
//當前用戶的會話
Session session = currentUser.getSession();
//判斷是否登錄 未登錄 需要登錄
/**
* 用戶包括兩部分
* principals and credentials
* principals(本人)表示用戶的標識信息 比如用戶名 用戶地址等
* credentials(憑證)表示用戶用於登錄的憑證 比如密碼 證書等
*/
if ( !currentUser.isAuthenticated() ) {
//用戶輸入的用戶名和密碼
UsernamePasswordToken token = new UsernamePasswordToken("jiaozi", "123456");
try {
currentUser.login( token );
System.out.println("登錄成功");
//是否認證通過
System.out.println(currentUser.isAuthenticated());
//判斷登錄後用戶是否有某個角色
if(currentUser.hasRole("role1")){
System.out.println("擁有role1角色");
}
//是否擁有權限
if(currentUser.isPermitted("user:delete:1")){
System.out.println("擁有與刪除1的權限");
}
} catch ( UnknownAccountException uae ) {
System.out.println("賬號錯誤");
} catch ( IncorrectCredentialsException ice ) {
System.out.println("密碼不匹配");
} catch ( LockedAccountException lae ) {
System.out.println("賬號被鎖定");
} catch ( AuthenticationException ae ) {
System.out.println("未知錯誤");
}
}
}
}
二. shiro web
集成web集成 參考 http://shiro.apache.org/web.html#programmatic-support
在之前例子基礎上進行拓展 添加war項目 添加shiro-web依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
web.xml添加shiro支持的過濾器和ini文件路徑配置參數
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>/WEB-INF/my.ini</param-value>
</context-param>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
添加幾個html和jsp用於測試
login.html添加用於登錄的表單,引入的jquery
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>嗶哩嗶哩彈幕視頻網 - ( ゜- ゜)つロ 乾杯~ - bilibili</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="this is my page">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="themes/icon.css">
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="jquery.easyui.min.js"></script>
<script type="text/javascript">
</script>
</head>
<body>
<div class="easyui-layout" style="width:100%;height:100%;">
<!-- 上北只能設置高度 一般不會設置寬度 -->
<div data-options="region:'north'" style="height:15%"></div>
<div data-options="region:'south',split:true" style="height:15%"></div>
<div data-options="region:'center'" >
<form id="ss" action="loginShiro" method="post" enctype="multipart/form-data">
<table cellpadding="5" align="center">
<tr>
<td><input class="easyui-textbox" type="text" name="userName" data-options="required:true,missingMessage:'請告訴我你的暱稱吧'"></input></td>
</tr>
<tr>
<td><input class="easyui-textbox" type="text" name="password" data-options="required:true,missingMessage:'密碼不能小於6個字符',validType:'length[6,16]'"></input></td>
</tr>
<tr>
<td><input class="text" type="submit" value="登錄 "/></td>
</tr>
</table>
</form>
</div>
</div>
</body>
</html>
成功頁面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
成功登錄
</body>
</html>
失敗頁面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
你沒有權限
</body>
</html>
servlet
package cn.et.less01.controller;
import java.io.IOException;
import javax.servlet.ServletException;
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.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
/**
* Servlet implementation class LoginController
*/
public class LoginController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public LoginController() {
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取頁面傳過來的用戶名和密碼
String username=request.getParameter("userName");
String password=request.getParameter("password");
//獲取當前的用戶
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
currentUser.login( token );
//是否認證通過
System.out.println(currentUser.isAuthenticated());
request.getRequestDispatcher("/suc.jsp").forward(request, response);
} catch ( UnknownAccountException uae ) {
System.out.println("賬號錯誤");
request.getRequestDispatcher("/login.html").forward(request, response);
} catch ( IncorrectCredentialsException ice ) {
System.out.println("密碼不匹配");
request.getRequestDispatcher("/login.html").forward(request, response);
} catch ( LockedAccountException lae ) {
System.out.println("賬號被鎖定");
} catch ( AuthenticationException ae ) {
System.out.println("未知錯誤");
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
三. shiro集成spring
添加maven的架包支持和shiro-spring
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.et</groupId>
<artifactId>Shiroless</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- 導入shiro的架包 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<!--事務的基礎 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<!-- 調用jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.12</version>
</dependency>
<!-- 文件上傳 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!-- 添加jacson的json解析庫 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<!-- 連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.5</version>
</dependency>
<!-- mybatis集成spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>javaee-api</artifactId>
<version>5.0-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>1.2_04</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>1.2_04</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
在web.xml中配置過濾器
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 解決亂碼的配置 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
======================================
spring的配置
======================================
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
=========================================
springmvc的配置
=========================================
-->
<!-- 配置springmvc支持 restful風格url
put+delete
form+hidden(_method) ?_method=put
-->
<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置springmvc的核心控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mymvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 啓用druid的監控功能 -->
<servlet>
<servlet-name>statViewServlet</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<param-name>loginUsername</param-name>
<param-value>admin</param-value>
</init-param>
<init-param>
<param-name>loginPassword</param-name>
<param-value>zmw</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>statViewServlet</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<!-- 配置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>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在web-inf下創建mymvc.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
">
<!-- springmvc控制層處理 + 視圖層 -->
<context:component-scan base-package="cn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
<mvc:default-servlet-handler/>
<!-- 上傳文件 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
<!-- 引用返回對象 響應json的消息轉換器 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/x-www-form-urlencoded</value>
</list>
</property>
</bean>
<!-- 配置返回對應解析成json的消息轉換器 -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/x-www-form-urlencoded</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
在resources中配置連接數據庫的四要素jdbc.properties文件
url=jdbc:mysql://localhost:3306/auth
driverClassName=com.mysql.jdbc.Driver
userName1=root
password=123456
配置log4j.properties文件
### set log levels ###
log4j.rootLogger = debug , stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
在resources配置spring.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
">
<!-- spring是bean的容器(service+repository)-->
<context:component-scan base-package="cn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 所有數據庫操作的源頭 實現自接口DataSouce
DriverManagerDataSource (請求產生一個連接 用完關閉 連接重用 連接池)
c3p0 dbcp druid(阿里 監控功能)
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driverClassName}"></property>
<property name="username" value="${userName1}"></property>
<property name="password" value="${password}"></property>
<!-- 默認初始化的連接個數 -->
<property name="initialSize" value="1"></property>
<!-- 最大允許的連接個數 -->
<property name="maxActive" value="200"></property>
<!-- 最大的等待人數 -->
<property name="maxIdle" value="100"></property>
<!-- 開啓sql統計功能 -->
<property name="filters" value="stat"></property>
</bean>
<!-- 集成mybatis -->
<!-- 配置session工廠 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
</bean>
<!-- 掃描mybatis的接口映射 -->
<bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.*.*.dao"></property>
</bean>
<!-- 事務管理器 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvise" transaction-manager="tm">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* cn.*..*.service.*.*(..))" id="myPointCut"/>
<!-- 關聯切點和事務管理器 -->
<aop:advisor advice-ref="txAdvise" pointcut-ref="myPointCut"/>
</aop:config>
<bean id="myFilter" class="cn.et.less01.conf.MyFilter">
</bean>
<!-- spring配置ini loginUrl沒有登錄 unauthorizedUrl沒有權限-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.html"/>
<property name="unauthorizedUrl" value="/un.html"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="myFilter"></entry>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- key的值key="authc" -->
/** = authc
</value>
</property>
</bean>
<!-- 讀取數據庫的授權數據 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myDbRealm"/>
</bean>
<!-- 後置處理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>
controller層
package cn.et.less01.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import cn.et.less01.dao.UserMapper;
@Controller
public class SpringLoginController {
@Autowired
UserMapper um;
@RequestMapping("/loginShiro")
public String Login(String userName,String password,Model model){
//獲取當前的用戶
Subject currentUser = SecurityUtils.getSubject();
//獲取頁面傳過來的用戶名和密碼
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
currentUser.login( token );
model.addAttribute("menuList", um.queryMenuByMenu(userName));
//是否認證通過
return "/save.jsp";
} catch ( UnknownAccountException uae ) {
System.out.println("賬號錯誤");
} catch ( IncorrectCredentialsException ice ) {
System.out.println("密碼不匹配");
} catch ( LockedAccountException lae ) {
System.out.println("賬號被鎖定");
} catch ( AuthenticationException ae ) {
System.out.println("未知錯誤");
}
return "/un.html";
}
}
設計數據庫
用戶表
角色表
權限表
菜單表
用戶與角色
角色與權限
權限與菜單
實體類
package cn.et.less01.entity;
public class UserInfo {
private Integer userId;
private String userName;
private String password;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
實體菜單類
package cn.et.less01.entity;
public class Menu {
private Integer menuid;
private String menuname;
private String menuurl;
private String menufitter;
private Integer ismenu;
public Integer getMenuid() {
return menuid;
}
public void setMenuid(Integer menuid) {
this.menuid = menuid;
}
public String getMenuname() {
return menuname;
}
public void setMenuname(String menuname) {
this.menuname = menuname;
}
public String getMenuurl() {
return menuurl;
}
public void setMenuurl(String menuurl) {
this.menuurl = menuurl;
}
public String getMenufitter() {
return menufitter;
}
public void setMenufitter(String menufitter) {
this.menufitter = menufitter;
}
public Integer getIsmenu() {
return ismenu;
}
public void setIsmenu(Integer ismenu) {
this.ismenu = ismenu;
}
@Override
public String toString() {
return "Menu [menuid=" + menuid + ", menuname=" + menuname + ", menuurl=" + menuurl + ", menufitter="
+ menufitter + ", ismenu=" + ismenu + "]";
}
}
業務邏輯層
package cn.et.less01.dao;
import java.util.List;
import java.util.Set;
import org.apache.ibatis.annotations.Select;
import cn.et.less01.entity.Menu;
import cn.et.less01.entity.UserInfo;
public interface UserMapper {
//查看用戶
@Select("select user_name as userName,pass_word as password from user_info where user_name=#{0}")
public UserInfo queryUser(String userName);
//查看角色
@Select("SELECT r.role_name FROM user_info u INNER JOIN user_role_relation urr ON u.user_id =urr.user_id "
+ " INNER JOIN role r ON r.role_id= urr.role_id "
+ " WHERE user_name=#{0}")
public Set<String> queryRoleByName(String userName);
//查看權限
@Select("SELECT p.pems_tag FROM user_info u INNER JOIN user_role_relation urr ON u.user_id =urr.user_id "
+ " INNER JOIN role r ON r.role_id= urr.role_id "
+ " INNER JOIN role_pems_relation rpr ON rpr.role_id=r.role_id "
+ " INNER JOIN pems p ON rpr.pems_id=p.pems_id "
+ " WHERE user_name=#{0}")
public Set<String> queryPemsByName(String userName);
//查看所有的菜單
@Select("select menu_id as menuid,menu_name as menuname,menu_url as menuurl,menu_fitter as menufitter,is_menu as ismenu from menu")
public List<Menu> queryMenu();
//通過路徑查看是否有權限
@Select("select menu_id as menuid,menu_name as menuname,menu_url as menuurl,menu_fitter as menufitter,is_menu as ismenu from menu where menu_url=#{0} ")
public List<Menu> queryMenuByurl(String url);
//通過用戶看用戶所屬的菜單
@Select("SELECT menu_name as menuname,menu_url as menuurl,menu_fitter as menufitter,is_menu as ismenu FROM menu m INNER JOIN user_menu_relation umr ON"
+ " umr.menu_id=m.menu_id "
+ "INNER JOIN user_info u ON u.user_id = umr.user_id "
+ "WHERE u.user_name =#{0} ")
public List<Menu> queryMenuByMenu(String userName);
}
自定義過濾規則
package cn.et.less01.conf;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.et.less01.dao.UserMapper;
import cn.et.less01.entity.Menu;
@Component
public class MyFilter extends AuthorizationFilter {
@Autowired
private ShiroFilterFactoryBean sffb;
/**
* 匹配指定過濾器規則的url
* @param regex
* @param url
* @return
*/
public static boolean matchUrl(String regex,String url){
regex=regex.replaceAll("/+", "/");
if(regex.equals(url)){
return true;
}
regex=regex.replaceAll("\\.", "\\\\.");
// /login.html /l*.html
regex=regex.replaceAll("\\*", ".*");
// /**/login.html /a/b/login.html
if(regex.indexOf("/.*.*/")>=0){
regex=regex.replaceAll("/\\.\\*\\.\\*/", "((/.*/)+|/)");
}
System.out.println(regex+"----"+url);
return Pattern.matches(regex, url);
}
@Autowired
UserMapper um;
/**
* 測試
* @param args
*/
public static void main(String[] args) {
System.out.println(matchUrl("/**/s*.html","/t/g/login.html"));
}
/**
* 在map中模擬 這個也可以將來定義在數據庫中
*/
/**
* isAccessAllowed用於判斷當前url的請求是否能驗證通過 如果驗證失敗 調用父類的onAccessDenied決定跳轉到登錄失敗頁還是授權失敗頁面
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
HttpServletRequest req=(HttpServletRequest)request;
String contextPath=req.getContextPath();
//獲取用戶訪問的資源的路徑
String url=req.getRequestURI();
url=url.split(contextPath)[1];
//獲取所有的菜單
List<Menu> qm= um.queryMenu();
//數據庫沒有配置當前URL的授權
if(qm.size()==0){
return false;
}
String urlAuth =null;
//獲取那些URL需要哪些認證
// List<Menu> q=um.queryMenuByurl(url);
for(Menu mu:qm){
if(matchUrl(mu.getMenuurl(),url)){
urlAuth=mu.getMenufitter();
}
}
//如果有授權就獲取權限有哪些
if(urlAuth==null){
return false;
}
//配置的過濾器是anon 直接放過
if(urlAuth.startsWith("anon")){
return true;
}
//配置的是authc 判斷當前用戶是否認證通過
Subject subject = getSubject(request, response);
if(urlAuth.startsWith("authc")){
return subject.isAuthenticated();
}
//授權認證 也需要判斷是否登錄 沒有登錄返回 登錄繼續下面的驗證
boolean ifAuthc=subject.isAuthenticated();
if(!ifAuthc)
return ifAuthc;
//如果是定義的roles過濾器 獲取所有的roles 一般是roles[a,b]
if(urlAuth.startsWith("roles")){
String[] rolesArray=urlAuth.split("roles\\[")[1].split("\\]")[0].split(",");
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
return subject.hasAllRoles(roles);
}
if(urlAuth.startsWith("perms")){
String[] perms=urlAuth.split("perms\\[")[1].split("\\]")[0].split(",");
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
if (!subject.isPermitted(perms[0])) {
isPermitted = false;
}
} else {
if (!subject.isPermittedAll(perms)) {
isPermitted = false;
}
}
}
return isPermitted;
}
return false;
}
}
自定義realm
package cn.et.less01.conf;
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.SimpleAccount;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.et.less01.dao.UserMapper;
import cn.et.less01.entity.UserInfo;
@Component
public class MyDbRealm extends AuthorizingRealm{
@Autowired
UserMapper um;
/**
* 獲取當前用戶的授權數據
* 將當前用戶在數據庫的權限加載到AuthorizationInfo
* 默認 在進行授權認證時調用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//獲取用戶名
String userName=principals.getPrimaryPrincipal().toString();
System.out.println(userName);
//查看角色
Set<String> roleList=um.queryRoleByName(userName);
//查看權限
Set<String> pemsList=um.queryPemsByName(userName);
//角色和權限集合對象
SimpleAuthorizationInfo at=new SimpleAuthorizationInfo();
//設置用戶的角色
at.setRoles(roleList);
//設置用戶的權限
at.setStringPermissions(pemsList);
return at;
}
/**
* 認證
* 將登陸輸入的用戶名和密碼和數據庫中的用戶名個密碼對比是否相等
* 返回null認證失敗 非null成功
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//獲取頁面傳進來的用戶和密碼
UsernamePasswordToken upt=(UsernamePasswordToken)token;
//查詢數據庫是否有這個賬號
System.out.println(upt.getPassword());
System.out.println(token.getPrincipal());
//查詢數據的用戶和密碼
UserInfo queryUs = um.queryUser(token.getPrincipal().toString());
//判斷賬號密碼是否一致
if(queryUs!=null && queryUs.getPassword().equals(new String(upt.getPassword()))){
//登錄成功
SimpleAccount sa = new SimpleAccount(upt.getUsername(),upt.getPassword(),"MyDbRealm");
return sa;
}
return null;
}
}