shiro安全框架
應用:
認證 授權 加密 會話管理(單點登錄) 緩存 與web集成
Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麽它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源
使用步驟:
過濾器的配置:
共有10個過濾器 但是實際使用的時候只要配置一個核心過濾器就行了。
<!--註意:如果是和struts2整合的話,shiro的filter必須在struts2的filter之前,否則action無法創建--> <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>
過濾器種類:
產生代理類的方式:
<!--放在applicationContext.xml事務管理器聲明前-->
<!--產生Shiro控制器的方式 使用cglib生成代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />
applicationContext-shiro.xml(web層):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <description>Shiro與spring整合的配置文件(天然整合)</description> <!-- SecurityManager配置 --> <!-- 配置Realm域 --> <!-- 密碼比較器 --> <!-- 代理如何生成? 用工廠來生成Shiro的相關過濾器 --> <!-- 配置緩存:ehcache緩存 --> <!-- 安全管理 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 2<property name="realm" ref="authRealm" /><!-- 引用自定義的realm --> <!-- 二級緩存 --> <property name="cacheManager" ref="shiroEhcacheManager" /> </bean> <!-- 自定義權限認證 --> <bean id="authRealm" class="com.zixue.shiro.AuthRealm"> <!-- class由程序員定義 --> <property name="userService" ref="userService" /> <!-- 自定義密碼加密算法 --> <property name="credentialsMatcher" ref="passwordMatcher" /> <!-- ref由程序員定義 --> </bean> <!-- 設置密碼加密策略 md5hash --> <bean id="passwordMatcher" class="com.zixue.shiro.CustomCredentialsMatcher" /> <!-- class由程序員定義 --> <!-- filter-name這個名字的值來自於web.xml中filter的名字 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!--這個才是生產過濾器的根源 web.xml的DelegatingFilterProxy並不能直接生成--> <property name="securityManager" ref="securityManager" /> <!--登錄頁面 --> <!--接收loginAction中的subject.login(token)方法,調用了Realm域中的認證方法(反射來實現調用)--> <property name="loginUrl" value="/index.jsp"></property> <!-- 登錄成功後 --> <property name="successUrl" value="/home.action"></property> <property name="filterChainDefinitions"> <!-- /**代表下面的多級目錄也過濾 --> <value> /index.jsp* = anon <!-- (過濾器名) --> /home* = anon /sysadmin/login/login.jsp* = anon /sysadmin/login/logout.jsp* = anon /login* = anon /logout* = anon /components/** = anon /css/** = anon /images/** = anon /js/** = anon /make/** = anon /skin/** = anon /stat/** = anon /ufiles/** = anon /validator/** = anon /resource/** = anon /** = authc <!--根目錄下所有資源及子目錄 --> /*.* = authc <!-- 本目錄所有資源 --> </value> </property> </bean> <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 --> <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml" /> <!-- 引入ehcache-shiro.xml --> </bean> <!-- 保證實現了Shiro內部lifecycle函數的bean執行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!-- 生成代理,通過代理進行控制 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <!-- 安全管理器 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean> </beans>
在applicationContext.xml文件中加載shiro配置文件:
<import resource="classpath:spring/applicationContext-shiro.xml"></import>
ehcache-shiro.xml 緩存(和applicationContext.xml一起)
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" name="shiroCache"> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"/> </ehcache>
編寫密碼比較器:
1.Encrypt.java(使用Md5Hash算法 放在工具包下)
public class Encrypt { /* * 散列算法一般用於生成數據的摘要信息,是一種不可逆的算法,一般適合存儲密碼之類的數據, * 常見的散列算法如MD5、SHA等。一般進行散列時最好提供一個salt(鹽),比如加密密碼“admin”, * 產生的散列值是“21232f297a57a5a743894a0e4a801fc3”, * 可以到一些md5解密網站很容易的通過散列值得到密碼“admin”, * 即如果直接對密碼進行散列相對來說破解更容易,此時我們可以加一些只有系統知道的幹擾數據, * 如用戶名和ID(即鹽);這樣散列的對象是“密碼+用戶名+ID”,這樣生成的散列值相對來說更難破解。 */ //高強度加密算法,不可逆 public static String md5(String password, String salt){ return new Md5Hash(password,salt,2).toString(); } public static void main(String[] args) { System.out.println(new Md5Hash("123456","tony",2).toString()); } }
2.密碼比較器 CustomCredentialsMatcher.java(和web並級的shiro包下)
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { /** * 提供一個專門用於進行密碼比較的方法 * token:代表用戶在界面輸入的用戶名密碼 * AuthenticationInfo info: 代表數據庫中的用戶信息 * * Md5Hash算法 * 將用戶在界面上輸入的明文-----------------加密成密文 * 將用戶在數據庫中的密文獲取出來---------------跟用戶輸入的明文加密後的結果進行比較------------如果相同證明密碼也正確,此時返回值就是true * * 返回值: true代表密碼比較成功 * false代表密碼比較失敗,此時程序也會報出異常 */ public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //1.向下轉型 UsernamePasswordToken upToken = (UsernamePasswordToken) token; //2.將用戶輸入的原始密碼進行加密 Md5Hash的參數說明:第一個參數是明文 第二個參數:代表鹽 第三個參數:叠代次數 //new Md5Hash(new String(upToken.getPassword()),upToken.getUsername(),2); String encryptPwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername()); //3.跟數據庫串密文進行比較 String pwd = info.getCredentials().toString(); //return encryptPwd.equals(pwd); return super.equals(pwd, encryptPwd); } }
配置(applicationContext-shiro.xml已配好 記得修改路徑):
//設置密碼加密策略 md5hash <bean id="passwordMatcher" class="com.zixue.shiro.CustomCredentialsMatcher">
編寫自定義Realm域:
作用:
1.調用業務層和CustomCredentialsMatcher密碼比較器(所以需要註入屬性)
2.進行認證和授權操作
AuthRealm.java(和web並級的shiro包下):
public class AuthRealm extends AuthorizingRealm { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } /** * 認證 (登錄) * AuthenticationInfo 返回值:代表用戶的認證信息 * 如果返回值不為null,說明用戶名正確,程序就會繼續定位到密碼比較器,進行密碼的比較 * 如果返回值為null,程序就會拋出異常 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //向下轉型 UsernamePasswordToken upToken = (UsernamePasswordToken) token; //1.調用業務方法,實現根據用戶名查詢 List<User> userList = userService.find("from User where userName=?", User.class, new String[]{upToken.getUsername()}); //2.如果用戶存在,就一步驗證密碼是否正確 if(userList!=null && userList.size()>0){ User user = userList.get(0);//取出查詢的用戶對象 //用戶名正確了 第一個參數:用戶對象 第二個參數:代表用戶的密碼 第三個參數:realm的名字,只要是一個字符串就可以 return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());//返回密碼比較器的類(進入我們註入的密碼比較器的對象) } //3.返回結果 return null; } /** * 授權---------------在jsp頁面碰到shiro標簽時,就要執行授權方法 */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) { //1.獲取當前登錄的用戶信息 User user = (User)pc.fromRealm(this.getName()).iterator().next(); //2.通過對象導航,得到角色列表 Set<Role> roleSet = user.getRoles(); List<String> moduleList = new ArrayList<String>(); //3.遍歷角色列表,得到每個角色 for (Role role : roleSet) { //通過對象導航,得到角色下面的模塊列表 Set<Module> moduleSet = role.getModules(); for (Module module : moduleSet) { if(!moduleList.contains(module.getName())){ moduleList.add(module.getName()); } } } //設置返回值 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermissions(moduleList); return info; } }
將編寫的AuthRealm域配置好(applicationContext-shiro.xml已配好 記得修改路徑):
//自定義權限認證 <bean id="autoRealm" class="com.zixue.shiro.AuthRealm"> <property name="userService" ref="userService"/> //自定義密碼加密算法 <propetty name="credentialsMatcher" ref="passwordMatcher"> </bean>
登陸操作:
loginAction
作用:應用程序與Shiro框架進行交互的Subject對象獲取
public class LoginAction extends BaseAction { private static final long serialVersionUID = 1L; private String username; private String password; //SSH傳統登錄方式 public String login() throws Exception { // if(true){ // String msg = "登錄錯誤,請重新填寫用戶名密碼!"; // this.addActionError(msg); // throw new Exception(msg); // } // User user = new User(username, password); // User login = userService.login(user); // if (login != null) { // ActionContext.getContext().getValueStack().push(user); // session.put(SysConstant.CURRENT_USER_INFO, login); //記錄session // return SUCCESS; // } // return "login"; if(UtilFuns.isEmpty(username)){ return "login"; } try { //1.應用程序與Shiro框架進行交互的Subject對象如何獲取 ? Subject subject = SecurityUtils.getSubject(); //2.AuthenicationToken是接口 所以只能new子類的對象 AuthenticationToken token = new UsernamePasswordToken(username, password); //3.實現登錄操作 subject.login(token);//進入AuthRealm類中的方法doGetAuthenticationInfo //4.從Shiro中獲取登錄的用戶信息 User user = (User)subject.getPrincipal(); //5.將用戶信息存入session中 session.put(SysConstant.CURRENT_USER_INFO, user); } catch (Exception e) { e.printStackTrace(); request.put("errorInfo", "用戶名或密碼錯誤!!"); return "login"; } return SUCCESS; } //退出 public String logout(){ session.remove(SysConstant.CURRENT_USER_INFO); //刪除session SecurityUtils.getSubject().logout(); //登出 return "logout"; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
測試:
當jsp頁面上出現Shiro標簽就會執行AuthRealm域中的授權方法
1.引入Shiro標簽
<%@taglib uri="http:shiro.apache.org/tags" prefix="shiro" %>
2.使用Shiro標簽進行授權判斷
<shiro:hasPermission name="貨運管理"><span id="topmenu" onclick="toModule(‘cargo‘);">貨運管理</span><span id="tm_separator"></span></shiro:hasPermission>
Shiro運行流程分析:
登陸流程:
授權流程:
關於進入自己賬戶後直接在地址欄輸入權限不同的其他用戶地址,依舊可以進入的
BUG解決:
使用perms過濾器進行權限攔截
1.配置 /sysadmin/deptAction_* = perms["部門管理"] 2.方法加入註解 @RequiresPermissions("部門管理") 3.加入配置 <property name="unauthorizedUrl" value="/index.jsp"></property>
shiro安全框架