以前就一直想学Shiro怎么使用,但一直没动力学,这次因为项目中要用,没办法就去学了。其实Shiro还是挺简单的,而且用着也很方便。例子是一个关于用户角色权限的例子,用户与角色,角色与权限均为多对多的关系。本次例子是Maven搭建,框架使用全注解方式。个人习惯用一些版本比较新的框架,一下是各框架版本
Spring版本:4.1.6
Hibernate版本:4.5.2
Shiro版本:1.3.2
数据连接:druid(注:其实druid和dbcp很像,基础配置基本相同)
jdbc.properties
# MySQL
hibernate.mysql.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
db.mysql.driverClassName=com.mysql.jdbc.Driver
# ssh
db.mysql.ssh.url=jdbc\:mysql\://127.0.0.1\:3306/test?useUnicode\=true&characterEncoding\=utf-8
db.mysql.ssh.username=root
db.mysql.ssh.password=root
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update
hibernate.cache.use_second_level_cache=false
hibernate.cache.use_query_cache=false
hibernate.jdbc.batch_size=50
# DBCP property
db.dbcp.maxActive=100
db.dbcp.maxIdle=30
db.dbcp.minIdle=5
db.dbcp.maxWait=5000
# DRUID property
db.druid.filters=stat
db.druid.maxActive=100
db.druid.initialSize=1
db.druid.maxIdle=300
db.druid.minIdle=5
db.druid.maxWait=5000
db.druid.timeBetweenEvictionRunsMillis=3600000
db.druid.minEvictableIdleTimeMillis=3600000
db.druid.validationQuery=SELECT 'x'
db.druid.testWhileIdle=true
db.druid.testOnBorrow=false
db.druid.testOnReturn=false
db.druid.poolPreparedStatements=true
db.druid.maxPoolPreparedStatementPerConnectionSize=50
applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
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
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 声明自动为spring容器中那些配置@Aspectj切面的bean创建代理,织入切面 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 支持事物注解(@Transactional) -->
<tx:annotation-driven/>
<!-- 加载配置文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:config/jdbc.properties</value>
</property>
</bean>
<!-- begin -->
<!-- 这几行代码原本可以放在 springmvc 的配置文件中,但在加入 Shiro 权限框架后必须放在 Spring 的配置文件中,否则在 realm 中获取不到 service -->
<context:annotation-config />
<context:component-scan base-package="com.test">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>
<!-- end -->
<!-- 配置sql慢的标准,超过5秒就是慢 -->
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="5000" />
<property name="logSlowSql" value="true" />
</bean>
<!-- sql注入攻击配置 -->
<bean id="wall-filter-config" class="com.alibaba.druid.wall.WallConfig" init-method="init">
<!-- 指定配置装载目录 -->
<property name="dir" value="META-INF/druid/wall/mysql" />
</bean>
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
<property name="dbType" value="mysql" />
<property name="config" ref="wall-filter-config" />
</bean>
<!-- 加载数据源 -->
<!-- SSH数据库 -->
<!-- druid连接方式 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${db.mysql.ssh.url}"/>
<property name="username" value="${db.mysql.ssh.username}"/>
<property name="password" value="${db.mysql.ssh.password}"/>
<property name="maxActive" value="${db.druid.maxActive}"/><!-- 最大连接池数量 -->
<!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 -->
<property name="initialSize" value="${db.druid.initialSize}" />
<property name="maxIdle" value="${db.druid.maxIdle}"/><!-- 最大空闲数,数据库连接的最大空闲时间。超出空闲时间该连接被释放 -->
<property name="minIdle" value="${db.druid.minIdle}"/><!-- 最小空时间 -->
<property name="maxWait" value="${db.druid.maxWait}"/><!-- 最大等待毫秒,单位为:ms。超出时间会出错误信息 -->
<!-- 有两个含义:
1) Destroy线程会检测连接的间隔时间
2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
-->
<property name="timeBetweenEvictionRunsMillis" value="${db.druid.timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${db.druid.minEvictableIdleTimeMillis}" />
<!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、 testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="${db.druid.validationQuery}" />
<!-- 建议配置为true,不影响性能,并且保证安全性。 申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis, 执行validationQuery检测连接是否有效。 -->
<property name="testWhileIdle" value="${db.druid.testWhileIdle}" />
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。默认为true -->
<property name="testOnBorrow" value="${db.druid.testOnBorrow}" />
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,默认为true -->
<property name="testOnReturn" value="${db.druid.testOnReturn}" />
<!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。 在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。默认false -->
<property name="poolPreparedStatements" value="${db.druid.poolPreparedStatements}" />
<!-- 指定每个连接上PSCache的大小 -->
<property name="maxPoolPreparedStatementPerConnectionSize" value="${db.druid.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 属性类型是字符串,通过别名的方式配置扩展插件,
常用的插件有:
监控统计用的filter:stat
日志用的filter:log4j
防御sql注入的filter:wal
-->
<property name="filters" value="${db.druid.filters}"/>
<property name="proxyFilters">
<list>
<ref bean="stat-filter"/>
<ref bean="wall-filter"/>
</list>
</property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan">
<list>
<value>com.test.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<!-- 方言 -->
<prop key="hibernate.dialect">${hibernate.mysql.dialect}</prop>
<!-- 是否打印SQL语句 -->
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<!-- 是否开启二级缓存 -->
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
<!-- 是否开启缓存查询 -->
<prop key="hibernate.cache.use_query_cahe">${hibernate.cache.use_query_cache}</prop>
<!-- 数据库批量增删改操作的最大数 -->
<prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop>
<!-- 设置自动更新|创建|验证表结构 -->
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<!-- hiberante4.x的session创建方式 -->
<!-- 使用本地事物 -->
<prop key="current_session_context_class">thead</prop>
<!-- 使用全局事物 -->
<!-- <prop key="current_session_context_class">jta</prop> -->
<!--<prop key="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</prop>-->
</props>
</property>
</bean>
<!-- 声明式事物 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置事物异常封装 -->
<bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<aop:config>
<aop:pointcut id="productServiceMethods" expression="execution(* com.test.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="create*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="destroy*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 必须加入 -->
<!-- 注解事物 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
这里要特别说明一下
<context:annotation-config />
<context:component-scan base-package="com.test">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>
这段代码在不是用Shiro的时候,放在SpringMVC的配置文件中也是可以的,但使用了Shiro后必须放在Spring的配置文件中,具体原因现在不是很清楚
springmvc-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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
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
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 避免异步后中文乱码 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="writeAcceptCharset" value="false"/><!-- 避免响应头过大 -->
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>text/json;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>application/xml;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!-- <context:component-scan base-package="com.test.contorller" /> -->
<context:component-scan base-package="com.test.contorller" />
<!-- 添加注解驱动 -->
<mvc:annotation-driven/>
<!-- 视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 上传文件相关的配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8" />
<property name="maxUploadSize" value="104857600" />
<property name="maxInMemorySize" value="4096" />
</bean>
</beans>
applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
<!--自定义的Roles Filter-->
<!-- 注意:下面如果这样配置:/**/del/**=roles["管理员,总经理"] Shiro 默认表示用户同时具有这两个角色才能访问,但我们往往需要只要满足其中一个即可,这时就需要重写拦截器 -->
<bean id="anyRoles" class="com.test.shiro.CustomRolesAuthorizationFilter" />
<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="/inde.jsp"/>
<property name="unauthorizedUrl" value="/403.jsp"/>
<!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截 -->
<!-- 这里对路径中包含 del 的请求地址只有拥有“管理员”或“总经理”角色的用户才能访问 -->
<property name="filterChainDefinitions">
<value>
/index.jsp=authc
/menu.jsp=authc
/**/del/**=anyRoles["管理员,总经理"]
/logout.jsp = logout
</value>
</property>
</bean>
<!-- 配置进行授权和认证的 Realm -->
<bean id="myRealm" class="com.testbt.shiro.ShiroDbRealm">
<!-- MD5加密 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property> <!-- 加密算法的名称 -->
<property name="hashIterations" value="32"></property> <!-- 配置加密的次数 -->
<!-- true:指定Hash散列值使用Hex加密存;false:表明hash散列值用用Base64-encoded存储 -->
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
</property>
</bean>
<!-- 配置 Shiro 的 SecurityManager Bean. -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="myRealm"/>
<property name="sessionMode" value="native"/>
</bean>
<!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 配置缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="cacheManagerEhcache"/>
</bean>
<!-- cacheManager, 指定ehcache-shiro.xml的位置 -->
<bean id="cacheManagerEhcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>classpath:config/ehcache-shiro.xml</value>
</property>
<!-- 由于hibernate也使用了Ehcache, 保证双方都使用同一个缓存管理器 -->
<property name="shared" value="true"/>
</bean>
</beans>
ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shirocache">
<diskStore path="java.io.tmpdir"/>
<!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro_cache"
maxElementsInMemory="2000"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
maxElementsOnDisk="0"
overflowToDisk="true"
memoryStoreEvictionPolicy="FIFO"
statistics="true">
</cache>
</ehcache>
不需要缓存的可以直接把applicationContext-shiro.xml中关于配置缓存的代码删掉即可
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>SSH</display-name>
<!-- Spring过滤器 -->
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>SSH</param-value>
</context-param>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:config/applicationContext.xml
classpath:config/applicationContext-shiro.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.htl</url-pattern>
</servlet-mapping>
<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>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/500.html</location>
</error-page>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
实体类
UserInfo.java
package com.test.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
/**
* UserInfo entity. @author Wangbt
*/
@Entity
@Table(name = "user_info", catalog = "test")
public class UserInfo implements java.io.Serializable {
// Fields
/**
*
*/
private static final long serialVersionUID = 40482158759345958L;
private String id;
private String account;
private String password;
private String userName;
private Integer age;
private Integer sex;
private List<Role> roles;
// Constructors
/** default constructor */
public UserInfo() {
}
/** minimal constructor */
public UserInfo(String id, String account, String password) {
this.id = id;
this.account = account;
this.password = password;
}
/** full constructor */
public UserInfo(String id, String account, String password,
String userName, Integer age, Integer sex, List<Role> roles) {
this.id = id;
this.account = account;
this.password = password;
this.userName = userName;
this.age = age;
this.sex = sex;
this.roles = roles;
}
// Property accessors
@Id
@Column(name = "id", unique = true, nullable = false, length = 36)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
@Column(name = "account", nullable = false, length = 36)
public String getAccount() {
return this.account;
}
public void setAccount(String account) {
this.account = account;
}
@Column(name = "password", nullable = false, length = 18)
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(name = "user_name", length = 225)
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Column(name = "age")
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
@Column(name = "sex")
public Integer getSex() {
return this.sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
@ManyToMany(cascade=CascadeType.REFRESH,fetch=FetchType.EAGER)
@JoinTable(name = "user_role",
joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")})
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Transient
public List<String> getRoleName() {
List<Role> roles = getRoles();
List<String> list = new ArrayList<String>();
for (Role role : roles) {
list.add(role.getRoleName());
}
return list;
}
}
Role.java
package com.test.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
/**
* Role entity. @author Wangbt
*/
@Entity
@Table(name = "role", catalog = "test")
public class Role implements java.io.Serializable {
// Fields
/**
*
*/
private static final long serialVersionUID = 6439880715734914582L;
private String id;
private String roleName;
private String parentId;
private String describe;
private List<UserInfo> userInfos;
private List<Authority> authorities;
// Constructors
/** default constructor */
public Role() {
}
/** minimal constructor */
public Role(String id) {
this.id = id;
}
/** full constructor */
public Role(String id, String roleName, String parentId, String describe,
List<UserInfo> userInfos, List<Authority> authorities) {
this.id = id;
this.roleName = roleName;
this.parentId = parentId;
this.describe = describe;
this.userInfos = userInfos;
this.authorities = authorities;
}
// Property accessors
@Id
@Column(name = "id", unique = true, nullable = false, length = 36)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
@Column(name = "role_name", length = 225)
public String getRoleName() {
return this.roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@Column(name = "parent_id", length = 26)
public String getParentId() {
return this.parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
@Column(name = "describe", length = 225)
public String getDescribe() {
return this.describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
@ManyToMany
@JoinTable(name = "user_role",
joinColumns = {@JoinColumn(name = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "user_id")})
public List<UserInfo> getUserInfos() {
return userInfos;
}
public void setUserInfos(List<UserInfo> userInfos) {
this.userInfos = userInfos;
}
@ManyToMany
@JoinTable(name = "role_authority",
joinColumns = {@JoinColumn(name = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_id")})
public List<Authority> getAuthorities() {
return authorities;
}
public void setAuthorities(List<Authority> authorities) {
this.authorities = authorities;
}
@Transient
public List<String> getAuthorityName() {
List<String> list = new ArrayList<String>();
List<Authority> authorities = getAuthorities();
for (Authority auth : authorities) {
list.add(auth.getAuthName());
}
return list;
}
}
Authority.java
package com.test.entity;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
/**
* Authority entity. @author Wangbt
*/
@Entity
@Table(name = "authority", catalog = "test")
public class Authority implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 4091626000965414186L;
// Fields
private String id;
private String authName;
private String parenntId;
private List<Role> roles;
// Constructors
/** default constructor */
public Authority() {
}
/** minimal constructor */
public Authority(String id) {
this.id = id;
}
/** full constructor */
public Authority(String id, String authName, String parenntId, List<Role> roles) {
this.id = id;
this.authName = authName;
this.parenntId = parenntId;
this.roles = roles;
}
// Property accessors
@Id
@Column(name = "id", unique = true, nullable = false, length = 36)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
@Column(name = "auth_name", length = 225)
public String getAuthName() {
return this.authName;
}
public void setAuthName(String authName) {
this.authName = authName;
}
@Column(name = "parennt_id", length = 36)
public String getParenntId() {
return this.parenntId;
}
public void setParenntId(String parenntId) {
this.parenntId = parenntId;
}
@ManyToMany
@JoinTable(name = "role_authority",
joinColumns = {@JoinColumn(name = "authority_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")})
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
Dao层
UserInfoDao.java
import com.test.entity.UserInfo;
public interface UserInfoDao {
public UserInfo findAccount(String account);
}
UserInfoDaoImpl.java
package com.test.dao.impl;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.test.dao.UserInfoDao;
import com.test.entity.UserInfo;
import com.test.util.BaseDao;
@Repository
public class UserInfoDaoImpl extends BaseDao<UserInfo, String> implements UserInfoDao {
@Autowired
private SessionFactory sessionFactory;
/**
* @return session
*/
public Session getSession(){
Session session = this.sessionFactory.getCurrentSession();
return session;
}
@Override
public UserInfo findAccount(String account) {
String hql = "from UserInfo where 1 = 1 and account = ?";
Query query = this.getSession().createQuery(hql);
query.setParameter(0, account);
UserInfo userInfo = (UserInfo) query.uniqueResult();
return userInfo;
}
}
Service层
UserInfoService.java
package com.test.service;
import com.test.entity.UserInfo;
public interface UserInfoService {
public UserInfo userLogin(String account);
}
UserInfoServiceImpl.java
package com.test.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.dao.UserInfoDao;
import com.test.entity.UserInfo;
import com.test.service.UserInfoService;
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserInfo login(String account) {
UserInfo user = this.userInfoDao.login(account);
if (user != null) {
return user;
}
return null;
}
}
接下来是最为关键的Realm的实现,用户的角色、权限主要通过这个类控制
ShiroDbRealm.java
package com.test.shiro;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.test.entity.Role;
import com.test.entity.UserInfo;
import com.test.service.UserInfoService;
import com.test.util.CryptographyUtil;
public class ShiroDbRealm extends AuthorizingRealm {
@Autowired
private UserInfoService userInfoService;
public static final String SESSIOIN_USER_KEY = "userInfo";
/**
* 授权查询回调函数,进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户访问控制的方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取登录时输入的帐号
//String account = (String) principalCollection.fromRealm(getName()).iterator().next();
UserInfo userInfo = (UserInfo) SecurityUtils.getSubject().getSession().getAttribute(ShiroDbRealm.SESSIOIN_USER_KEY);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 设置角色
info.addRoles(userInfo.getRoleName());
List<Role> roles = userInfo.getRoles();
List<String> auths = new ArrayList<>();
for (Role role : roles) {
List<String> list = role.getAuthority();
list.removeAll(auths);
auths.addAll(list);
}
// 设置权限
info.addStringPermissions(auths);
return info;
}
/**
* 认证回调函数,登录信息和用户信息验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
// 把 token 转成 user
UserInfo userInfo = tokenToUser((UsernamePasswordToken)authToken);
// 验证用户是否可以登录
// hashIterations 必须与配置文件中的一致,否则认证失败
String pwd = CryptographyUtil.md5(userInfo.getPassword(), "feicui", 32);
UserInfo user = userInfoService.login(userInfo.getAccount());
if (user == null)
return null;// 找不到数据
// 设置 session
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute(ShiroDbRealm.SESSIOIN_USER_KEY, user);
// 当前 Realm 的 name
String realmName = this.getName();
// 登录的主要信息:可以是一个实体类对象,但实体类的对象一定是根据 token 的 username 查询得到的
// Object principal = user.getAccount();
Object principal = authToken.getPrincipal();
// Shiro 通过 credentialsSalt 值给原始密码加密,最终比较两次密码是否一致
ByteSource credentialsSalt = ByteSource.Util.bytes("feicui");
return new SimpleAuthenticationInfo(principal, user.getPassword(), credentialsSalt, realmName);
}
/**
* 从 UsernamePasswordToken 中获取用户信息
* @param authToken
* @return
*/
private UserInfo tokenToUser(UsernamePasswordToken authToken) {
UserInfo user = new UserInfo();
user.setAccount(authToken.getUsername());
user.setPassword(String.valueOf(authToken.getPassword()));
return user;
}
}
CustomRolesAuthorizationFilter.java
package com.test.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
/**
* 重写 Shiro 拦截器
* @author Wangbt
*
*/
public class CustomRolesAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object object) throws Exception {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) object;
if (rolesArray == null || rolesArray.length == 0) //没有角色限制,有权限访问
return true;
for (int i = 0; i < rolesArray.length; i++) { //若当前用户是rolesArray中的任何一个,则有权限访问
if (subject.hasRole(rolesArray[i]))
return true;
}
return false;
}
}
UserInfoController.java
package com.test.contorller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
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.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.test.entity.UserInfo;
import com.test.service.UserInfoService;
@Controller
@RequestMapping(value = "/user/info", method = RequestMethod.POST)
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/**
* 登录
* @param account
* @param pwd
* @return
*/
@RequestMapping(value = "/login")
public String login(UserInfo userInfo, Model model) {
if (SecurityUtils.getSubject().getSession() != null)
SecurityUtils.getSubject().logout();
String res = loginUser(userInfo);
if (!"SUCC".equals(res)) {
model.addAttribute("msg", res);
return "redirect:/login.jsp";
}
return "redirect:/index.jsp";
}
/**
* 退出
* @return
*/
@RequestMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
private String loginUser(UserInfo user) {
if (isRelogin(user)) return "SUCC";// 如果已经登录,则无需再登录
return shiroLogin(user);
}
private String shiroLogin(UserInfo user) {
// 组装 token,包括用户名、密码、角色、权限等等
UsernamePasswordToken token = new UsernamePasswordToken(user.getAccount(), user.getPassword().toCharArray(), null);
token.setRememberMe(true);
// shiro 验证登录
try {
SecurityUtils.getSubject().login(token);;
} catch (UnknownAccountException ex){
return "帐号或密码错误";
} catch (IncorrectCredentialsException ex){
return "帐号不存在或者密码错误";
} catch (AuthenticationException ex) {
return ex.getMessage();
} catch (Exception e) {
return "内部错误,请重新尝试";
}
return "SUCC";
}
private boolean isRelogin(UserInfo user) {
Subject subject = SecurityUtils.getSubject();
return subject.isAuthenticated();// true:参数未改变,无需重新登录,默认为已经登录成功;false:需重新登录
}
}
后台代码总算是完了,接下来就是测试了
success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<% String path = request.getContextPath(); %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>提示信息:${msg }</h1>
<h1>帐号:${account }</h1>
<shiro:hasAnyRoles name="user">
<a href="<%=path %>/user.jsp">User Page</a>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="admin">
<a href="<%=path %>/admin.jsp">Admin Page</a>
</shiro:hasAnyRoles>
<shiro:hasPermission name="改">
<a href="<%=path %>/admin.jsp">auth Page</a>
</shiro:hasPermission>
</body>
</html>
登录成功后会调转到 success.jsp 页面,不同角色和权限的用户看到的连接也不同,获知直接访问这些页面,没有权限的用户会跳转到默认的页面,给出相应的提示。最后上一张项目结构图
下载地址: http://download.csdn.net/download/cainiao_accp/9945944
示例代码和下载代码有所不同,但下载代码比示例代码要更加严谨