什麼是權限?
權限是管理web應用用戶的一種手段,比如,一個電商平臺,用戶具有user的角色,他可以在這個商場裏面進行交易。商家擁有的是user的角色同時也擁有manager的角色,因此,他可以進行買賣的同時進行對自己商品的管理。shiro就是一個基於RBAC權限設計模型的權限管理框架。
什麼是Shiro ?
Apache Shiro是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理等功能:
認證 - 用戶身份識別,常被稱爲用戶“登錄”;
授權 - 訪問控制;
密碼加密 - 保護或隱藏數據防止被偷窺;
會話管理 - 每用戶相關的時間敏感的狀態。
以下是對使用SpringMVC+shiro+hibernate框架對用戶管理的一個例子:
附上項目:https://git.oschina.net/jeremie_astray/SpringMVC_Shiro/tree/master/
Annotion版本:https://git.oschina.net/jeremie_astray/SpringMVC_Shiro/tree/shiro_annotation
一、實體
對應關係:
用戶與角色爲一對多關係
角色與權限爲多對多關係
權限過濾與角色和權限爲一對一關係
t_user(,用戶表,密碼爲md5加密,可以自己修改)
t_role(角色表)
t_permission(權限表)
t_function(權限過濾表)
t_user_role(用戶-角色表,中間表)
t_role_permission(角色-權限表,中間表)
實體類可以在文章結尾的git鏈接查看
二、包導入及spring配置
maven配置如下:
- <span style="font-size:12px;"> <properties>
- <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <spring.version>4.1.0.RELEASE</spring.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- <!-- SpringMVCjar -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aspects</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-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-expression</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-orm</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-tx</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-web</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-test</artifactId>
- <version>${spring.version}</version>
- <scope>test</scope>
- </dependency>
- <!-- Hibernate-->
- <dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache</artifactId>
- <version>2.7.2</version>
- </dependency>
- <dependency>
- <groupId>commons-dbcp</groupId>
- <artifactId>commons-dbcp</artifactId>
- <version>1.4</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.26</version>
- </dependency>
- <!-- javax提供的annotation -->
- <dependency>
- <groupId>javax.inject</groupId>
- <artifactId>javax.inject</artifactId>
- <version>1</version>
- </dependency>
- <!-- **************************** -->
- <!-- hibernate驗證 -->
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-core</artifactId>
- <version>4.3.6.Final</version>
- </dependency>
- <dependency>
- <groupId>org.hibernate.common</groupId>
- <artifactId>hibernate-commons-annotations</artifactId>
- <version>4.0.5.Final</version>
- </dependency>
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-entitymanager</artifactId>
- <version>4.3.6.Final</version>
- </dependency>
- <dependency>
- <groupId>org.hibernate.javax.persistence</groupId>
- <artifactId>hibernate-jpa-2.1-api</artifactId>
- <version>1.0.0.Final</version>
- </dependency>
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-ehcache</artifactId>
- <version>4.3.6.Final</version>
- </dependency>
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-validator</artifactId>
- <version>5.1.2.Final</version>
- </dependency>
- <dependency>
- <groupId>com.mchange</groupId>
- <artifactId>c3p0</artifactId>
- <version>0.9.5-pre8</version>
- </dependency>
- <!-- jstl -->
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>jstl</artifactId>
- <version>1.2</version>
- </dependency>
- <!-- servlet -->
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <version>3.1.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>javax.servlet.jsp</groupId>
- <artifactId>javax.servlet.jsp-api</artifactId>
- <version>2.3.1</version>
- <scope>provided</scope>
- </dependency>
- <!-- Shiro -->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-core</artifactId>
- <version>1.2.3</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-web</artifactId>
- <version>1.2.3</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring</artifactId>
- <version>1.2.3</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-ehcache</artifactId>
- <version>1.2.3</version>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.4</version>
- </dependency>
- <dependency>
- <groupId>commons-beanutils</groupId>
- <artifactId>commons-beanutils</artifactId>
- <version>1.8.3</version>
- </dependency>
- <dependency>
- <groupId>commons-fileupload</groupId>
- <artifactId>commons-fileupload</artifactId>
- <version>1.3.1</version>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.6</version>
- </dependency>
- <dependency>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- <version>1.1.2</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.7.5</version>
- </dependency>
- <dependency>
- <groupId>asm</groupId>
- <artifactId>asm</artifactId>
- <version>3.3.1</version>
- </dependency>
- <dependency>
- <groupId>org.aspectj</groupId >
- <artifactId>aspectjweaver</artifactId >
- <version> 1.6.11</version >
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
- <configuration>
- <source>1.7</source>
- <target>1.7</target>
- <encoding>UTF8</encoding>
- <compilerArguments>
- <endorseddirs>${endorsed.dir}</endorseddirs>
- </compilerArguments>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-war-plugin</artifactId>
- <version>2.4</version>
- <configuration>
- <failOnMissingWebXml>false</failOnMissingWebXml>
- </configuration>
- </plugin>
- <!-- tomcat7maven插件 -->
- <plugin>
- <groupId>org.apache.tomcat.maven</groupId>
- <artifactId>tomcat7-maven-plugin</artifactId>
- <version>2.2</version>
- <configuration>
- <path>/</path>
- <port>80</port>
- <contextReloadable>true</contextReloadable>
- <contextFile>src/main/webapp/META-INF/context.xml</contextFile>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-dependency-plugin</artifactId>
- <version>2.6</version>
- <executions>
- <execution>
- <phase>validate</phase>
- <goals>
- <goal>copy</goal>
- </goals>
- <configuration>
- <outputDirectory>${endorsed.dir}</outputDirectory>
- <silent>true</silent>
- <artifactItems>
- <artifactItem>
- <groupId>javax</groupId>
- <artifactId>javaee-endorsed-api</artifactId>
- <version>7.0</version>
- <type>jar</type>
- </artifactItem>
- </artifactItems>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build></span>
備註:maven的tomcat7插件可以使用tomcat7:run指令運行
- <span style="font-size:12px;"><?xml version="1.0" encoding="UTF-8" ?>
- <web-app version="2.5"
- 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_2_5.xsd">
- <display-name>Archetype Created Web Application</display-name>
- <!--過濾字符集-->
- <filter>
- <filter-name>encoding</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>encoding</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- spring-orm-hibernate4的OpenSessionInViewFilter -->
- <filter>
- <filter-name>opensessioninview</filter-name>
- <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>opensessioninview</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- 配置springmvc servlet -->
- <servlet>
- <servlet-name>springmvc</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>springmvc</servlet-name>
- <!-- / 表示所有的請求都要經過此serlvet -->
- <url-pattern>*.html</url-pattern>
- </servlet-mapping>
- <!-- spring的監聽器 -->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath*:applicationContext.xml</param-value>
- </context-param>
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
- <!-- Shiro配置 -->
- <filter>
- <filter-name>shiroFilter</filter-name>
- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>shiroFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <welcome-file-list>
- <welcome-file>login.html</welcome-file>
- </welcome-file-list>
- </web-app> </span>
備註:springmvc只對*.html進行過濾,而shiro則是對所有url進行過濾
springmvc-servlet的配置
- <span style="font-size:12px;"><?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"
- 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">
- <context:component-scan base-package="com.etop" use-default-filters="false">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
- </context:component-scan>
- <context:component-scan base-package="com.etop.controller"/>
- <mvc:annotation-driven/>
- <!-- 根據客戶端的不同的請求決定不同的view進行響應, 如 /blog/1.json /blog/1.xml -->
- <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
- <!-- 擴展名至mimeType的映射,即 /account.json => application/json -->
- <property name="favorPathExtension" value="true"/>
- <!-- 用於開啓 /userinfo/123?format=json 的支持 -->
- <property name="favorParameter" value="true"/>
- <property name="parameterName" value="format"/>
- <!-- 是否忽略Accept Header -->
- <property name="ignoreAcceptHeader" value="false"/>
- <property name="mediaTypes"> <!--擴展名到MIME的映射;favorPathExtension, favorParameter是true時起作用 -->
- <value>
- ccjson=application/json
- ccxml=application/xml
- html=text/html
- </value>
- </property>
- <!-- 默認的content type -->
- <property name="defaultContentType" value="text/html"/>
- </bean>
- </beans></span>
- <span style="font-size:12px;"><?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:aop="http://www.springframework.org/schema/aop"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:tx="http://www.springframework.org/schema/tx"
- 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/context http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
- " default-lazy-init="true">
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <property name="realm" ref="myRealm"/>
- <!-- 使用下面配置的緩存管理器 -->
- <property name="cacheManager" ref="cacheManager"/>
- </bean>
- <!--自定義Realm-->
- <bean id="myRealm" class="com.etop.shiro.MyRealm"/>
- <!-- 配置shiro的過濾器工廠類,id- shiroFilter要和我們在web.xml中配置的過濾器一致 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <!-- 調用我們配置的權限管理器 -->
- <property name="securityManager" ref="securityManager"/>
- <!-- 配置我們的登錄請求地址 -->
- <property name="loginUrl" value="/login.html"/>
- <!-- 配置我們在登錄頁登錄成功後的跳轉地址,如果你訪問的是非/login地址,則跳到您訪問的地址 -->
- <property name="successUrl" value="/user.html"/>
- <!-- 如果您請求的資源不再您的權限範圍,則跳轉到/403請求地址 -->
- <property name="unauthorizedUrl" value="/403.html"/>
- <!-- 權限配置 -->
- <property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource"/>
- </bean>
- <!--自定義filterChainDefinitionMap-->
- <bean id="chainDefinitionSectionMetaSource" class="com.etop.shiro.ChainDefinitionSectionMetaSource"/>
- <!--shiro緩存管理器-->
- <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
- <bean id="propertyConfigurer"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations" value="classpath:jdbc.properties"/>
- </bean>
- <!--hibernate session工廠設置-->
- <bean id="sessionFactory"
- class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="dataSource"/>
- <property name="packagesToScan">
- <list>
- <value>com.etop.pojo</value>
- </list>
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
- <prop key="hibernate.generate_statistics">false</prop>
- <prop key="hibernate.show_sql">true</prop>
- <prop key="hibernate.format_sql">false</prop>
- <prop key="hibernate.jdbc.batch_size">50</prop>
- <prop key="jdbc.use_scrollable_resultset">false</prop>
- <prop key="javax.persistence.validation.mode">none</prop>
- <prop key="hibernate.cache.use_second_level_cache">true</prop>
- <prop key="hibernate.cache.use_query_cache">true</prop>
- <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
- <prop key="jdbc.use_scrollable_resultset">false</prop>
- </props>
- </property>
- </bean>
- <!-- c3p0 configuration -->
- <bean id="mainDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
- <property name="driverClass" value="${jdbc.driverClass}"/>
- <property name="jdbcUrl" value="${jdbc.url}"/>
- <property name="user" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- <property name="minPoolSize" value="${jdbc.minPoolSize}"/>
- <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
- <property name="checkoutTimeout" value="${jdbc.checkoutTimeout}"/>
- <property name="maxStatements" value="${jdbc.maxStatements}"/>
- <property name="testConnectionOnCheckin" value="${jdbc.testConnectionOnCheckin}"/>
- <property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
- </bean>
- <bean id="dataSource"
- class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
- <property name="targetDataSource">
- <ref bean="mainDataSource"/>
- </property>
- </bean>
- <context:annotation-config/>
- <context:component-scan base-package="com.etop">
- <context:exclude-filter type="regex" expression="com.cn.controller.*"/>
- </context:component-scan>
- <bean id="transactionManager"
- class="org.springframework.orm.hibernate4.HibernateTransactionManager">
- <property name="sessionFactory">
- <ref bean="sessionFactory"/>
- </property>
- </bean>
- <!-- 攔截配置 -->
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <!--說明事務類別 -->
- <tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="save*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="add*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="batch*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="sendOpen*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="sendClose*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
- <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
- <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
- <tx:method name="load*" propagation="REQUIRED" read-only="true"/>
- <tx:method name="*" read-only="true"/>
- </tx:attributes>
- </tx:advice>
- <!-- 切入點 -->
- <aop:config expose-proxy="true" proxy-target-class="true">
- <!-- service層事務 -->
- <aop:advisor id="serviceTx" advice-ref="txAdvice"
- pointcut="execution(public * com.etop.service.*.*(..))" order="1"/>
- </aop:config>
- <tx:annotation-driven/>
- </beans>
- </span>
備註:配置好自定義的Realm與chainDefinitionSectionMetaSource
- securityManager是shiro的核心,初始化時協調各個模塊運行。
- realm是shiro的橋樑,進行數據源配置
jdbc.properties:
- <span style="font-size:12px;">jdbc.driverClass = com.mysql.jdbc.Driver
- jdbc.url = jdbc:mysql://127.0.0.1:3306/shiro
- jdbc.username = root
- jdbc.password = root
- jdbc.minPoolSize=2
- jdbc.maxPoolSize=20
- jdbc.checkoutTimeout=3000
- jdbc.maxStatements=50
- jdbc.testConnectionOnCheckin = false
- jdbc.idleConnectionTestPeriod = 18000</span>
Realm是shiro獲取身份驗證相關信息與獲取授權信息的重寫:
獲取授權信息(doGetAuthorizationInfo()):通過用戶名和userService接口就可以獲取對應角色及權限信息。
獲取身份驗證相關信息(doGetAuthenticationInfo()):首先根據傳入的用戶名獲取User信息;然後如果user爲空,那麼拋出沒找到帳號異常UnknownAccountException;如果user找到但鎖定了拋出鎖定異常LockedAccountException;最後生成AuthenticationInfo信息,交給間接父類AuthenticatingRealm使用CredentialsMatcher進行判斷密碼是否匹配,如果不匹配將拋出密碼錯誤異常IncorrectCredentialsException;另外如果密碼重試此處太多將拋出超出重試次數異常ExcessiveAttemptsException;
而filterChainDefinition則是對url訪問權限的重寫:
產生責任鏈,確定每個url的訪問權限
參見格式如下:
/static/**=anon
<!-- perms[user:query]表示訪問此連接需要權限爲user:query的用戶 -->
/user=perms[user:query]
<!-- roles[manager]表示訪問此連接需要用戶的角色爲manager -->
/user/add=roles[manager]
/user/del/**=roles[admin]
/user/edit/**=roles[manager]
原本是由ini文件直接讀取,我這裏寫成由數據庫讀取,方便管理
MyRealm.java
- <span style="font-size:12px;">package com.etop.shiro;
- import java.util.Collection;
- import javax.inject.Inject;
- import com.etop.service.UserService;
- 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.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.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import com.etop.pojo.Role;
- import com.etop.pojo.User;
- /**
- * Created by Jeremie on 2014/10/1.
- */
- @Service
- @Transactional
- public class MyRealm extends AuthorizingRealm{
- @Inject
- private UserService userService;
- /**
- * <span style="font-family: Helvetica, Tahoma, Arial, sans-serif; font-size: 14px; line-height: 25.2000007629395px;"><span style="font-family: Helvetica, Tahoma, Arial, sans-serif; font-size: 14px; line-height: 25.2000007629395px;">獲取授權信息</span></span>
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
- //獲取登錄時輸入的用戶名
- String loginName=(String) principalCollection.fromRealm(getName()).iterator().next();
- //到數據庫獲取此用戶
- User user=userService.findByName(loginName);
- if(user!=null){
- //權限信息對象info,用來存放查出的用戶的所有的角色(role)及權限(permission)
- SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
- //用戶的角色集合
- info.setRoles(user.getRolesName());
- //用戶的角色對應的所有權限,如果只使用角色定義訪問權限
- Collection<Role> roleList=user.getRoleList();
- for (Role role : roleList) {
- info.addStringPermissions(role.getPermissionsName());
- }
- return info;
- }
- return null;
- }
- /**
- * 獲取身份驗證相關信息
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(
- AuthenticationToken authenticationToken) throws AuthenticationException {
- //UsernamePasswordToken對象用來存放提交的登錄信息
- UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
- //查出是否有此用戶
- User user=userService.findByName(token.getUsername());
- if(user!=null){
- //若存在,將此用戶存放到登錄認證info中
- return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
- }
- return null;
- }
- }</span>
ChainDefinitionSectionMetaSource.java
- <span style="font-size:12px;">package com.etop.shiro;
- import com.etop.pojo.Function;
- import com.etop.service.FunctionService;
- import org.apache.commons.lang.StringUtils;
- import org.apache.shiro.config.Ini;
- import org.springframework.beans.factory.FactoryBean;
- import org.springframework.beans.factory.annotation.Autowired;
- import java.util.Iterator;
- import java.util.List;
- /**
- * Created by Jeremie on 2014/10/1.
- */
- public class ChainDefinitionSectionMetaSource implements FactoryBean<Ini.Section> {
- @Autowired
- private FunctionService functionService;
- //靜態資源訪問權限
- private String filterChainDefinitions = "/static/**=anon";
- @Override
- public Ini.Section getObject() throws Exception {
- List<Function> list = functionService.findAll();
- Ini ini = new Ini();
- //加載默認的url
- ini.load(filterChainDefinitions);
- Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
- //循環Resource的url,逐個添加到section中。section就是filterChainDefinitionMap,
- //裏面的鍵就是鏈接URL,值就是存在什麼條件才能訪問該鏈接
- for (Iterator<Function> it = list.iterator(); it.hasNext(); ) {
- Function function = it.next();
- //構成permission字符串
- if (StringUtils.isNotEmpty(function.getValue()) && StringUtils.isNotEmpty(function.getType())) {
- String permission = "";
- switch(function.getType()){
- case "anon":
- permission = "anon";
- break;
- case "perms":
- permission = "perms[" + function.getPermission().getPermissionname() + "]";
- break;
- case "roles":
- permission = "roles[" + function.getRole().getRolename() + "]";
- break;
- default:
- break;
- }
- section.put(function.getValue(), permission);
- }
- }
- //所有資源的訪問權限,必須放在最後
- section.put("/**", "authc");
- return section;
- }
- @Override
- public Class<?> getObjectType() {
- return this.getClass();
- }
- @Override
- public boolean isSingleton() {
- return false;
- }
- }
- </span>
登錄界面login.jsp
- <%@ page language="java" pageEncoding="UTF-8"%>
- <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>登錄頁面</title>
- <script type="text/javascript" src="static/js/md5.js"></script>
- </head>
- <body>
- <h1>登錄頁面----<span style="color: red;">${message }</span></h1>
- <form action="/login.html" name="user" method="post">
- 用戶名:<input type="text" name="username"/> <br/>
- 密 碼:<input type="password" id="password" name="password"/> <br/>
- <input type="button" onclick="submitform()" value="登錄"/>
- <input type="reset" value="重置"/>
- </form>
- </body>
- <script type="text/javascript">
- function submitform(){
- var password = document.getElementById("password");
- document.getElementById("password").value = hex_md5(password.value);
- document.user.submit();
- }
- </script>
- </html>
- <%@ page language="java" pageEncoding="UTF-8" %>
- <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>用戶列表</title>
- <script type="text/javascript" src="static/js/jquery-2.0.3.min.js"></script>
- </head>
- <body>
- <h1>${message }</h1>
- <h1>用戶列表<shiro:hasPermission name="user:add">--<a href="/user/add.html">添加用戶</a></shiro:hasPermission>---<a href="/logout.html">退出登錄</a></h1>
- <h2>權限列表</h2>
- <shiro:authenticated>用戶已經登錄顯示此內容</shiro:authenticated><br/>
- <shiro:hasRole name="manager">manager角色登錄顯示此內容</shiro:hasRole><br/>
- <shiro:hasRole name="admin">admin角色登錄顯示此內容</shiro:hasRole><br/>
- <shiro:hasRole name="normal">normal角色登錄顯示此內容</shiro:hasRole><br/>
- <shiro:hasRole name="user"><p style="color: red;">測試專用!!</p></shiro:hasRole>
- <shiro:hasAnyRoles name="manager,admin">**manager or admin 角色用戶登錄顯示此內容**</shiro:hasAnyRoles><br/>
- <p>============================我是邪惡的分割線==========================</p>
- <shiro:principal/>-顯示當前登錄用戶名<br/>
- <shiro:hasPermission name="user:add">user:add權限用戶顯示此內容</shiro:hasPermission><br/>
- <shiro:hasPermission name="user:del">user:del權限用戶顯示此內容</shiro:hasPermission><br/>
- <shiro:hasPermission name="user:update">user:update權限用戶顯示此內容</shiro:hasPermission><br/>
- <shiro:hasPermission name="user:query">user:query權限用戶顯示此內容</shiro:hasPermission><br/>
- <shiro:lacksPermission name="user:add">不具有user:add權限用戶顯示此內容</shiro:lacksPermission><br/>
- <shiro:lacksPermission name="user:del">不具有user:del權限用戶顯示此內容</shiro:lacksPermission><br/>
- <shiro:lacksPermission name="user:update">不具有user:update權限用戶顯示此內容</shiro:lacksPermission><br/>
- <shiro:lacksPermission name="user:query">不具有user:query權限用戶顯示此內容</shiro:lacksPermission><br/>
- <shiro:hasPermission name="user:query">所有用戶列表<br/></shiro:hasPermission>
- <ul>
- <c:forEach items="${userList }" var="user">
- <li><shiro:hasPermission name="user:query">用戶名:${user.username }</shiro:hasPermission>
- <shiro:hasPermission name="user:query">----密碼:${user.password }</shiro:hasPermission>
- <shiro:hasPermission name="user:update">----<a href="/user/edit.html?id=${user.id}">修改用戶</a></shiro:hasPermission>
- <shiro:hasPermission name="user:del">----<a href="javascript:void(0);" class="del" ref="${user.id }">刪除用戶</a></shiro:hasPermission>
- </li>
- </c:forEach>
- </ul>
- <script type="text/javascript">
- $(function () {
- $(".del").click(function () {
- var id = $(this).attr("ref");
- $.ajax({
- type: "GET",
- url: "/user/del.html?id="+id,
- success: function (e) {
- alert("刪除成功(不是真刪除,測試而已)");
- },
- error: function(json){
- alert("刪除失敗");
- }
- });
- });
- });
- </script>
- </body>
- </html>
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <html>
- <head>
- <title></title>
- </head>
- <body>
- url權限控制:
- 有此權限功能
- </body>
- </html>
403頁面
- <%@ page language="java" pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>權限錯誤</title>
- </head>
- <body>
- <h1>403,You don't have permission to access / on this server</h1>
- </body>
- </html>
靜態引用js
UserController.java
如果有對應權限,則會進入此Controller轉發到success.jsp,否則會被shiro轉發到403.jsp
- package com.etop.controller;
- import com.etop.basic.controller.BaseController;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
- /**
- * Created by Jeremie on 2014/10/3.
- */
- @Controller
- @RequestMapping("/user")
- public class UserController extends BaseController {
- //add,edit,del頁面並沒有寫具體邏輯,要驗證是否成功,需要觀察控制檯輸出。
- @RequestMapping("/add.html")
- public String addUser(){
- return "/success.jsp";
- }
- @RequestMapping("/edit.html")
- public String updateUser(int id){
- System.out.println("=========================================>要修改的id爲:" + id);
- return "/success.jsp";
- }
- @ResponseBody
- @RequestMapping(value = "/del.html",produces = "text/html; charset=utf-8",method= RequestMethod.GET)
- public String deleteUser(String id){
- System.out.println("=========================================>要刪除的id爲:" + id);
- return "";
- }
- }
HomeController.java
處理用戶登錄,用戶登出
其中SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));爲登錄方法
這是shiro的驗證的順序:
- package com.etop.controller;
- import javax.validation.Valid;
- import com.etop.basic.controller.BaseController;
- import com.etop.service.UserService;
- import org.apache.commons.lang.StringUtils;
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.AuthenticationException;
- import org.apache.shiro.authc.UsernamePasswordToken;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.validation.BindingResult;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import com.etop.pojo.User;
- import org.springframework.web.servlet.mvc.support.RedirectAttributes;
- import java.util.List;
- @Controller
- public class HomeController extends BaseController {
- @Autowired
- private UserService userService;
- @RequestMapping(value="/login.html",method=RequestMethod.GET,produces = "text/html; charset=utf-8")
- public String loginForm(Model model,String message){
- if(!StringUtils.isEmpty(message))
- model.addAttribute(message);
- model.addAttribute("user", new User());
- return "/login.jsp";
- }
- @RequestMapping(value="/login.html",method=RequestMethod.POST,produces = "text/html; charset=utf-8")
- public String login(@Valid User user,BindingResult bindingResult,Model model,RedirectAttributes attr){
- try {
- if(bindingResult.hasErrors()){
- addMessage(attr, "用戶名或密碼錯誤");
- return "redirect:/login.html";
- }
- //使用shiro管理登錄
- SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
- //獲取所有用戶信息,權限由前端shiro標籤控制
- List<User> userList = userService.getAllUser();
- model.addAttribute("userList", userList);
- return "/user.jsp";
- } catch (AuthenticationException e) {
- addMessage(attr, "用戶名或密碼錯誤");
- return "redirect:/login.html";
- }
- }
- @RequestMapping(value="/logout.html",method=RequestMethod.GET)
- public String logout(RedirectAttributes attr){
- //使用權限管理工具進行用戶的退出,註銷登錄
- SecurityUtils.getSubject().logout();
- addMessage(attr, "您已安全退出");
- return "redirect:/login.html";
- }
- @RequestMapping("/403.html")
- public String unauthorizedRole(){
- return "/403.jsp";
- }
- }
五、補充內容,有關網頁權限的註解配置,詳見項目第二個分支
po主幾經波折,終於把使用註解的方式來通過權限來控制url的訪問給弄好了。
首先附上經過修改後的userController註解版(具體用法)
- package com.etop.controller;
- import com.etop.basic.controller.BaseController;
- import org.apache.shiro.authz.UnauthorizedException;
- import org.apache.shiro.authz.annotation.RequiresPermissions;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
- /**
- * 處理用戶操作的控制器
- *
- * Created by Jeremie on 2014/10/3.
- */
- @Controller
- @RequestMapping("/user")
- public class UserController extends BaseController {
- @RequiresPermissions(value = "user:add")
- @RequestMapping("/add.html")
- public String addUser() throws UnauthorizedException {
- return "/success.jsp";
- }
- @RequiresPermissions(value = "user:edit")
- @RequestMapping("/edit.html")
- public String updateUser(int id){
- System.out.println("=========================================>要修改的id爲:" + id);
- return "/success.jsp";
- }
- @RequiresPermissions(value = "user:del")
- @ResponseBody
- @RequestMapping(value = "/del.html",produces = "text/html; charset=utf-8",method= RequestMethod.GET)
- public String deleteUser(String id){
- System.out.println("=========================================>要刪除的id爲:" + id);
- return "";
- }
- }
備註:(附各種權限的註解)
1)@RequiresAuthentication 需要通過驗證後的權限
2)@RequiresGuest 遊客權限可以通過
3)@RequiresPermissions("user:add") 需要當前角色擁有user:add權限才能通過
4)@RequiresRoles("admin") 需要admin角色才能通過
5)@RequiresUser 需要有已知身份的角色才能通過
6)若不具備權限,程序會拋出UnauthorizedException,可以對其進行捕獲處理(這裏的處理是跳到403頁面)
- @ExceptionHandler(Exception.class)
- public String handleException(Exception ex, HttpServletRequest request){
- if(ex instanceof UnauthorizedException){
- log.error("當前用戶沒有此權限");
- return "/403.jsp";
- }else {
- log.error("系統發生異常", ex);
- ex.printStackTrace();
- request.setAttribute("exMsg", ex.getMessage());
- return "errors/exception";
- }
- }
相關配置(重要,po主就是因爲漏了這段配置,一直沒能成功截獲):
springmvc_servlet.xml:
- <context:component-scan base-package="com.etop" use-default-filters="false">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
- <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
- </context:component-scan>
- <aop:config proxy-target-class="true"></aop:config>
- <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
- <property name="securityManager" ref="securityManager"/>
- </bean>
- <context:component-scan base-package="com.etop.controller"/>
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
掃描Controller的時候,必須把shiro的註解部分進行掃描
另外,分支二加入了緩存的配置方式,具體在項目中體現。
最後,爲方便大家,再次附上項目鏈接:
項目:https://git.oschina.net/jeremie_astray/SpringMVC_Shiro/tree/master/
Annotion版本:https://git.oschina.net/jeremie_astray/SpringMVC_Shiro/tree/shiro_annotation