Shiro是apache旗下的一款輕量級的Java安全框架,它能夠提供以下服務:html
Authentication(認證)java
Authorization(受權)mysql
Session Management(會話管理)web
Cryptography(加密)spring
接下來咱們來一塊兒進入shiro的世界吧sql
<!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency>
[users] shiro = 201314
package com.simple.shiro; import org.apache.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; /** * @author xiaobianchen * @version 1.0 2016/4/19 */ public class HelloShiro { private static final Logger logger = Logger.getLogger(HelloShiro.class); public static void main(String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //獲取當前用戶 Subject currentUser = SecurityUtils.getSubject(); //登陸 UsernamePasswordToken token = new UsernamePasswordToken("shiro", "201314"); try { currentUser.login(token); } catch (AuthenticationException e) { logger.info("login failure!"); return; } logger.info("login success! Hello " + currentUser.getPrincipal()); //註銷 currentUser.logout(); } }
咱們分析一下這個 HelloShiro 吧: 數據庫
須要讀取 classpath 下的 shiro.ini 配置文件,並經過工廠類建立 SecurityManager 對象,最終將其放入 SecurityUtils 中,供 Shiro 框架隨時獲取。apache
一樣經過 SecurityUtils 類獲取 Subject 對象,其實就是當前用戶,只不過在 Shiro 的世界裏優雅地將其稱爲 Subject(主體)。緩存
首先使用一個 Username 與 Password,來建立一個 UsernamePasswordToken 對象,而後咱們經過這個 Token 對象調用 Subject 對象的 login 方法,讓 Shiro 進行用戶身份認證。安全
當登陸失敗時,您可使用 AuthenticationException 來捕獲這個異常;當登陸成功時,您能夠調用 Subject 對象的 getPrincipal 方法來獲取 Username,此時 Shiro 已經爲您建立了一個 Session。
最後仍是經過 Subject 對象的 logout 方法來註銷本次 Session。
shiro的內部調用流程以下:
Shiro官方提供的Web模塊-shiro-web
你只須要在pom.xml文件中添加以下配置:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency>
而後在web.xml文件中添加Listener與一個Filter
<?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"> <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> </filter-mapping> </web-app>
EnvironmentLoaderListener這個監聽器是用來初始化SecurityManager的,而且經過ShiroFilter來完成驗證和受權的
在數據庫中創建以下面的表
在shiro.ini配置:
[main] authc.loginUrl=/login ds = org.apache.commons.dbcp.BasicDataSource ds.driverClassName = com.mysql.jdbc.Driver ds.url = jdbc:mysql://localhost:3306/test ds.username = root ds.password = root passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource = $ds jdbcRealm.authenticationQuery = select password from user where username = ? jdbcRealm.userRolesQuery = select r.role_name from user u, user_role ur, role r where u.id = ur.user_id and r.id = ur.role_id and u.username = ? jdbcRealm.permissionsQuery = select p.permission_name from role r, role_permission rp, permission p where r.id = rp.role_id and p.id = rp.permission_id and r.role_name = ? jdbcRealm.permissionsLookupEnabled = true jdbcRealm.credentialsMatcher = $passwordMatcher securityManager.realms = $jdbcRealm cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager securityManager.cacheManager = $cacheManager [urls] / = anon /space/** = authc
對以上配置解釋以下:
首先,在 [main] 片斷中,咱們定義了一個「authc.loginUrl=/login」,用於配置當須要認證時須要跳轉的 URL 地址,這裏表示重定向到 /login 請求,經過 Servlet 映射後可定位到 login 頁面。
而後,定義了一個 DBCP 的 DataSource,用於獲取 JDBC 數據庫鏈接。
而後,定義 JdbcRealm 並指定 DataSource,經過配置如下幾條 SQL 來完成認證與受權:
authenticationQuery:該 SQL 語句用於提供身份認證,即經過 username 查詢 password。
userRolesQuery:該 SQL 語句用於提供基於角色的受權驗證(屬於粗粒度級別),即經過 username 查詢 role_name。
permissionQuery:該 SQL 語句用於提供基於權限的受權驗證(屬於細粒度級別),即經過 role_name 查詢 permission_name,此時須要開啓 permissionsLookupEnabled 開關,默認是關閉的。
最後,在 [urls] 片斷中,咱們定義了一系列的 URL 過濾規則,Shiro 已經提供了一些默認的 Filter(過濾器),便於咱們隨時使用,固然您也能夠擴展其它的過濾器。就本例配置而言,解釋以下:
/ = anon:對於「/」請求(首頁)能夠匿名訪問的。
/space/** = authc:對於以「/space/」開頭的請求,均由 authc 過濾器處理,也就是完成身份認證操做。
每次認證都須要與數據庫打交道,因此基於性能的考慮 在這裏咱們配置了cacheManager
那麼對於咱們新建一個index.jsp頁面
<%@ page pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <html> <head> <title>首頁</title> </head> <body> <h1>首頁</h1> <shiro:guest> <p>身份:遊客</p> <a href="<c:url value="/login"/>">登陸</a> <a href="<c:url value="/register"/>">註冊</a> </shiro:guest> <shiro:user> <p>身份:<shiro:principal/></p> <a href="<c:url value="/space"/>">空間</a> <a href="<c:url value="/logout"/>">退出</a> </shiro:user> </body> </html>
基於servlet服務器的代碼:
@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserServiceImpl(); @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲取表單數據 String username = request.getParameter("username"); String password = request.getParameter("password"); boolean isRememberMe = request.getParameter("rememberMe") != null; // 調用登陸服務 try { userService.login(username, password, isRememberMe); } catch (LoginException e) { request.setAttribute("exception", e.getName()); return; } // 重定向到空間頁面 response.redirect("/index") } }
關於shiro-web目前就介紹這麼多了
spring與shiro的集成 首先須要在pom.xml中添加依賴:
<!-- Shiro Spring--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency>
web.xml中作以下配置:
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <!-- 讀取配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:conf/spring-mybatis.xml classpath:conf/spring-shiro.xml </param-value> </context-param> <!--字符集過濾器--> <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> <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> </filter-mapping> <!-- 配置監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--springmvc 核心配置--> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <description>SpringContext</description> <param-name>contextConfigLocation</param-name> <param-value>classpath:conf/mvc-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置主頁 --> <welcome-file-list> <welcome-file>/WEB-INF/jsp/login.jsp</welcome-file> </welcome-file-list> <!-- 配置session超時時間,單位分鐘 --> <session-config> <session-timeout>30</session-timeout> </session-config> <!--配置錯誤頁面--> <error-page> <error-code>404</error-code> <location>/WEB-INF/jsp/error/404.jsp</location> </error-page> </web-app>
最重要的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.xsd"> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="filterChainDefinitions"> <value> / = anon /space/** = authc </value> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="jdbcRealm"/> <property name="cacheManager" ref="cacheManager"/> </bean> <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm"> <property name="dataSource" ref="dataSource"/> <property name="authenticationQuery" value="select password from user where username = ?"/> <property name="userRolesQuery" value="select r.role_name from user u, user_role ur, role r where u.id = ur.user_id and r.id = ur.role_id and u.username = ?"/> <property name="permissionsQuery" value="select p.permission_name from role r, role_permission rp, permission p where r.id = rp.role_id and p.id = rp.permission_id and r.role_name = ?"/> <property name="permissionsLookupEnabled" value="true"/> <property name="cacheManager" ref="cacheManager"/> <property name="credentialsMatcher" ref="passwordMatcher"/> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!--緩存--> <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/> <!--密碼加密--> <bean id="passwordMatcher" class="org.apache.shiro.authc.credential.PasswordMatcher"/> <bean id="passwordService" class="org.apache.shiro.authc.credential.DefaultPasswordService"/> </beans>
登陸頁面建一個jsp頁面:
<%@ page pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <html> <head> <title>登陸</title> </head> <body> <h1><a href="<c:url value="/"/>">首頁</a> - 登陸</h1> <shiro:notAuthenticated> <form action="<c:url value="/signIn"/>" method="post"> <table> <tr> <td>用戶名:</td> <td><input type="text" name="username"/></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"/></td> </tr> <tr> <td></td> <td> <label><input type="checkbox" name="rememberMe"/>記住我</label> </td> </tr> <tr> <td></td> <td> <button type="submit">登陸</button> </td> </tr> </table> </form> <c:if test="${requestScope['exception'] == 'LoginException'}"> <p>登陸失敗!</p> </c:if> </shiro:notAuthenticated> <shiro:authenticated> <c:redirect url="/space"/> </shiro:authenticated> </body> </html>
控制器層:
@Controller public class LoginController { private static final Logger LOG = LoggerFactory.getLogger(LoginController.class); @RequestMapping(value = "/index",method = RequestMethod.GET) public String index(){ return "index"; } @Autowired private IUserService userService; @RequestMapping(value = "/signIn",method = RequestMethod.GET) public String signIn(){ return "signIn"; } @RequestMapping(value = "/signIn",method = RequestMethod.POST) public String submit(HttpServletRequest request) { // 獲取表單數據 String username = request.getParameter("username"); String password = request.getParameter("password"); boolean isRememberMe = request.getParameter("rememberMe") != null; // 調用登陸服務 try { userService.login(username, password); } catch (LoginException e) { request.setAttribute("exception", e.getName()); return signIn(); } // 重定向到主頁面 return "redirect:/index"; } }