一、Shiro 簡介
1、關於 Shiro
- Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
- 官網:https://shiro.apache.org/
2、Shiro 主要功能
- Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
- Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
- Session Manager:會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通 JavaSE 環境的,也可以是如 Web 環境的;
- Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
- Web Support:Web 支持,可以非常容易的集成到 Web 環境;
- Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄
3、Shiro 的工作原理
-
Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是 Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有 Subject 都綁定到 SecurityManager,與 Subject 的所有交互都會委託給 SecurityManager;可以把 Subject 認爲是一個門面;SecurityManager 纔是實際的執行者;
-
SecurityManager:安全管理器;即所有與安全有關的操作都會與 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它負責與後邊介紹的其他組件進行交互,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
-
Realm:域 Shiro 從 Realm 獲取安全數據(如用戶、 角色、 權限) ,就是說 SecurityManager要驗證用戶身份, 那麼它需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法; 也需要從 Realm 得到用戶相應的角色/權限進行驗證用戶是否能進行操作; 可以把 Realm 看成 DataSource,即安全數據,可以理解成Dao組件訪問權限數據的組件
二、Shiro 入門案例(整合web項目)
第一步:pom.xml 引入maven包
- shiro-core
- shiro-web
- commons-logging(日誌記錄)
- mysql-connector-java
- druid
- javax.servlet-api
第二步:web.xml配置(在main/java/webapp/WEB-INF/web.xml)
<?xml version="1.0" encoding="UTF-8"?>
<web-app
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_3_0.xsd"
version="3.0"
metadata-complete="false">
<!--- shiro 1.2 -->
<!-- 加載shiro配置文件 -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默認先從/WEB-INF/shiro.ini,如果沒有找classpath:shiro.ini -->
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro-formfilter.ini</param-value>
</context-param>
<!-- 配置shiro的過濾器 -->
<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>
</filter-mapping>
</web-app>
第三步:shiro.ini
[main]
authc.loginUrl=/login.jsp
roles.unauthorizedUrl=/unauthorized.jsp
perms.unauthorizedUrl=/unauthorized.jsp
[users]
zhang=123,admin
wang=123
[roles]
admin=user:*,menu:*
[urls]
/login=anon
/logout2=logout
/role.jsp=roles[admin]
/permission.jsp=perms[user:create]
/**=user
在上面的代碼中:
第四步:登錄 LoginServlet.java
package cn.lemon.shiro.web.servlet;
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 javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "loginServlet", urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String error = null;
String username = req.getParameter("username");
String password = req.getParameter("password");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);
try {
subject.login(token);/*根據用戶名密碼令牌,判斷和shiro.ini中的zhangsan=123,wang=123是否匹配*/
} 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 {//登錄成功
req.getSession().setAttribute("subject", subject);
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
}
}
第五步:login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄</title>
<style>.error{color:red;}</style>
</head>
<body>
<h5>login.jsp</h5>
<div class="error">${error}</div>
<form action="${pageContext.request.contextPath}/login" method="post">
用戶名:<input type="text" name="username"><br/>
密碼:<input type="password" name="password"><br/>
<input type="submit" value="登錄">
</form>
</body>
</html>
第六步:註銷 LogoutServlet.java
package cn.lemon.shiro.web.servlet;
import org.apache.shiro.SecurityUtils;
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 java.io.IOException;
@WebServlet(name = "logoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
SecurityUtils.getSubject().logout();
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
第七步:shiro-formfilter.ini
[main]
authc.loginUrl=/formfilterlogin
roles.loginUrl=/formfilterlogin
perms.loginUrl=/formfilterlogin
roles.unauthorizedUrl=/unauthorized.jsp
perms.unauthorizedUrl=/unauthorized.jsp
[users]
zhang=123,admin
wang=123
[roles]
admin=user:*,menu:*
[urls]
/formfilterlogin=authc
/login=anon
/logout2=logout
/role.jsp=roles[admin]
/permission.jsp=perms[user:create]
/**=authc