2.3.SSM整合
Shiro 的組件都是 JavaBean/POJO 式的組件,所以非常容易使用 Spring 進行組件管理,可以非常方便的從 ini 配置遷移到 Spring 進行管理,且支持 JavaSE 應用及 Web 應用的集成。
2.3.1.pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.wanho</groupId>
<artifactId>shiroTeaching</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>shiroTeaching Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.2.4.RELEASE</spring.version>
<mybatis.version>3.0.6</mybatis.version>
<jackson.version>1.9.10</jackson.version>
<shiro.version>1.2.2</shiro.version>
<mybatis.spring.version>1.2.2</mybatis.spring.version>
<jstl.version>1.2</jstl.version>
<servlet-api.version>2.5</servlet-api.version>
<jsp-api.version>2.0</jsp-api.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.11</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- JSP相關 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<finalName>shiroTeaching</finalName>
<plugins>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<path>/</path>
<port>8080</port>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.3.2.spring配置文件
applicationContext-shiro
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 加載配置文件
<context:property-placeholder location="classpath:resource/*.properties"/>-->
<!-- 緩存管理器
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> -->
<!-- 緩存管理器 使用Ehcache實現 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro/ehcache.xml"/>
</bean>
<!-- 憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" /><!-- 加密算法的名稱 -->
<property name="hashIterations" value="2" /><!-- 配置加密的次數 -->
</bean>
<!--Realm實現-->
<bean id="userRealm" class="net.wanho.shiro.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher" />
<property name="cachingEnabled" value="false" />
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 開啓shiro註解支持(切面支持)-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 相當於調用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
<!-- rememberMeManager管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- 記住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<!-- 記住我cookie生效時間,默認單位是秒 7*24*60*60-->
<property name="maxAge" value="604800" />
</bean>
<!-- Shiro的Web過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login/toLogin" />
<property name="unauthorizedUrl" value="/error.html"/>
<property name="filterChainDefinitions">
<value>
<!--
anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數
roles(角色):例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。
perms(權限):例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method] ,其中method爲post,get,delete等。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString
是你訪問的url裏的?後面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查
-->
/login/toLogin = anon
/login/checkLogin = anon
/login/logout = authc
/** = user
</value>
</property>
</bean>
<!--權限不足-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">/error</prop>
</props>
</property>
</bean>
<!-- Shiro生命週期處理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>
-
shiroFilter:此處使用 ShiroFilterFactoryBean 來創建 ShiroFilter 過濾器;filters 屬性用於定義自己的過濾器,即 ini 配置中的 [filters] 部分;filterChainDefinitions 用於聲明 url 和 filter 的關係,即 ini 配置中的 [urls] 部分。
applicationContext-dao
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 加載配置文件 -->
<context:property-placeholder location="classpath:resource/*.properties"/>
<!-- 數據庫連接池
maxActive 最大的存活時間
minIdle 最小連接數
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="10"></property >
<property name="minIdle" value="5"></property>
</bean>
<!-- 配置sqlSessionFactory ,關聯數據源
value只能配置簡單數據類型,ref關聯到複雜類型(對象)
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"/>
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置自動掃描,掃描mapper對象,交給spring代理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="net.wanho.mapper"></property>
</bean>
</beans>
applicationContext-trans
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 實際上用的是數據源的事務 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 傳播行爲 -->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="create*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* net.wanho.service.*.*(..))"/>
</aop:config>
</beans>
2.3.3.springMVC配置文件
springMVC.xml
<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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 加載配置文件 -->
<context:property-placeholder location="classpath:resource/*.properties"/>
<!-- 默認的註解映射的支持 -->
<mvc:annotation-driven/>
<context:component-scan base-package="net.wanho.controller"/>
<!-- 視圖解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 啓用AOP 自定義註解用 -->
<aop:config proxy-target-class="true"></aop:config>
</beans>
web.xml
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>smePlantform</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置一個解決post中文亂碼的過濾器 -->
<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>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置springmvc-->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/springmvc.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<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>
</web-app>
2.3.4 實現shiro整合ehcache
<?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"
/>
<!-- 授權緩存 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="1"
overflowToDisk="false"
statistics="true">
</cache>
<!-- 認證緩存 -->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="1"
overflowToDisk="false"
statistics="true">
</cache>
<!--session緩存-->
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="1"
statistics="true">
</cache>
</ehcache>
2.4.密碼比對,MD5&鹽值加密
2.4.1.散列算法
散列算法一般用於生成數據的摘要信息,是一種不可逆的算法,一般適合存儲密碼之類的數據,常見的散列算法如 MD5、SHA 等。一般進行散列時最好提供一個 salt(鹽),比如加密密碼 “admin”,產生的散列值是 “21232f297a57a5a743894a0e4a801fc3”,可以到一些 md5 解密網站很容易的通過散列值得到密碼 “admin”,即如果直接對密碼進行散列相對來說破解更容易,此時我們可以加一些只有系統知道的干擾數據,如用戶名和 ID(即鹽);這樣散列的對象是 “密碼 + 用戶名 +ID”,這樣生成的散列值相對來說更難破解。
String str = "hello";
String salt = "123";
String md5 = new Md5Hash(str, salt).toString();//還可以轉換爲 toBase64()/toHex()
Shiro 還提供了通用的散列支持:
String str = "hello";
String salt = "123";
//內部使用MessageDigest
String simpleHash = new SimpleHash("MD5", str, salt).toString();
通過調用 SimpleHash 時指定散列算法,其內部使用了 Java 的 MessageDigest 實現。
2.4.2.HashedCredentialsMatcher 實現密碼驗證服務
Shiro 提供了 CredentialsMatcher 的散列實現 HashedCredentialsMatcher,和之前的 PasswordMatcher 不同的是,它只用於密碼驗證,且可以提供自己的鹽,而不是隨機生成鹽,且生成密碼散列值的算法需要自己寫,因爲能提供自己的鹽。
1、生成密碼散列值
此處我們使用 MD5 算法,“密碼 + 鹽(用戶名 + 隨機數)” 的方式生成散列值:
String algorithmName = "md5";
String username = "liu";
String password = "123";
String salt1 = username;
String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
int hashIterations = 2;
SimpleHash hash = new SimpleHash(algorithmName, password, salt1 + salt2, hashIterations);
String encodedPassword = hash.toHex()
如果要寫用戶模塊,需要在新增用戶 / 重置密碼時使用如上算法保存密碼,將生成的密碼及 salt2 存入數據庫(因爲我們的散列算法是:md5(md5(密碼 +username+salt2)))。
2、功能實現
User
public class TUser {
private Integer tUserId;
private String tUserName;
private String tUserAccount;
private String tUserPassword;
private String tSort;
private String tUserType;
private Integer tParentUserId;
private Integer departmentId;
private String tStatus;
}
public class UserService implements IUserService{
/**
* 獲取所有的角色
* @param username
* @return
*/
@Override
public Set<String> findRoles(String username) {
Set<String> set = new HashSet<>();
set.add("admin");
return set;
}
/**
* 獲取所有的權限
* @param username
* @return
*/
@Override
public Set<String> findPermissions(String username) {
Set<String> set = new HashSet<>();
set.add("dustin:test");
return set;
}
/**
* 根據用戶名獲取用戶信息
* @param username
* @return
*/
@Override
public TUser findByUsername(String username) {
TUser user = new TUser();
user.settUserName(username);
user.settUserPassword(shiroMD5("123456"));
user.settSort("abcd");
return user;
}
//shiro的密碼加密,參數hashIterations表示加密次數,應該跟配置文件中的相同
private String shiroMD5(String credentials){
//加密方式
String hashAlgorithmName = "MD5";
//String credentials = "123456";
//鹽
ByteSource credentialsSalt = ByteSource.Util.bytes("dustin"+"abcd");
//加密次數
int hashIterations = 2;
Object obj = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations);
return obj.toString();
}
}
public class UserRealm extends AuthorizingRealm {
@Resource
private IUserService userService;
/**
* 獲取角色與權限
*doGetAuthorizationInfo執行時機有三個,如下:
* 1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去調用這個是否有什麼角色或者是否有什麼權限的時候;
* 2、@RequiresRoles("admin") :在方法上加註解的時候;
* 3、@shiro.hasPermission name = "admin"/@shiro.hasPermission:"dustin:test"在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//獲取用戶所有角色
authorizationInfo.setRoles(userService.findRoles(username));
//獲取用戶所有權限
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
/**
* 登錄信息驗證
*
* 1.doGetAuthenticationInfo執行時機如下
* 當調用Subject currentUser = SecurityUtils.getSubject();
* currentUser.login(token);
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
TUser user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException();//沒找到帳號
}
if(Boolean.TRUE.equals(user.gettStatus())) {
throw new LockedAccountException(); //帳號鎖定
}
//交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以自定義實現
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.gettUserName(), //用戶名
user.gettUserPassword(), //密碼
ByteSource.Util.bytes(user.gettUserName()+user.gettSort()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
public class loginController {
@RequestMapping("/toLogin")
public String tologin(HttpServletRequest request, HttpServletResponse response, Model model){
System.out.println("來自IP[" + request.getRemoteHost() + "]的訪問");
return "login";
}
/**
* 驗證用戶名和密碼
* @return
*/
@RequestMapping("/checkLogin")
public String login(String username,String password,ModelMap map) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject currentUser = SecurityUtils.getSubject();
try {
//使用shiro來驗證
if (!currentUser.isAuthenticated()){
//這裏是記住我,記cookie
token.setRememberMe(false);
currentUser.login(token);//驗證角色和權限
}
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
map.put("code","登錄失敗:"+"賬號密碼不正確");
}
return JSONUtils.toJSONString(map);
}
@RequestMapping("/isLogin")
@ResponseBody
public String isLogin() {
Subject currentUser = SecurityUtils.getSubject();
currentUser.isAuthenticated();
return String.valueOf(currentUser.isAuthenticated());
}
@RequestMapping("/logout")
@ResponseBody
public String logout() {
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
return "logout success";
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登錄頁面</title>
<%@include file="/taglib.jsp" %>
</head>
<body>
<form action="${ctx}/login/checkLogin" method="post">
username: <input type="text" name="username"><br>
password: <input type="password" name="password"><br>
<input type="submit" value="登錄">
</form>
</body>
</html>
此處就是把步驟 1 中生成的相應數據組裝爲 SimpleAuthenticationInfo,通過 SimpleAuthenticationInfo 的 credentialsSalt 設置鹽,HashedCredentialsMatcher 會自動識別這個鹽。
- saltStyle 表示使用密碼 + 鹽的機制,authenticationQuery 第一列是密碼,第二列是鹽;
- 通過 authenticationQuery 指定密碼及鹽查詢 SQL;
此處還要注意 Shiro 默認使用了 apache commons BeanUtils,默認是不進行 Enum 類型轉型的,此時需要自己註冊一個 Enum 轉換器 “BeanUtilsBean.getInstance().getConvertUtils().register(new EnumConverter(), JdbcRealm.SaltStyle.class);” 具體請參考示例 “com.github.zhangkaitao.shiro.chapter5.hash.PasswordTest” 中的代碼。
3、spring配置
<!-- 憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" /><!-- 加密算法的名稱 -->
<property name="hashIterations" value="2" /><!-- 配置加密的次數 -->
</bean>
此處最需要注意的就是 HashedCredentialsMatcher 的算法需要和生成密碼時的算法一樣。另外 HashedCredentialsMatcher 會自動根據 AuthenticationInfo 的類型是否是 SaltedAuthenticationInfo 來獲取 credentialsSalt 鹽。
2.4.3.密碼重試次數限制
如在 1 個小時內密碼最多重試 5 次,如果嘗試次數超過 5 次就鎖定 1 小時,1 小時後可再次重試,如果還是重試失敗,可以鎖定如 1 天,以此類推,防止密碼被暴力破解。我們通過繼承 HashedCredentialsMatcher,且使用 Ehcache 記錄重試次數和超時時間。
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String)token.getPrincipal();
//retry count + 1
Element element = passwordRetryCache.get(username);
if(element == null) {
element = new Element(username , new AtomicInteger(0));
passwordRetryCache.put(element);
}
AtomicInteger retryCount = (AtomicInteger)element.getObjectValue();
if(retryCount.incrementAndGet() > 5) {
//if retry count > 5 throw
throw new ExcessiveAttemptsException();
}
boolean matches = super.doCredentialsMatch(token, info);
if(matches) {
//clear retry count
passwordRetryCache.remove(username);
}
return matches;
}
如上代碼邏輯比較簡單,即如果密碼輸入正確清除 cache 中的記錄;否則 cache 中的重試次數 +1,如果超出 5 次那麼拋出異常表示超出重試次數了。
2.5.標籤&權限註解
2.5.1.jsp標籤
Shiro 提供了 JSTL 標籤用於在 JSP/GSP 頁面進行權限控制,如根據登錄用戶顯示相應的頁面按鈕
導入標籤庫
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
標籤庫定義在 shiro-web.jar 包下的 META-INF/shiro.tld 中定義。
2.5.1.1.guest 標籤
<shiro:guest>
歡迎遊客訪問,<a href="${pageContext.request.contextPath}/login.jsp">登錄</a>
</shiro:guest>
用戶沒有身份驗證時顯示相應信息,即遊客訪問信息。
2.5.1.2.user 標籤
<shiro:user>
已認證通過的用戶,setRememberMe = true
</shiro:user>
用戶已經身份驗證 / 記住我登錄後顯示相應的信息。
2.5.1.3.authenticated 標籤
<shiro:authenticated>
用戶[<shiro:principal/>]已身份驗證通過
</shiro:authenticated
用戶已經身份驗證通過,即 Subject.login 登錄成功,不是記住我登錄的。
2.5.1.4.notAuthenticated 標籤
<shiro:notAuthenticated>
未身份驗證(包括記住我)
</shiro:notAuthenticated>
用戶已經身份驗證通過,即沒有調用 Subject.login 進行登錄,包括記住我自動登錄的也屬於未進行身份驗證。
2.5.1.5.principal 標籤
<shiro: principal/>
顯示用戶身份信息,默認調用 Subject.getPrincipal() 獲取,即 Primary Principal。
<shiro:principal type="java.lang.String"/>
相當於 Subject.getPrincipals().oneByType(String.class)。
<shiro:principal property="username"/>
相當於 ((User)Subject.getPrincipals()).getUsername()。
2.5.1.6.hasRole 標籤
<shiro:hasRole name="admin">
用戶[<shiro:principal/>]擁有角色admin<br/>
</shiro:hasRole>
如果當前 Subject 有角色將顯示 body 體內容。
2.5.1.7.hasAnyRoles 標籤
<shiro:hasAnyRoles name="admin,user">
用戶[<shiro:principal/>]擁有角色admin或user<br/>
</shiro:hasAnyRoles>;
如果當前 Subject 有任意一個角色(或的關係)將顯示 body 體內容。
2.5.1.8.hasPermission 標籤
<shiro:hasPermission name="user:create">
用戶[<shiro:principal/>]擁有權限user:create<br/>
</shiro:hasPermission>
如果當前 Subject 有權限將顯示 body 體內容。
2.5.1.8.lacksPermission 標籤
<shiro:lacksPermission name="org:create">
用戶[<shiro:principal/>]沒有權限org:create<br/>
</shiro:lacksPermission>
如果當前 Subject 沒有權限將顯示 body 體內容。
2.5.2.權限註解
Shiro 提供了相應的註解用於權限控制,如果使用這些註解就需要使用 AOP 的功能來進行判斷,如 Spring AOP;Shiro 提供了 Spring AOP 集成用於權限註解的解析和驗證。
@RequiresAuthentication
表示當前 Subject 已經通過 login 進行了身份驗證;即 Subject.isAuthenticated() 返回 true。
@RequiresUser
表示當前 Subject 已經身份驗證或者通過記住我登錄的。
@RequiresGuest
表示當前 Subject 沒有身份驗證或通過記住我登錄過,即是遊客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)
表示當前 Subject 需要角色 admin 和 user。
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)
表示當前 Subject 需要權限 user:a 或 user:b。
此處使用了 Spring MVC 來測試 Shiro 註解,當然 Shiro 註解不僅僅可以在 web 環境使用,在獨立的 JavaSE 中也是可以用的,此處只是以 web 爲例了。
在 spring-mvc.xml 配置文件添加 Shiro Spring AOP 權限註解的支持:
<aop:config proxy-target-class="true"></aop:config>
<bean class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
如上配置用於開啓 Shiro Spring AOP 權限註解的支持;<aop:config proxy-target-class="true">
表示代理類。
接着就可以在相應的控制器(Controller)中使用如下方式進行註解:
@RequiresRoles("admin")
@RequestMapping("/hello2")
public String hello2() {
return "success";
}
訪問 hello2 方法的前提是當前用戶有 admin 角色。
2.6.會話管理(瞭解)
Shiro 提供了完整的企業級會話管理功能,不依賴於底層容器(如 web 容器 tomcat),不管 JavaSE 還是 JavaEE 環境都可以使用,提供了會話管理、會話事件監聽、會話存儲 / 持久化、容器無關的集羣、失效 / 過期支持、對 Web 的透明支持、SSO 單點登錄的支持等特性。即直接使用 Shiro 的會話管理可以直接替換如 Web 容器的會話管理。
2.6.1.會話
所謂會話,即用戶訪問應用時保持的連接關係,在多次交互中應用能夠識別出當前訪問的用戶是誰,且可以在多次交互中保存一些數據。如訪問一些網站時登錄成功後,網站可以記住用戶,且在退出之前都可以識別當前用戶是誰。
Shiro 的會話支持不僅可以在普通的 JavaSE 應用中使用,也可以在 JavaEE 應用中使用,如 web 應用。且使用方式是一致的。
login("classpath:shiro.ini", "zhang", "123");
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession()
登錄成功後使用 Subject.getSession() 即可獲取會話;其等價於 Subject.getSession(true),即如果當前沒有創建 Session 對象會創建一個;另外 Subject.getSession(false),如果當前沒有創建 Session 則返回 null(不過默認情況下如果啓用會話存儲功能的話在創建 Subject 時會主動創建一個 Session)。
session.getId();
獲取當前會話的唯一標識。
session.getHost();
獲取當前 Subject 的主機地址,該地址是通過 HostAuthenticationToken.getHost() 提供的。
session.getTimeout();
session.setTimeout(毫秒);
獲取 / 設置當前 Session 的過期時間;如果不設置默認是會話管理器的全局過期時間。
session.getStartTimestamp();
session.getLastAccessTime();
獲取會話的啓動時間及最後訪問時間;如果是 JavaSE 應用需要自己定期調用 session.touch() 去更新最後訪問時間;如果是 Web 應用,每次進入 ShiroFilter 都會自動調用 session.touch() 來更新最後訪問時間。
session.touch();
session.stop();
更新會話最後訪問時間及銷燬會話;當 Subject.logout() 時會自動調用 stop 方法來銷燬會話。如果在 web 中,調用 javax.servlet.http.HttpSession. invalidate() 也會自動調用 Shiro Session.stop 方法進行銷燬 Shiro 的會話。
session.setAttribute("key", "123");
session.removeAttribute("key");
設置 / 獲取 / 刪除會話屬性;在整個會話範圍內都可以對這些屬性進行操作。
Shiro 提供的會話可以用於 JavaSE/JavaEE 環境,不依賴於任何底層容器,可以獨立使用,是完整的會話模塊。
測試
/**
* 測試會話session
*/
@Test
public void testSession() {
Subject subject = login("classpath:shiro-realm.ini", "zhang", "123");
Session session = subject.getSession();
//獲取當前會話的唯一標識
System.out.println(session.getId());
//獲取當前 Subject 的主機地址
System.out.println(session.getHost());
//獲取 / 設置當前 Session 的過期時間
System.out.println(session.getTimeout());
// 獲取會話的啓動時間及最後訪問時間
System.out.println(session.getStartTimestamp());
System.out.println(session.getLastAccessTime());
// 更新會話最後訪問時間及銷燬會話
//session.touch();
//session.stop();
//設置屬性值
session.setAttribute("name", "zhangsan");
System.out.println(session.getAttribute("name"));
}
2.6.2.會話管理器
會話管理器管理着應用中所有 Subject 的會話的創建、維護、刪除、失效、驗證等工作。是 Shiro 的核心組件,頂層組件 SecurityManager 直接繼承了 SessionManager,且提供了SessionsSecurityManager 實現直接把會話管理委託給相應的 SessionManager,DefaultSecurityManager 及 DefaultWebSecurityManager 默認 SecurityManager 都繼承了 SessionsSecurityManager。
SecurityManager 提供瞭如下接口:
Session start(SessionContext context); //啓動會話
Session getSession(SessionKey key) throws SessionException; //根據會話Key獲取會話
另外用於 Web 環境的 WebSessionManager 又提供瞭如下接口:
boolean isServletContainerSessions();// 是否使用 Servlet 容器的會話
Shiro 還提供了 ValidatingSessionManager 用於驗資並過期會話:
void validateSessions();// 驗證所有會話是否過期
DefaultSessionManager:DefaultSecurityManager 使用的默認實現,用於 JavaSE 環境;
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默認實現,用於 Web 環境,其直接使用 Servlet 容器的會話;
DefaultWebSessionManager:用於 Web 環境的實現,可以替代 ServletContainerSessionManager,自己維護着會話,直接廢棄了 Servlet 容器的會話管理。
spring中配置
<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionDAO" ref="sessionDAO"/>
</bean>
2.6.3.會話監聽器
會話監聽器用於監聽會話創建、過期及停止事件:
public class MySessionListener1 implements SessionListener {
@Override
public void onStart(Session session) {//會話創建時觸發
System.out.println("會話創建:" + session.getId());
}
@Override
public void onExpiration(Session session) {//會話過期時觸發
System.out.println("會話過期:" + session.getId());
}
@Override
public void onStop(Session session) {//退出/會話過期時觸發
System.out.println("會話停止:" + session.getId());
}
}
如果只想監聽某一個事件,可以繼承 SessionListenerAdapter 實現:
public class MySessionListener2 extends SessionListenerAdapter {
@Override
public void onStart(Session session) {
System.out.println("會話創建:" + session.getId());
}
}
2.6.4.會話存儲 / 持久化(sessionDAO)
Shiro 提供 SessionDAO 用於會話的 CRUD,即 DAO(Data Access Object)模式實現:
//如DefaultSessionManager在創建完session後會調用該方法;如保存到關係數據庫/文件系統/NoSQL數據庫;即可以實現會話的持久化;返回會話ID;主要此處返回的ID.equals(session.getId());
Serializable create(Session session);
//根據會話ID獲取會話
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新會話;如更新會話最後訪問時間/停止會話/設置超時時間/設置移除屬性等會調用
void update(Session session) throws UnknownSessionException;
//刪除會話;當會話過期/會話停止(如用戶退出時)會調用
void delete(Session session);
//獲取當前所有活躍用戶,如果用戶量多此方法影響性能
Collection<Session> getActiveSessions();
Shiro 內嵌瞭如下 SessionDAO 實現:
AbstractSessionDAO 提供了 SessionDAO 的基礎實現,如生成會話 ID 等;CachingSessionDAO 提供了對開發者透明的會話緩存的功能,只需要設置相應的 CacheManager 即可;MemorySessionDAO 直接在內存中進行會話維護;而 EnterpriseCacheSessionDAO 提供了緩存功能的會話維護,默認情況下使用 MapCache 實現,內部使用 ConcurrentHashMap 保存緩存的會話。
Shiro 提供了使用 Ehcache 進行會話存儲,Ehcache 可以配合 TerraCotta 實現容器無關的分佈式集羣。
首先在 pom.xml 裏添加如下依賴:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
配置 ehcache.xml
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
statistics="true"/>
Cache 的名字默認爲 shiro-activeSessionCache,即設置的 sessionDAO 的 activeSessionsCacheName 屬性值。
spring中配置
<!-- 會話ID生成器 -->
<bean id="sessionIdGenerator"
class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 會話DAO -->
<bean id="sessionDAO"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 會話驗證調度器 -->
<bean id="sessionValidationScheduler"
class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="1800000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionDAO" ref="sessionDAO"/>
</bean>
- sessionDAO. activeSessionsCacheName:設置 Session 緩存名字,默認就是 shiro-activeSessionCache;
- cacheManager:緩存管理器,用於管理緩存的,此處使用 Ehcache 實現;
- cacheManager.cacheManagerConfigFile:設置 ehcache 緩存的配置文件;
- securityManager.cacheManager:設置 SecurityManager 的 cacheManager,會自動設置實現了 CacheManagerAware 接口的相應對象,如 SessionDAO 的 cacheManager;
如果自定義實現 SessionDAO,繼承 CachingSessionDAO 即可:
public class MySessionDAO extends CachingSessionDAO {
private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
String sql = "insert into sessions(id, session) values(?,?)";
jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));
return session.getId();
}
protected void doUpdate(Session session) {
if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {
return; //如果會話過期/停止 沒必要再更新了
}
String sql = "update sessions set session=? where id=?";
jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());
}
protected void doDelete(Session session) {
String sql = "delete from sessions where id=?";
jdbcTemplate.update(sql, session.getId());
}
protected Session doReadSession(Serializable sessionId) {
String sql = "select session from sessions where id=?";
List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);
if(sessionStrList.size() == 0) return null;
return SerializableUtils.deserialize(sessionStrList.get(0));
}
}
doCreate/doUpdate/doDelete/doReadSession 分別代表創建 / 修改 / 刪除 / 讀取會話;此處通過把會話序列化後存儲到數據庫實現。
2.7.實現rememberMe
Shiro 提供了記住我(RememberMe)的功能,比如訪問如淘寶等一些網站時,關閉了瀏覽器下次再打開時還是能記住你是誰,下次訪問時無需再登錄即可訪問,基本流程如下:
- 首先在登錄頁面選中 RememberMe 然後登錄成功;如果是瀏覽器登錄,一般會把 RememberMe 的 Cookie 寫到客戶端並保存下來;
- 關閉瀏覽器再重新打開;會發現瀏覽器還是記住你的;
- 訪問一般的網頁服務器端還是知道你是誰,且能正常訪問;
- 但是比如我們訪問淘寶時,如果要查看我的訂單或進行支付時,此時還是需要再進行身份認證的,以確保當前用戶還是你。
2.7.1.RememberMe 配置
spring中配置
<!-- 記住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<!-- 記住我cookie生效時間,默認單位是秒 7*24*60*60-->
<property name="maxAge" value="604800" />
</bean>
- sessionIdCookie:maxAge=-1 表示瀏覽器關閉時失效此 Cookie;
- rememberMeCookie:即記住我的 Cookie,保存時長 30 天;
<!-- rememberMeManager管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
- rememberMe 管理器,cipherKey 是加密 rememberMe Cookie 的密鑰;默認 AES 算法;
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
-
web過濾器
<!-- Shiro的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login/toLogin" /> <property name="unauthorizedUrl" value="/error.html"/> <property name="filterChainDefinitions"> <value> /login/toLogin = anon /login/checkLogin = anon /login/logout = authc /** = user </value> </property> </bean>
anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數
roles(角色):例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。
perms(權限):例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method] ,其中method爲post,get,delete等。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString
是你訪問的url裏的?後面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查
2.8.shiro異常處理
處理shiro異常有3種方式:
(1)使用Spring-MVC提供的SimpleMappingExceptionResolver;
(2)實現Spring的異常處理接口HandlerExceptionResolver 自定義自己的異常處理器;
(3)使用@ExceptionHandler註解實現異常處理;
2.8.1.使用Spring-MVC提供的SimpleMappingExceptionResolver;
代碼若拋出授權未通過異常即UnauthorizedException,跳轉noPermission.jsp頁面(會經過視圖解析器後跳轉)
<!--權限不足-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">/noPermission</prop>
</props>
</property>
</bean>
注:/noPermission 是指經過視圖解析器的頁面,這裏直接配需要跳轉到的頁面的名字即可
2.8.2.實現Spring的異常處理接口HandlerExceptionResolver 自定義自己的異常處理器;
實現HandlerExceptionResolver 接口自定義異常處理器,HandlerExceptionResolver是一個接口,只有一個方法,我們只需要實現這個接口;
我們在springMVC的配置文件中進行如下配置
<!--然後通過 Spring的HandlerExceptionResolver去進行全局捕獲,不論你在系統哪裏去throw,只要實現了 HandlerExceptionResovler這個接口,Spring都會攔截下異常進行處理 -->
<bean id="exceptionResolver" class="net.wanho.exception.MyExceptionResolver"></bean>
MyExceptionResolver
public class MyExceptionResolver implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==============異常開始=============");
ex.printStackTrace();
System.out.println("==============異常結束=============");
ModelAndView mv = new ModelAndView("noPermission");
return mv;
}
}
2.8.3.@ExceptionHandler註解實現異常處理
將@ExceptionHandler標註在Controller的方法上,該方法將處理由@RequestMapping方法拋出的異常
首先要增加BaseController類,並在類中使用@ExceptionHandler註解聲明異常處理,代碼如下:
public class BaseController {
/** 基於@ExceptionHandler異常處理 */
@ExceptionHandler
public String exp(HttpServletRequest request, Exception ex) {
// 根據不同錯誤轉向不同頁面
if(ex instanceof org.apache.shiro.authz.UnauthorizedException) {
return "noPermission";
}else {
return "error";
}
}
將需要異常處理的Controller繼承BaseController即可