Apache Shiro是一款強大、靈活的開源安全管理框架,以十分優雅的方式處理authentication(身份驗證)、authorization(授權)、enterprise session(企業會話)和cryptography(加密)。
Apache Shiro的首要目標就是易於上手和容易理解。在軟件中,安全管理有時會非常復雜、痛苦,但Apache Shiro會讓它變得非常簡單。下面是Apache Shiro可以做的事:
- 鑒別用戶身份
- 管理用戶權限,例如:判斷用戶是否有某一角色或用戶是否被允許做某一操作
- 即使沒有web或EJB容器,也可以使用session API
- 在鑒別用戶身份時、權限管理時或session生命周期內進行一些操作
- 可以聚合一個或多個用戶權限數據源並且以用戶視圖的形式統一表現出來
- 實現了單點登錄功能(SSO)
- 無需登錄便可實現“記住我”這一功能
shiro的四大核心部分:Authentication(身份驗證)、Authorization(授權)、Session Management(會話管理)、cryptography(加密)
shiro的三個核心組件:
Subject :正與系統進行交互的人,或某一個第三方服務。所有 Subject 實例都被綁定到(且這是必須的)一個SecurityManager 上。
SecurityManager:Shiro 架構的心臟,用來協調內部各安全組件,管理內部組件實例,並通過它來提供安全管理的各種服務。當 Shiro 與一個 Subject 進行交互時,實質上是幕后的 SecurityManager 處理所有繁重的 Subject 安全操作。
Realms :本質上是一個特定安全的 DAO。當配置 Shiro 時,必須指定至少一個 Realm 用來進行身份驗證和/或授權。Shiro 提供了多種可用的 Realms 來獲取安全相關的數據。如關系數據庫(JDBC),INI 及屬性文件等。可以定義自己 Realm 實現來代表自定義的數據源。
導入jar包:
<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>
配置web.xml,shiro過濾器:
<!-- 配置 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>
創建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-3.1.xsd"> <!-- 啟用shrio授權注解攔截方式 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 裝配 securityManager --> <property name="securityManager" ref="securityManager" /> <!-- 配置登陸頁面 --> <property name="loginUrl" value="/user/login" /> <!-- 登陸成功后的一面 --> <property name="successUrl" value="/md5/index" /> <property name="unauthorizedUrl" value="/user/unauthorized" /> <!-- 具體配置需要攔截哪些 URL, 以及訪問對應的 URL 時使用 Shiro 的什么 Filter 進行攔截. --> <property name="filterChainDefinitions"> <value> /user/login = anon /user/register = anon /user/doRegister = anon /user/doLogin = anon /user/unauthorized = anon /md5/list = authc /md5/index = authc /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.cn.hnust.util.ShiroRealm"></bean> <!-- 配置 Shiro 的 SecurityManager Bean. --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager" /> <property name="realm" ref="myRealm" /> </bean> <!-- 配置 Bean 后置處理器: 會自動的調用和 Spring 整合后各個組件的生命周期方法. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"> </bean> </beans>
anon是不需要驗證
authc是需要驗證才能使用
然后就可以將web.xml中contextConfigLocationg的值改成spring-*.xml
在resource文件夾下面添加ehcache-shiro.xml,
<!-- ~ Licensed to the Apache Software Foundation (ASF) under one ~ or more contributor license agreements. See the NOTICE file ~ distributed with this work for additional information ~ regarding copyright ownership. The ASF licenses this file ~ to you under the Apache License, Version 2.0 (the ~ "License"); you may not use this file except in compliance ~ with the License. You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, ~ software distributed under the License is distributed on an ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ~ KIND, either express or implied. See the License for the ~ specific language governing permissions and limitations ~ under the License. --> <!-- EhCache XML configuration file used for Shiro spring sample application --> <ehcache> <!-- Sets the path to the directory where cache .data files are created. If the path is a Java System Property it is replaced by its value in the running VM. The following properties are translated: user.home - User's home directory user.dir - User's current working directory java.io.tmpdir - Default temp file path --> <diskStore path="java.io.tmpdir/shiro-spring-sample"/> <!--Default Cache configuration. These will applied to caches programmatically created through the CacheManager. The following attributes are required: maxElementsInMemory - Sets the maximum number of objects that will be created in memory eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element is never expired. overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache has reached the maxInMemory limit. The following attributes are optional: timeToIdleSeconds - Sets the time to idle for an element before it expires. i.e. The maximum amount of time between accesses before an element expires Is only used if the element is not eternal. Optional attribute. A value of 0 means that an Element can idle for infinity. The default value is 0. timeToLiveSeconds - Sets the time to live for an element before it expires. i.e. The maximum time between creation time and when an element expires. Is only used if the element is not eternal. Optional attribute. A value of 0 means that and Element can live for infinity. The default value is 0. diskPersistent - Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value is 120 seconds. memoryStoreEvictionPolicy - Policy would be enforced upon reaching the maxElementsInMemory limit. Default policy is Least Recently Used (specified as LRU). Other policies available - First In First Out (specified as FIFO) and Less Frequently Used (specified as LFU) --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <!-- We want eternal="true" (with no timeToIdle or timeToLive settings) because Shiro manages session expirations explicitly. If we set it to false and then set corresponding timeToIdle and timeToLive properties, ehcache would evict sessions without Shiro's knowledge, which would cause many problems (e.g. "My Shiro session timeout is 30 minutes - why isn't a session available after 2 minutes?" Answer - ehcache expired it due to the timeToIdle property set to 120 seconds.) diskPersistent=true since we want an enterprise session management feature - ability to use sessions after even after a JVM restart. --> <cache name="shiro-activeSessionCache" maxElementsInMemory="10000" eternal="true" overflowToDisk="true" diskPersistent="true" diskExpiryThreadIntervalSeconds="600"/> <cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization" maxElementsInMemory="100" eternal="false" timeToLiveSeconds="600" overflowToDisk="false"/> </ehcache>
接下來是寫ShiroRealm類(繼承AuthorizingRealm):
package com.cn.hnust.util; import javax.annotation.Resource; 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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import com.cn.hnust.dao.IUserDao; import com.cn.hnust.entity.User; public class ShiroRealm extends AuthorizingRealm{ @Resource private IUserDao userDao; String pass; /** * 授權 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); Object principal = principalCollection.getPrimaryPrincipal(); if("admin".equals(principal)){ info.addRole("admin"); } if("user".equals(principal)){ info.addRole("list"); } info.addRole("user"); return info; } /** * 用戶驗證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1. token 中獲取登錄的 username! 注意不需要獲取password. Object principal = token.getPrincipal(); //2. 利用 username 查詢數據庫得到用戶的信息. User user=userDao.getByUserName((String)principal); if(user!=null){ pass=user.getPassword(); } //當前 Realm 的name String realmName = getName(); //返回值實例化 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, pass, realmName); return info; } }
接下來是controller:
@RequestMapping(value="/doLogin",method={RequestMethod.POST}) public @ResponseBody String doLogin(HttpServletRequest request,Map<String, Object> dataMap){ String username=request.getParameter("username"); String password=Md5Utils.getMD5_32bits(request.getParameter("password")); Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken(username, password); try{ subject.login(token); }catch(AuthenticationException e){ System.out.println("登錄失敗:"+e.getMessage()); return "0"; } return "1"; }
關於Md5Utils:
package com.cn.hnust.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Md5Utils { public static String getMD5_16bits(String str) { return getMD5_32bits(str).substring(8, 24); } public static String getMD5_32bits(String str) { if(str == null || str.equals("")){ throw new BusinessException("md5加密內容不能為空"); } MessageDigest md=null; try { md = MessageDigest.getInstance("MD5"); byte[] md5Bytes = md.digest(str.getBytes()); StringBuffer hexValue = new StringBuffer(); for (int i = 0; i < md5Bytes.length; i++) { int val = ((int) md5Bytes[i]) & 0xff; if (val < 16) hexValue.append("0"); hexValue.append(Integer.toHexString(val)); } str = hexValue.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return str; } }