Shiro 提供了與 Web 集成的支持,其通過(guò)一個(gè) ShiroFilter 入口來(lái)攔截需要安全控制的 URL,然后進(jìn)行相應(yīng)的控制,ShiroFilter 類似于如 Struts2/SpringMVC 這種 web 框架的前端控制器,其是安全控制的入口點(diǎn),其負(fù)責(zé)讀取配置(如 ini 配置文件),然后判斷 URL 是否需要登錄 / 權(quán)限等工作。
1、創(chuàng)建 webapp 應(yīng)用
此處我們使用了 jetty-maven-plugin 和 tomcat7-maven-plugin 插件;這樣可以直接使用 “mvn jetty:run” 或“mvn tomcat7:run”直接運(yùn)行 webapp 了。然后通過(guò) URLhttp://localhost:8080/chapter7 / 訪問(wèn)即可。
2、依賴
Servlet3
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
Servlet3 的知識(shí)可以參考 https://github.com/zhangkaitao/servlet3-showcase 及 Servlet3 規(guī)范 http://www.iteye.com/blogs/subjects/Servlet-3-1。
shiro-web
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.2</version>
</dependency>
其他依賴請(qǐng)參考源碼的 pom.xml。
1、Shiro 1.1 及以前版本配置方式
<filter>
<filter-name>iniShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
<init-param>
<param-name>configPath</param-name>
<param-value>classpath:shiro.ini</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>iniShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<init-param>
<param-name>config</param-name>
<param-value>
ini配置文件貼在這
</param-value>
</init-param>
2、Shiro 1.2 及以后版本的配置方式
從 Shiro 1.2 開始引入了 Environment/WebEnvironment 的概念,即由它們的實(shí)現(xiàn)提供相應(yīng)的 SecurityManager 及其相應(yīng)的依賴。ShiroFilter 會(huì)自動(dòng)找到 Environment 然后獲取相應(yīng)的依賴。
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
通過(guò) EnvironmentLoaderListener 來(lái)創(chuàng)建相應(yīng)的 WebEnvironment,并自動(dòng)綁定到 ServletContext,默認(rèn)使用 IniWebEnvironment 實(shí)現(xiàn)。
可以通過(guò)如下配置修改默認(rèn)實(shí)現(xiàn)及其加載的配置文件位置:
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro.ini</param-value>
</context-param>
shiroConfigLocations 默認(rèn)是 “/WEB-INF/shiro.ini”,IniWebEnvironment 默認(rèn)是先從 / WEB-INF/shiro.ini 加載,如果沒(méi)有就默認(rèn)加載 classpath:shiro.ini。
3、與 Spring 集成
<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>
DelegatingFilterProxy 作用是自動(dòng)到 spring 容器查找名字為 shiroFilter(filter-name)的 bean 并把所有 Filter 的操作委托給它。然后將 ShiroFilter 配置到 spring 容器即可:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!—忽略其他,詳見(jiàn)與Spring集成部分 -->
</bean>
最后不要忘了使用 org.springframework.web.context.ContextLoaderListener 加載這個(gè) spring 配置文件即可。因?yàn)槲覀儸F(xiàn)在的 shiro 版本是 1.2 的,因此之后的測(cè)試都是使用 1.2 的配置。
ini 配置部分和之前的相比將多出對(duì) url 部分的配置。
[main]
\#默認(rèn)是/login.jsp
authc.loginUrl=/login
roles.unauthorizedUrl=/unauthorized
perms.unauthorizedUrl=/unauthorized
[users]
zhang=123,admin
wang=123
[roles]
admin=user:*,menu:*
[urls]
/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
其中最重要的就是 [urls] 部分的配置,其格式是: “url = 攔截器 [參數(shù)],攔截器[參數(shù)]”;即如果當(dāng)前請(qǐng)求的 url 匹配[urls] 部分的某個(gè) url 模式,將會(huì)執(zhí)行其配置的攔截器。比如 anon 攔截器表示匿名訪問(wèn)(即不需要登錄即可訪問(wèn));authc 攔截器表示需要身份認(rèn)證通過(guò)后才能訪問(wèn);roles[admin]攔截器表示需要有 admin 角色授權(quán)才能訪問(wèn);而 perms["user:create"]攔截器表示需要有 “user:create” 權(quán)限才能訪問(wèn)。
url 模式使用 Ant 風(fēng)格模式
Ant 路徑通配符支持?、、,注意通配符匹配不包括目錄分隔符 “/”:
?:匹配一個(gè)字符,如”/admin?” 將匹配 / admin1、admin2,但不匹配 / admin 或 / admin123;
*:匹配零個(gè)或多個(gè)字符串,如 / admin * 將匹配 / admin、/admin123,但不匹配 / admin/1;
**:匹配路徑中的零個(gè)或多個(gè)路徑,如 / admin/** 將匹配 / admin/a 或 / admin/a/b。
url 模式匹配順序
url 模式匹配順序是按照在配置中的聲明順序匹配,即從頭開始使用第一個(gè)匹配的 url 模式對(duì)應(yīng)的攔截器鏈。如:
/bb/**=filter1
/bb/aa=filter2
/**=filter3
如果請(qǐng)求的 url 是 “/bb/aa”,因?yàn)榘凑章暶黜樞蜻M(jìn)行匹配,那么將使用 filter1 進(jìn)行攔截。
攔截器將在下一節(jié)詳細(xì)介紹。接著我們來(lái)看看身份驗(yàn)證、授權(quán)及退出在 web 中如何實(shí)現(xiàn)。
首先配置需要身份驗(yàn)證的 url
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
即訪問(wèn)這些地址時(shí)會(huì)首先判斷用戶有沒(méi)有登錄,如果沒(méi)有登錄默會(huì)跳轉(zhuǎn)到登錄頁(yè)面,默認(rèn)是 / login.jsp,可以通過(guò)在 [main] 部分通過(guò)如下配置修改:
authc.loginUrl=/login
登錄 Servlet(com.github.zhangkaitao.shiro.chapter7.web.servlet.LoginServlet)
@WebServlet(name = "loginServlet", urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp);
}
@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);
try {
subject.login(token);
} catch (UnknownAccountException e) {
error = "用戶名/密碼錯(cuò)誤";
} catch (IncorrectCredentialsException e) {
error = "用戶名/密碼錯(cuò)誤";
} catch (AuthenticationException e) {
//其他錯(cuò)誤,比如鎖定,如果想單獨(dú)處理請(qǐng)單獨(dú)catch處理
error = "其他錯(cuò)誤:" + e.getMessage();
}
if(error != null) {//出錯(cuò)了,返回登錄頁(yè)面
req.setAttribute("error", error);
req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp);
} else {//登錄成功
req.getRequestDispatcher("/WEB-INF/jsp/loginSuccess.jsp").forward(req, resp);
}
}
}
測(cè)試
首先輸入 http://localhost:8080/chapter7/login 進(jìn)行登錄,登錄成功后接著可以訪問(wèn) http://localhost:8080/chapter7/authenticated 來(lái)顯示當(dāng)前登錄的用戶:
${subject.principal} 身份驗(yàn)證已通過(guò)。
當(dāng)前實(shí)現(xiàn)的一個(gè)缺點(diǎn)就是,永遠(yuǎn)返回到同一個(gè)成功頁(yè)面(比如首頁(yè)),在實(shí)際項(xiàng)目中比如支付時(shí)如果沒(méi)有登錄將跳轉(zhuǎn)到登錄頁(yè)面,登錄成功后再跳回到支付頁(yè)面;對(duì)于這種功能大家可以在登錄時(shí)把當(dāng)前請(qǐng)求保存下來(lái),然后登錄成功后再重定向到該請(qǐng)求即可。
Shiro 內(nèi)置了登錄(身份驗(yàn)證)的實(shí)現(xiàn):基于表單的和基于 Basic 的驗(yàn)證,其通過(guò)攔截器實(shí)現(xiàn)。
shiro-basicfilterlogin.ini 配置
[main]
authcBasic.applicationName=please login
………省略u(píng)sers
[urls]
/role=authcBasic,roles[admin]
1、authcBasic 是 org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 類型的實(shí)例,其用于實(shí)現(xiàn)基于 Basic 的身份驗(yàn)證;applicationName 用于彈出的登錄框顯示信息使用,如圖:
2、[urls] 部分配置了 /role 地址需要走 authcBasic 攔截器,即如果訪問(wèn) /role 時(shí)還沒(méi)有通過(guò)身份驗(yàn)證那么將彈出如上圖的對(duì)話框進(jìn)行登錄,登錄成功即可訪問(wèn)。
web.xml
把 shiroConfigLocations 改為 shiro-basicfilterlogin.ini 即可。
測(cè)試
輸入 http://localhost:8080/chapter7/role,會(huì)彈出之前的 Basic 驗(yàn)證對(duì)話框輸入 “zhang/123” 即可登錄成功進(jìn)行訪問(wèn)。
基于表單的攔截器身份驗(yàn)證和【1】類似,但是更簡(jiǎn)單,因?yàn)槠湟呀?jīng)實(shí)現(xiàn)了大部分登錄邏輯;我們只需要指定:登錄地址 / 登錄失敗后錯(cuò)誤信息存哪 / 成功的地址即可。
shiro-formfilterlogin.ini
[main]
authc.loginUrl=/formfilterlogin
authc.usernameParam=username
authc.passwordParam=password
authc.successUrl=/
authc.failureKeyAttribute=shiroLoginFailure
[urls]
/role=authc,roles[admin]
1、authc 是 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 類型的實(shí)例,其用于實(shí)現(xiàn)基于表單的身份驗(yàn)證;通過(guò) loginUrl 指定當(dāng)身份驗(yàn)證時(shí)的登錄表單;usernameParam 指定登錄表單提交的用戶名參數(shù)名;passwordParam 指定登錄表單提交的密碼參數(shù)名;successUrl 指定登錄成功后重定向的默認(rèn)地址(默認(rèn)是 “/”)(如果有上一個(gè)地址會(huì)自動(dòng)重定向帶該地址);failureKeyAttribute 指定登錄失敗時(shí)的 request 屬性 key(默認(rèn) shiroLoginFailure);這樣可以在登錄表單得到該錯(cuò)誤 key 顯示相應(yīng)的錯(cuò)誤消息;
web.xml
把 shiroConfigLocations 改為 shiro-formfilterlogin.ini 即可。
登錄 Servlet
@WebServlet(name = "formFilterLoginServlet", urlPatterns = "/formfilterlogin")
public class FormFilterLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String errorClassName = (String)req.getAttribute("shiroLoginFailure");
if(UnknownAccountException.class.getName().equals(errorClassName)) {
req.setAttribute("error", "用戶名/密碼錯(cuò)誤");
} else if(IncorrectCredentialsException.class.getName().equals(errorClassName)) {
req.setAttribute("error", "用戶名/密碼錯(cuò)誤");
} else if(errorClassName != null) {
req.setAttribute("error", "未知錯(cuò)誤:" + errorClassName);
}
req.getRequestDispatcher("/WEB-INF/jsp/formfilterlogin.jsp").forward(req, resp);
}
}
在登錄 Servlet 中通過(guò) shiroLoginFailure 得到 authc 登錄失敗時(shí)的異常類型名,然后根據(jù)此異常名來(lái)決定顯示什么錯(cuò)誤消息。
測(cè)試
輸入 http://localhost:8080/chapter7/role
,會(huì)跳轉(zhuǎn)到 “/formfilterlogin” 登錄表單,提交表單如果 authc 攔截器登錄成功后,會(huì)直接重定向會(huì)之前的地址 “/role”;假設(shè)我們直接訪問(wèn) “/formfilterlogin” 的話登錄成功將直接到默認(rèn)的 successUrl。
shiro.ini
[main]
roles.unauthorizedUrl=/unauthorized
perms.unauthorizedUrl=/unauthorized
[urls]
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
通過(guò) unauthorizedUrl 屬性指定如果授權(quán)失敗時(shí)重定向到的地址。roles 是 org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 類型的實(shí)例,通過(guò)參數(shù)指定訪問(wèn)時(shí)需要的角色,如 “[admin]”,如果有多個(gè)使用 “,” 分割,且驗(yàn)證時(shí)是 hasAllRole 驗(yàn)證,即且的關(guān)系。Perms 是 org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 類型的實(shí)例,和 roles 類似,只是驗(yàn)證權(quán)限字符串。
web.xml
把 shiroConfigLocations 改為 shiro.ini 即可。
RoleServlet/PermissionServlet
@WebServlet(name = "permissionServlet", urlPatterns = "/permission")
public class PermissionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Subject subject = SecurityUtils.getSubject();
subject.checkPermission("user:create");
req.getRequestDispatcher("/WEB-INF/jsp/hasPermission.jsp").forward(req, resp);
}
}
@WebServlet(name = "roleServlet", urlPatterns = "/role")
public class RoleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Subject subject = SecurityUtils.getSubject();
subject.checkRole("admin");
req.getRequestDispatcher("/WEB-INF/jsp/hasRole.jsp").forward(req, resp);
}
}
測(cè)試
首先訪問(wèn) http://localhost:8080/chapter7/login
,使用帳號(hào) “zhang/123” 進(jìn)行登錄,再訪問(wèn) /role 或 /permission 時(shí)會(huì)跳轉(zhuǎn)到成功頁(yè)面(因?yàn)槠涫跈?quán)成功了);如果使用帳號(hào) “wang/123” 登錄成功后訪問(wèn)這兩個(gè)地址會(huì)跳轉(zhuǎn)到 “/unauthorized” 即沒(méi)有授權(quán)頁(yè)面。
shiro.ini
[urls]
/logout=anon
指定 /logout 使用 anon 攔截器即可,即不需要登錄即可訪問(wèn)。
LogoutServlet
@WebServlet(name = "logoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
SecurityUtils.getSubject().logout();
req.getRequestDispatcher("/WEB-INF/jsp/logoutSuccess.jsp").forward(req, resp);
}
}
直接調(diào)用 Subject.logout 即可,退出成功后轉(zhuǎn)發(fā) / 重定向到相應(yīng)頁(yè)面即可。
測(cè)試
首先訪問(wèn) http://localhost:8080/chapter7/login
,使用帳號(hào) “zhang/123” 進(jìn)行登錄,登錄成功后訪問(wèn) /logout 即可退出。
Shiro 也提供了 logout 攔截器用于退出,其是 org.apache.shiro.web.filter.authc.LogoutFilter 類型的實(shí)例,我們可以在 shiro.ini 配置文件中通過(guò)如下配置完成退出:
[main]
logout.redirectUrl=/login
[urls]
/logout2=logout
通過(guò) logout.redirectUrl 指定退出后重定向的地址;通過(guò) /logout2=logout 指定退出 url 是 /logout2。這樣當(dāng)我們登錄成功后然后訪問(wèn) /logout2 即可退出。
更多建議: