尊重原創:http://blog.csdn.net/donggua3694857/article/details/52157313
最近在學習Shiro,首先非常感謝開濤大神的《跟我學Shiro》系列,在我學習的過程中發揮了很大的指導作用。學習一個新的東西首先就是做一個demo,多看不如多敲,只有在實踐中才能發現自己的欠缺,下面記錄下來我整合shiro的過程。如果有不足之處,還望各位看官多多指出。
一、基本名詞解釋
Apache Shiro是一個強大易用的Java安全框架。它可以幫助我們完成:認證、授權、加密、會話管理、與Web集成、緩存、單點登錄等等,而且它的API也十分簡潔易用,所以現在有很多人都在使用它。它的基本能功能點如圖所示:
從圖上我們可以看出Shiro的四大核心功能:
Authentication(身份驗證):簡稱為“登錄”,即證明用戶是誰。
Authorization(授權):訪問控制的過程,即決定是否有權限去訪問受保護的資源。
Session Management(會話管理):管理用戶特定的會話,即使在非 Web 或 EJB 應用程序。
Cryptography(加密):通過使用加密算法保持數據安全
我們同時也可以看看Shiro的架構長什么樣子:
同樣的虛線框框圈着的是Shiro3大核心組件:
Subject :正與系統進行交互的人,或某一個第三方服務。所有 Subject 實例都被綁定到(且這是必須的)一個SecurityManager 上。
SecurityManager:Shiro 架構的心臟,用來協調內部各安全組件,管理內部組件實例,並通過它來提供安全管理的各種服務。當 Shiro 與一個 Subject 進行交互時,實質上是幕后的 SecurityManager 處理所有繁重的 Subject 安全操作。
Realms :本質上是一個特定安全的 DAO。當配置 Shiro 時,必須指定至少一個 Realm 用來進行身份驗證和/或授權。Shiro 提供了多種可用的 Realms 來獲取安全相關的數據。如關系數據庫(JDBC),INI 及屬性文件等。可以定義自己 Realm 實現來代表自定義的數據源。
以上是些基本的名稱解釋。如需要查看更詳細的請參考開濤大神的博客。
二、准備工作
整合程序沿用之前的例子Maven+spring+Spring MVC+MyBatis+MySQL整合SSM框架,現在我們需要在此基礎上繼續整合進Shiro。
三、開始整合
1.加入jar包
整合demo用的是maven對依賴進行管理。我們需要在pom.xml里加上配置:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.1.32</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-core</artifactId>
- <version>1.2.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-web</artifactId>
- <version>1.2.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring</artifactId>
- <version>1.2.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-ehcache</artifactId>
- <version>1.2.2</version>
- </dependency>
里面我加入了阿里的fastjson。我用fastjson替換了jackson(理由:沒有什么理由,就是喜歡用了.....)
2.Spring-mvc.xml的配置
這是替換掉jackson的配置
- <mvc:annotation-driven>
- <mvc:message-converters>
- <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
- <bean class="org.springframework.http.converter.FormHttpMessageConverter" />
- <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
- <!--<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />-->
- <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" />
- </mvc:message-converters>
- </mvc:annotation-driven>
- <mvc:default-servlet-handler />
- <!-- 避免IE執行AJAX時,返回JSON出現下載文件 -->
- <!-- 支持JSON數據格式 -->
- <bean
- class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
- <property name="messageConverters">
- <list>
- <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/><!-- 解析導出文件byte流 -->
- <ref bean="fastJsonHttpMessageConverter" />
- <!--
- <ref bean="mappingJacksonHttpMessageConverter" />
- -->
- </list>
- </property>
- </bean>
- <!--<bean id="mappingJacksonHttpMessageConverter"
- class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
- </bean>-->
- <!-- 使用fastJson來支持JSON數據格式 -->
- <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
- <property name="supportedMediaTypes">
- <list>
- <value>text/html;charset=UTF-8</value>
- <value>application/json</value>
- </list>
- </property>
- <property name="features">
- <list>
- <value>WriteMapNullValue</value>
- <value>QuoteFieldNames</value>
- </list>
- </property>
- </bean>
- <!-- 配置 Shiro 的 Filter -->
- <filter>
- <description>shiro 權限攔截</description>
- <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>
- <?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.1.xsd">
- <!-- 啟用shrio授權注解攔截方式 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <!-- 裝配 securityManager -->
- <property name="securityManager" ref="securityManager"/>
- <!-- 配置登陸頁面 -->
- <property name="loginUrl" value="/index.jsp"/>
- <!-- 登陸成功后的一面 -->
- <property name="successUrl" value="/jsp/success.jsp"/>
- <span style="white-space:pre"> </span><property name="unauthorizedUrl" value="/jsp/unauthorized.jsp"/>
- <!-- 具體配置需要攔截哪些 URL, 以及訪問對應的 URL 時使用 Shiro 的什么 Filter 進行攔截. -->
- <property name="filterChainDefinitions">
- <value>
- /index.jsp=anon
- /jsp/success.jsp=anon
- /jsp/fail.jsp=anon
- /jsp/user.jsp = roles[user]
- /jsp/admin.jsp = roles[admin]
- /logout = logout
- </value>
- </property>
- </bean>
- <!-- 配置緩存管理器 -->
- <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
- <!-- 指定 ehcache 的配置文件 -->
- <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
- </bean>
- <!-- 配置進行授權和認證的 Realm -->
- <bean id="myRealm" class="com.gray.base.shiro.ShiroDbRealm">
- <property name="userService" ref="userService" />
- </bean>
- <bean id="userService" class="com.gray.user.service.impl.UserServiceImpl" />
- <!-- 配置 Shiro 的 SecurityManager Bean. -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <property name="cacheManager" ref="cacheManager"/>
- <property name="realm" ref="myRealm"/>
- <property name="sessionMode" value="native">
- </property>
- </bean>
- <!-- 配置 Bean 后置處理器: 會自動的調用和 Spring 整合后各個組件的生命周期方法. -->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
- </beans>

- <%@ page language="java" contentType="text/html; charset=utf-8"pageEncoding="utf-8"%>
- <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
- <!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=utf-8">
- <title></title>
- </head>
- <body>
- ${successMsg } Welcome! <shiro:principal/>
- <br><br>
- <shiro:hasAnyRoles name="user">
- <a href="/jsp/user.jsp">User Page</a>
- </shiro:hasAnyRoles>
- <br><br>
- <shiro:hasAnyRoles name="admin">
- <a href="/jsp/admin.jsp">Admin Page</a>
- </shiro:hasAnyRoles>
- <br><br>
- <a href="../test/logout.do">Logout</a>
- </body>
- </html>
6.1 Controller層的編寫
這次整合我么主要要實現登陸和登出功能,因此controller層我們這樣寫:
- package com.gray.user.controller;
- import java.io.IOException;
- 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.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 com.gray.user.entity.User;
- import com.gray.user.service.impl.UserServiceImpl;
- @Controller
- @RequestMapping("/test")
- public class LoginController {
- @Autowired
- private UserServiceImpl userService;
- @RequestMapping("/dologin.do") //url
- public String dologin(User user, Model model){
- String info = loginUser(user);
- if (!"SUCC".equals(info)) {
- model.addAttribute("failMsg", "用戶不存在或密碼錯誤!");
- return "/jsp/fail";
- }else{
- model.addAttribute("successMsg", "登陸成功!");//返回到頁面說夾帶的參數
- model.addAttribute("name", user.getUsername());
- return "/jsp/success";//返回的頁面
- }
- }
- @RequestMapping("/logout.do")
- public void logout(HttpServletRequest request,HttpServletResponse response) throws IOException{
- Subject subject = SecurityUtils.getSubject();
- if (subject != null) {
- try{
- subject.logout();
- }catch(Exception ex){
- }
- }
- response.sendRedirect("/index.jsp");
- }
- private String loginUser(User user) {
- if (isRelogin(user)) return "SUCC"; // 如果已經登陸,無需重新登錄
- return shiroLogin(user); // 調用shiro的登陸驗證
- }
- private String shiroLogin(User user) {
- // 組裝token,包括客戶公司名稱、簡稱、客戶編號、用戶名稱;密碼
- UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword().toCharArray(), null);
- token.setRememberMe(true);
- // shiro登陸驗證
- try {
- SecurityUtils.getSubject().login(token);
- } catch (UnknownAccountException ex) {
- return "用戶不存在或者密碼錯誤!";
- } catch (IncorrectCredentialsException ex) {
- return "用戶不存在或者密碼錯誤!";
- } catch (AuthenticationException ex) {
- return ex.getMessage(); // 自定義報錯信息
- } catch (Exception ex) {
- ex.printStackTrace();
- return "內部錯誤,請重試!";
- }
- return "SUCC";
- }
- private boolean isRelogin(User user) {
- Subject us = SecurityUtils.getSubject();
- if (us.isAuthenticated()) {
- return true; // 參數未改變,無需重新登錄,默認為已經登錄成功
- }
- return false; // 需要重新登陸
- }
- }
shiro從Realm獲取安全數據,也就是說SecurityManager要驗證身份,它需要從Realm獲取相應的用戶進行比較以確定用戶的身份是否合法;我們可以把Realm看作是DataSource,安全數據源。實現自定義Realm主要是繼承AuthrizingRealm這個父類,重寫doGetAuthrizationInfo和doGetAuthenticationInfo這兩個方法,其中doGetAuthenticationInfo是用來驗證用戶合法性的,根據輸入的用戶信息從數據庫中查出用戶,根據用戶情況拋出不同的異常。doGetAuthrizationInfo是對當前用的用戶進行授權的。在這里我把角色寫死了。在實際項目開發中應該是在表中做權限和角色的定義再從數據庫中查出一個集合然后迭代授權。具體實現代碼如下:
- package com.gray.base.shiro;
- import org.apache.shiro.SecurityUtils;
- 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.session.Session;
- import org.apache.shiro.subject.PrincipalCollection;
- import org.springframework.beans.factory.annotation.Autowired;
- import com.gray.user.entity.User;
- import com.gray.user.service.impl.UserServiceImpl;
- public class ShiroDbRealm extends AuthorizingRealm {
- @Autowired
- private UserServiceImpl userService;
- public static final String SESSION_USER_KEY = "gray";
- /**
- * 授權查詢回調函數, 進行鑒權但緩存中無用戶的授權信息時調用,負責在應用程序中決定用戶的訪問控制的方法
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
- User user = (User) SecurityUtils.getSubject().getSession().getAttribute(ShiroDbRealm.SESSION_USER_KEY);
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
- info.addRole(user.getRole().trim());
- return info;
- }
- /**
- * 認證回調函數,登錄信息和用戶驗證信息驗證
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken authcToken) throws AuthenticationException {
- // 把token轉換成User對象
- User userLogin = tokenToUser((UsernamePasswordToken) authcToken);
- // 驗證用戶是否可以登錄
- User ui = userService.doUserLogin(userLogin);
- if(ui == null)
- return null; // 異常處理,找不到數據
- // 設置session
- Session session = SecurityUtils.getSubject().getSession();
- session.setAttribute(ShiroDbRealm.SESSION_USER_KEY, ui);
- //當前 Realm 的 name
- String realmName = this.getName();
- //登陸的主要信息: 可以是一個實體類的對象, 但該實體類的對象一定是根據 token 的 username 查詢得到的.
- // Object principal = ui.getUsername();
- Object principal = authcToken.getPrincipal();
- return new SimpleAuthenticationInfo(principal, userLogin.getPassword(), realmName);
- }
- private User tokenToUser(UsernamePasswordToken authcToken) {
- User user = new User();
- user.setUsername(authcToken.getUsername());
- user.setPassword(String.valueOf(authcToken.getPassword()));
- return user;
- }
- //一定要寫getset方法
- public UserServiceImpl getUserService() {
- return userService;
- }
- public void setUserService(UserServiceImpl userService) {
- this.userService = userService;
- }
- }
具體的serviceImpl的實現我就不寫出來了,數據庫的查詢我相信大家都會(默認你們都會)。隨后如果有需要我會把整合代碼放上來。
最后貼幾張測試圖:
登陸界面:

以管理員角色的賬號登陸:

AdminPage:

以普通用戶角色的賬號登陸:

UserPage:

當你以普通用戶登錄卻想訪問adminpage時:

登陸失敗:

最后登出點擊logout返回登陸主界面。
以上就是整合Shiro的所有過程,當然在實際項目中不會那么簡單但我覺得原理也是差不多的。學無止境,最后再次感謝網上提供資料的各位大神。