1.shiro(權限框架(認證,賦予角色,授權...)):
readme.txt(運行機制):
1.從jsp的form中的action屬性跳轉到springmvc的Handler中(controller)
2.首先獲取Subject,因為securityManager在applicationContext.xml中配置過了,所以可以直接
通過Subject currentUser = SecurityUtils.getSubject();
3.開始認證
首先把賬號和密碼封裝進UsernamePasswordToken中,然后調用subject的login(UsernamePasswordToken);
4.真正的認證開始(需要和數據庫進行交互)
select * from table where username="zhangsan"
select * from table where username="zhangsan" and passowrd="123456"
4.1 通過username先查詢該登錄用戶是否存在,如果不存在就拋出異常
4.2 如果登錄用戶存在,再次檢驗是否可以正常使用,如果不能正常使用就拋出異常
4.3 匹配密碼
5.密碼匹配流程
5.1.從前端獲取用戶輸入的密碼
5.2.從數據庫中通過用戶名查詢密碼
5.3.把查詢出的密碼SimpleAuthenticationInfo
5.4.shiro會通過SimpleCredentialsMatcher.class中doCredentialsMatch()進行匹配從前端傳遞的密碼和從數據庫中查詢的密碼是否一致
5.4.1.doCredentialsMatch()會重寫equals()來進行匹配if("123456".equals(user.getPassword()))
5.4.2.該方法中需要兩個參數
AuthenticationToken(I)-實現類->UsernamePasswordToken(封裝了前端傳遞的用戶名和密碼)
用戶名和密碼
AuthenticationInfo(I)-被繼承->SaltedAuthenticationInfo(I)-實現類->SimpleAuthenticationInfo(username, user.getPassword(), getName())
username:獲取密碼使用
user.getPassword():需要和前端進行匹配
getName()-->realmName(ShiroRealm2)
為授權做准備
6.密碼加密
6.1.因為密碼為明文,存入數據庫中和項目運行過程中會在開發者工具中出現,所以存在安全隱患問題(如果用戶密碼遭遇泄漏,並且牽扯到金錢問題,發開公司將會承擔用戶的一切損失)
6.2.進行為密碼的加密
shiro會在配置文件中指定加密方式(.ini配置文件,和spring整合以后會在applicationContext.xml中id="jdbcRealm")
最終密碼匹配是在SimpleCredentialsMatcher.class進行-->實現CredentialsMatcher(I)
SimpleCredentialsMatcher.class下有一個子類,一旦使用指定shiro用MD5對密碼進行加密的配置以后,密碼匹配會在 Md5CredentialsMatcher.class中進行
Md5CredentialsMatcher.class已經將來被廢棄,所以
since 1.1 - use the HashedCredentialsMatcher directly and set its
最終對密碼進行加密和匹配HashedCredentialsMatcher進行實現
如何對明文密碼進行加密
6.2.1.從前端獲取到用戶輸入的密碼-->通過doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)進行密碼比對
當進入方法的第一個參數時:AuthenticationToken-->首先會到org.apache.shiro.authc.credential.HashedCredentialsMatcher進行加密-->返回被加密過的密碼
-->返回給AuthenticationToken token
-->進行對比AuthenticationToken token和AuthenticationInfo info(從數據庫查詢出的密碼進行對比)
shiro使用哪一種方式對密碼進行明文加密(MD5)
6.2.2.<!-- 指定密碼的加密算法為MD5 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- hashAlgorithmName:指定了shiro的加密方式,默認就是MD5,如果需要使用其他的加密方式,就必須自定義實現 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!--指定使用MD5加密的次數-->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
開啟shiro的加密方式
6.2.3.因為如果兩個用戶的密碼相同,MD5加密后存入數據庫也相同,需要使用"顏值"加密
user.getPassword():從數據庫中查詢出的密碼
ByteSource.Util.bytes("1"):顏值
ByteSource類型(I)
里面會有一個內部實現類,可以構造出ByteSource實例對象
This is slightly nicer than needing to know the implementation class to use.
如果獲取到顏值?
ByteSource.Utils.bytes("字符串類型");返回值就是顏值所需要的類型(ByteSource)
使用new SimpleAuthenticationInfo(username, user.getPassword(), ByteSource.Util.bytes("1"), getName());
7.授權
7.1.因為AuthenticatingRealm只做認證功能,那么授權在同一個class中無法處理
7.2.授權需要使用到的class是AuthenticatingRealm一個子類AuthorizingRealm
AuthorizingRealm.class既可以進行認證功能,也可以進行授權的功能
LoginHandler.java:
package com.zzsxt.lee.web.shiro.controller; import javax.servlet.http.HttpSession; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; 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.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.zzsxt.lee.web.shiro.service.TestService; @Controller public class LoginHandler { @Autowired private TestService testService; @RequestMapping("/login.action") public String login(@RequestParam("username") String username, @RequestParam("password") String password) { // 開始進行登錄 System.out.println("我是處理登錄請求的方法,我被訪問過"); Subject currentUser = SecurityUtils.getSubject(); // let's login the current user so we can check against roles and // AuthenticatingRealm permissions: // currentUser.isAuthenticated()當前用戶是否被認證 // 如果當前用戶被認證了,說明用戶還是處於登錄狀態 if (!currentUser.isAuthenticated()) { // 如果沒有被認證,開始登錄 // 把需要登錄的用戶名和密碼存入shiro提供的令牌中 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { // subject調用login方法來進行匹配用戶是否可以登錄成功 // login方法的參數需要接收shiro的UsernamePasswordToken類型 currentUser.login(token); } catch (AuthenticationException ae) { System.out.println("登錄失敗!" + ae.getMessage()); } } return "redirect:success.jsp"; } @RequestMapping("/annotation.action") public String ViewAdminPage(HttpSession session) { session.setAttribute("user", "lisi"); // 如果沒有權限調用test()方法的時候,就直接拋出異常 try { testService.test(); } catch (Exception e) { e.getMessage(); return "redirect:unauthorized.jsp"; } return "redirect:success.jsp"; } }
User.java:
package com.zzsxt.lee.web.shiro.model; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 3978937578087629248L; private Long id; private String username; private String password; private Integer age; private Integer lockedUser; public Long getId() { return id; } public void setId(Long id) { this.id = id; } 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; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getLockedUser() { return lockedUser; } public void setLockedUser(Integer lockedUser) { this.lockedUser = lockedUser; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((age == null) ? 0 : age.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((lockedUser == null) ? 0 : lockedUser.hashCode()); result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((username == null) ? 0 : username.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (age == null) { if (other.age != null) return false; } else if (!age.equals(other.age)) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (lockedUser == null) { if (other.lockedUser != null) return false; } else if (!lockedUser.equals(other.lockedUser)) return false; if (password == null) { if (other.password != null) return false; } else if (!password.equals(other.password)) return false; if (username == null) { if (other.username != null) return false; } else if (!username.equals(other.username)) return false; return true; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", password=" + password + ", age=" + age + ", lockedUser=" + lockedUser + "]"; } }
ShiroRealm.java:
package com.zzsxt.lee.web.shiro.reamls; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.realm.Realm; /** * @description 實現Realm接口 * @author Seven Lee * @date 2017年9月21日 下午2:11:56 * */ public class ShiroRealm implements Realm { @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { return null; } @Override public String getName() { return null; } @Override public boolean supports(AuthenticationToken arg0) { return false; } }
ShiroRealm2.java:
package com.zzsxt.lee.web.shiro.reamls; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.util.ByteSource; import com.zzsxt.lee.web.shiro.model.User; public class ShiroRealm2 extends AuthenticatingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // token從controller中調用login(UsernamePasswordToken)傳遞過來 // 1.把AuthenticationToken類型的token轉換為UsernamePasswordToken // upToken:封裝了從前端傳遞的用戶名和密碼(用戶輸入) UsernamePasswordToken upToken = (UsernamePasswordToken) token; // 2.通過UsernamePasswordToken獲取username String username = upToken.getUsername(); // 3.通過username查詢數據庫 System.out.println("從數據庫中匹配username:" + username); // 4.如果沒有查詢到用戶對象信息,就拋出找不到用戶異常UnknownAccountException // 模擬通過username已經從數據庫中查詢出了User對象,而且用戶存在 User user = new User(); user.setId(1L); user.setUsername("zhangsan"); user.setPassword("123456"); user.setAge(17); user.setLockedUser(0); if (!user.getUsername().equals(username)) { throw new UnknownAccountException("該用戶不存在"); } // 5.如果用戶存在-->根據查詢用戶信息的情況,拋出其他的異常(該用戶已經被鎖定,用戶鎖定異常LockedAccountException) if (user.getLockedUser() == 1) { throw new LockedAccountException("該用戶被鎖定"); } // 6.返回AuthenticationInfo類型的數據 // principal:查詢出的用戶實體類對象,也可以是用戶名 // credentials:用戶的密碼 // realmName:realm的name // SimpleAuthenticationInfo sact = new SimpleAuthenticationInfo(username, user.getPassword(), getName()); SimpleAuthenticationInfo sact = new SimpleAuthenticationInfo(username, user.getPassword(), ByteSource.Util.bytes("1"), getName()); return sact; } public static void main(String[] args) { // e10adc3949ba59abbe56e057f20f883e加密一次后的123456的密碼 // fc1709d0a95a6be30bc5926fdb7f22f4加密1024次后的123456的密碼 // ee74a75f182c46effa1a4b350d537566加完鹽值(ByteSource.Util.bytes("1"))后的密碼 User user = new User(); user.setId(1L); new SimpleHash("MD5", "123456", ByteSource.Util.bytes("1"), 1024); } }
ShiroRealm3.java:
package com.zzsxt.lee.web.shiro.reamls; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import com.zzsxt.lee.web.shiro.model.User; public class ShiroRealm3 extends AuthorizingRealm { /** * @description 認證(登錄功能) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // token從controller中調用login(UsernamePasswordToken)傳遞過來 // 1.把AuthenticationToken類型的token轉換為UsernamePasswordToken // upToken:封裝了從前端傳遞的用戶名和密碼(用戶輸入) UsernamePasswordToken upToken = (UsernamePasswordToken) token; // 2.通過UsernamePasswordToken獲取username String username = upToken.getUsername(); // 3.通過username查詢數據庫 System.out.println("從數據庫中匹配username:" + username); // 4.如果沒有查詢到用戶對象信息,就拋出找不到用戶異常UnknownAccountException // 模擬通過username已經從數據庫中查詢出了User對象,而且用戶存在 User user1 = new User(); user1.setId(1L); user1.setUsername("zhangsan"); user1.setPassword("ee74a75f182c46effa1a4b350d537566"); user1.setAge(17); user1.setLockedUser(0); User user2 = new User(); user2.setId(1L); user2.setUsername("lisi"); user2.setPassword("ee74a75f182c46effa1a4b350d537566"); user2.setAge(20); user2.setLockedUser(0); if (!user2.getUsername().equals(username)) { throw new UnknownAccountException("該用戶不存在"); } // 5.如果用戶存在-->根據查詢用戶信息的情況,拋出其他的異常(該用戶已經被鎖定,用戶鎖定異常LockedAccountException) if (user2.getLockedUser() == 1) { throw new LockedAccountException("該用戶被鎖定"); } // 6.返回AuthenticationInfo類型的數據 // principal:查詢出的用戶實體類對象,也可以是用戶名 // credentials:用戶的密碼 // realmName:realm的name // SimpleAuthenticationInfo sact = new // SimpleAuthenticationInfo(username, user.getPassword(), getName()); // 在認證中的第一個參數: // 可以是Username也可以是User實體類對象 // 如果傳的參數為username,那么在授權階段,使用principals.getPrimaryPrincipal();獲取到的就是Username SimpleAuthenticationInfo sact = new SimpleAuthenticationInfo(user2.getUsername(), user2.getPassword(), ByteSource.Util.bytes("1"), getName()); return sact; } /** * @description 授權 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1.通過principals獲取已經認證完畢的用戶名 //String username = (String) principals.getPrimaryPrincipal(); // 2.根據用戶名去數據庫中查詢該認證用戶下角色/權限信息 // Set<String> roles = new HashSet<String>(); // roles.add("user"); // if ("zhangsan".equals(username)) { // roles.add("admin"); // } List<String> permissionList = new ArrayList<String>(); permissionList.add("menu:book"); // 3.把角色/權限信息封裝進SimpleAuthorizationInfo.class SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //info.addRoles(roles); info.addStringPermissions(permissionList); // 4.返回SimpleAuthorizationInfo.class return info; } }
TestService.java:
package com.zzsxt.lee.web.shiro.service; import java.util.Date; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.annotation.RequiresRoles; /** * @description @Service是spring的注解,和shiro並沒有關系 * @RequiresRoles是shiro的注解,如果要使用shiro的注解,就一定要在shiro的配置文件中進行配置 * 再由shiro一同交給spring進行托管 * 如果想使用shiro權限注解,必須要在shiro配置文件中進行配置,配置這個TestService.class * <bena id="xxx" class="xx.xxx.xx.xx.Xxx.class"></bean> * @author Seven Lee * @date 2017年9月22日 下午4:14:52 * */ public class TestService { @RequiresRoles({ "admin" }) public void test() { System.out.println("------------------------" + new Date().toString() + "------------------------"); String username = (String) SecurityUtils.getSubject().getSession().getAttribute("user"); System.out.println(username); } }
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- ========================================================= Shiro Core Components - Not Spring Specific ========================================================= --> <!-- Shiro's main business-tier object for web-enabled applications (use DefaultSecurityManager instead when there is no web environment) --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager" /> <property name="sessionMode" value="native" /> <property name="realm" ref="jdbcRealm" /> </bean> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" /> </bean> <bean id="jdbcRealm" class="com.zzsxt.lee.web.shiro.reamls.ShiroRealm3"> <!-- 指定密碼的加密算法為MD5 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- hashAlgorithmName:指定了shiro的加密方式,默認就是MD5,如果需要使用其他的加密方式,就必須自定義實現 --> <property name="hashAlgorithmName" value="MD5"></property> <!--指定使用MD5加密的次數 --> <property name="hashIterations" value="1024"></property> </bean> </property> </bean> <!-- ========================================================= Shiro Spring-specific integration ========================================================= --> <!-- 最終shiro的生命周期由spring的ioc容器進行托管 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run: --> <!-- 默認自動代理生成器(生成shiro對象) 開啟shiro注解 必須要配置在lifecycleBeanPostProcessor之后 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" /> <!-- 配置 securityManager 可以用注解的形式實現 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <!-- securityManager:subject --> <property name="securityManager" ref="securityManager" /> </bean> <!-- 這就是和你們至關重要的配置文件了!!!!! 和以后的java代碼有關系 shiroFilter securityManager loginUrl:登錄的路徑 successUrl:登錄成功跳轉的路徑 unauthorizedUrl:如果該用戶沒有權限,需要跳轉的頁面 --> <!-- <bean id="shiroFilter">和web.xml的<filter-name>shiroFilter</filter-name>保持一致 如果配置的不一致 報錯org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'shiroFilter' is defined --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.jsp" /> <property name="successUrl" value="/success.jsp" /> <!-- 在授權階段才能進入該頁面 如果已經認證沒有權限的時候,才會觸發 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- anon:如果用戶匿名訪問也可以訪問成功 shiro直接放行,並不需要認證和授權 /login.jsp:路徑 /login.jsp = anon:login頁面不需要認證和授權,可以直接訪問 authc:如果訪問該路徑,就必須要認證和授權 /**:所有的路徑 /login.jsp = anon /** = authc 只有login.jsp不需要攔截,其他一切路徑都需要進行認證和授權 /路徑 = roles[admin] --> <property name="filterChainDefinitions"> <value> <!-- shiro攔截配置優先選舉規則 如果把/**配置到前面 后面所有配置都失效 如果配置的不是/** shiro會默認覆蓋前面的配置 --> /login.* = anon /logout = logout <!-- roles[admin,user] 猜想: 如果roles[]中配置了兩個角色,那么被認證的用戶就必須擁有這兩個角色才能正常訪問該角色下路徑 否則,就會被跳轉到無權限頁面 因為zhangsan用戶即擁有user角色又擁有admin角色,所以能夠訪問該路徑 如果把zhangsan用戶下的user角色給刪除調,則不能訪問該路徑 猜想正確 --> <!-- /success.jsp= roles[user] --> /** = authc </value> </property> </bean> <bean id="testService" class="com.zzsxt.lee.web.shiro.service.TestService"></bean> </beans>
ehcache.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>
log4j.properties:
#
# 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.
#
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=TRACE
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro-servlet.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" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.zzsxt.lee.web.shiro"></context:component-scan> <mvc:default-servlet-handler /> <mvc:annotation-driven></mvc:annotation-driven> <!-- Jsp視圖解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <!--spring入口文件的配置 --> <!-- 確定配置文件位置 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- shiro入口程序 --> <filter> <filter-name>shiroFilter</filter-name> <!-- init(Config config){ String targetBeanName = shiroFilter; } --> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 第二種配置<bean id=""> 在web.xml中的filter標簽里配置<init-param></init-param> <param-name>targetBeanName</param-name>一定寫規范 <param-value>abc</param-value>指向了<bean>的id --> <!-- <init-param> <param-name>targetBeanName</param-name> <param-value>abc</param-value> </init-param> --> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置spring 監聽器,加載xml配置文件 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- DispatcherServlet:前端控制器 配置前端控制器servlet --> <servlet> <servlet-name>shiro</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 表示隨WEB服務器啟動 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>shiro</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <display-name>20170921_shiro_spring</display-name> </web-app>
admin.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'login.jsp' starting page</title> </head> <body> <h1>Admin Page</h1> </body> </html>
login.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'login.jsp' starting page</title> </head> <body> <h1>Login Page</h1> <form action="/zzsxt/login.action" method="post"> Username:<input type="text" name="username" /><br /> Password:<input type="password" name="password" /><br /> <input type="submit" value="submit" /> </form> </body> </html>
success.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'login.jsp' starting page</title> </head> <body> <h1>Success Page</h1> <!-- shiro:principal 從shiro中取出認證后傳遞到授權中的對象-可以為實體對象,也可以是username property="password":從認證中傳遞來的實體對象的屬性(User:username,passowrd,age,lockedUser...) 如果從認證傳遞過來的是一個username的話,則無需property="password"屬性 --> <h4>Welcome:<shiro:principal></shiro:principal></h4> <!-- shiro:guest 無需認證直接可以訪問 --> <shiro:guest> <a href="">登錄</a> </shiro:guest> <shiro:hasRole name="admin"> <shiro:hasPermission name="menu:books"> <a href="/zzsxt/admin.action">admin page</a> </shiro:hasPermission> <shiro:hasPermission name="menu:book"> <a href="/zzsxt/admin.action">user page</a> </shiro:hasPermission> </shiro:hasRole> <a href="/zzsxt/user.jsp">user page</a> <a href="/zzsxt/annotation.action">測試注解</a> <a href="/zzsxt/logout">登出</a> </body> </html>
unauthorized.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'login.jsp' starting page</title> </head> <body> <h1>Unauthorized Page</h1> </body> </html>
user.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'login.jsp' starting page</title> </head> <body> <h1>User Page</h1> </body> </html>