> 文档中心 > 可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了


shiro简介:

shiro是apache提供的一个强大易用的Java安全框架,用于身份验证、授权、密码学和会话管理。下载源码。

开发环境及技术:

1、mysql - 5.7.21
2、navicat(mysql客户端管理工具)
3、eclipse
4、jdk9
5、tomcat 8.5
6、spring & springmvc
7、mybatis 3
8、shiro
9、maven

现在直接开始建项目,从项目中具体讲解shiro的使用,项目虽简单,却五脏俱全。

一、数据库设计:

数据库有三张表,分别是tb_user用户表,tb_role角色表,tb_permission权限表。

1. tb_user
可能是最完整的spring整合shiro的demo了

设置外键rid关联tb_role
可能是最完整的spring整合shiro的demo了

2. tb_role
可能是最完整的spring整合shiro的demo了

3. tb_permission
可能是最完整的spring整合shiro的demo了

设置外键关联tb_role
可能是最完整的spring整合shiro的demo了

二、项目环境搭建:

1. 新建maven web app,结构如下:
可能是最完整的spring整合shiro的demo了
2. 接下来就是添加依赖:

<dependencies> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- 添加Servlet支持 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <!-- 添加jtl支持 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 添加Spring支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.14.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2</version> </dependency>     <!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version> </dependency> <!-- 添加日志支持 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency>  <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency>  <!-- c3p0数据源 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>     <!-- shiro相关 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency>  </dependencies>

依赖对应的作用在代码中有简要的注释。

3. 完成配置文件:

在spring文件夹下有:
spring-dao.xml,
spring-mvc.xml,
spring-service.xml,
spring-shiro.xml

①spring-dao.xml

<!-- 配置整合mybatis过程 --> <!-- 1、配置数据库相关参数properties的属性:${url} --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 2、配置数据库连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置连接池属性 --> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- c3p0连接池的私有属性 --> <property name="maxPoolSize" value="30" /> <property name="minPoolSize" value="10" /> <!-- 关闭连接不自动commit --> <property name="autoCommitOnClose" value="false" /> <!-- 获取连接超时时间 --> <property name="checkoutTimeout" value="10000" /> <!-- 当获取连接失败时重试次数 --> <property name="acquireRetryAttempts" value="2" /> </bean>   <!-- 3、配置mybatis的sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自动扫描mappers.xml文件 --> <property name="mapperLocations" value="classpath:mappers/*.xml" /> <!-- mybatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> </bean> <!-- 4、DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.zhu.shiroweb.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>

②spring-service.xml

<!-- 自动扫描 --><context:component-scan base-package="com.zhu.shiroweb.service" />

③spring-mvc.xml

 <!-- 配置springmvc --><!-- 1、开启springMvc注解模式 --><mvc:annotation-driven /><!-- 2、视图解析器 --><bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/" /><property name="suffix" value=".jsp"></property></bean><!-- 3、开启Shiro注解 --><beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"depends-on="lifecycleBeanPostProcessor" /><beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager" /></bean> <!-- 4、扫描web相关的bean --><context:component-scan base-package="com.zhu.shiroweb.controller"/>

④spring-shiro.xml

<!-- 1、将自定义Realm加入IOC容器 --><bean id="myRealm" class="com.zhu.shiroweb.realm.MyRealm"> <!-- 配置MD5加密,若不进行MD5加密,这段代码不用 -->    <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">     <!-- MD5加密 -->     <property name="hashAlgorithmName" value="MD5"/>     <!-- 加密次数 -->     <property name="hashIterations" value="1024"/> </bean>    </property>     <!-- 配置MD5加密,若不进行MD5加密,这段代码不用 -->    </bean>  <!-- 2、配置安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">      <property name="realm" ref="myRealm"/>  </bean>  <!-- 3、配置Shiro过滤器,id名必须和web.xml中的过滤器名一致 --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">      <!-- Shiro的核心安全接口,这个属性是必须的 -->      <property name="securityManager" ref="securityManager"/>    <!-- 身份认证失败,则跳转到登录页面的配置 -->      <property name="loginUrl" value="/login.jsp"/>    <!-- 权限认证失败,则跳转到指定页面 -->      <property name="unauthorizedUrl" value="/unauthor.jsp"/>      <!-- Shiro连接约束配置,即过滤链的定义 -->      <property name="filterChainDefinitions">   <value>/login=anon/admin/**=authc,roles[admin]           

以下配置文件在resources根目录:
⑤jdbc.properties

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql:///#?useUnicode=true&characterEncoding=utf8jdbc.username=#jdbc.password=#

⑥log4j.properties

log4j.rootLogger=DEBUG, Console    #Console  log4j.appender.Console=org.apache.log4j.ConsoleAppender  log4j.appender.Console.layout=org.apache.log4j.PatternLayout  log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n    log4j.logger.java.sql.ResultSet=INFO  log4j.logger.org.apache=INFO  log4j.logger.java.sql.Connection=DEBUG  log4j.logger.java.sql.Statement=DEBUG  log4j.logger.java.sql.PreparedStatement=DEBUG  

⑦mybatis-config.xml

<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><typeAliases><package name="com.zhu.shiroweb.entity"/></typeAliases></configuration>

web.xml在WEB-INF目录下:
⑧web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1" metadata-complete="true"><display-name>shiroweb</display-name><welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/spring-*.xml</param-value></context-param><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:spring/spring-mvc.xml</param-value></init-param><load-on-startup>1</load-on-startup><async-supported>true</async-supported></servlet><servlet-mapping><servlet-name>springMVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><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><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><async-supported>true</async-supported><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping></web-app>

至此已完成所有配置,所有的配置文件核心代码都写了注释。

三、项目功能实现

登录验证:

项目功能描述:
在数据库中有两个用户,一个tom,角色为admin,对应的权限有create,delete,query和update,另一个用户cat,角色为guest,权限只有create和query。

1. 首先新建User实体类(set、get方法略):
User.java

public class User {private Integer uid;private String userName;private String password;}

2. dao层的开发
UserDao.java

public interface UserDao {/** * 根据用户名查询用户 * @param userName * @return */public User getByUserName(String userName);/** * 根据用户名查询角色 * @param userName * @return */public Set<String> getRoles(String userName);/** * 根据用户名查询权限 * @param userName * @return */public Set<String> getPermissions(String userName);}

UserDao.xml

<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.zhu.shiroweb.dao.UserDao"><resultMap type="com.zhu.shiroweb.entity.User" id="UserResult"><result property="uid" column="uid"/><result property="userName" column="user_name"/><result property="password" column="pass_word"/></resultMap><select id="getByUserName" parameterType="String" resultMap="UserResult">select * from tb_user where user_name=#{userName}</select><select id="getRoles" parameterType="String" resultType="String">select r.role_name    from tb_user u,tb_role r     where u.rid=r.rid     and u.user_name=#{userName}</select><select id="getPermissions" parameterType="String" resultType="String">select p.permission_name from tb_user u,tb_role r,tb_permission p where u.rid=r.rid and p.rid=r.rid and u.user_name=#{userName}</select></mapper> 

注意: 这里并没有role和permission对应的实体类,也可以新建其对应的实体类,然后把他们设置为User的成员变量,用List集合装载。但是这样做更麻烦一点,因为等下shiro要用到的就是Set集合,如果是List等下还需做转换。

3. dao层测试
写到这可以做一下junit测试,由于篇幅原因,此处不再赘述。

4. service层开发
由于并没有增加逻辑,只是简单调用dao层,所以不再说明。

5. 自定义realm
MyRealm.java

public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/** * 为登录用户授予权限和角色 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String userName = (String) principals.getPrimaryPrincipal();SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();authorizationInfo.setRoles(userService.getRoles(userName));authorizationInfo.setStringPermissions(userService.getPermissions(userName));return authorizationInfo;}/** * 验证当前登录用户 */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String userName = (String) token.getPrincipal();User user = userService.getByUserName(userName);//使用md5加密//当前realm对象的nameString realmName = getName();//盐值ByteSource credenttialsSalt = ByteSource.Util.bytes(user.getUserName());//封装用户信息,构建AuthenticationInfo对象并返回AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(),credenttialsSalt, realmName);return authcInfo;//使用md5加密//不加密/* * if (user != null) {  *     AuthenticationInfo authcInfo = new *     SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "xx"); *      return authcInfo;  * } else {  *      return null; * } *///不加密}

这个类就验证登录和为用户授权两个方法,代码中已有注释说明。若不使用MD5加密,则把我写了MD5加密注释之间那段代码注释掉,把下面注释放开就行;若要使用MD5加密,数据库中的密码也得是加密后的密码可以通过下面的方法获取明文密码对应的密文密码。
获取密文的方法:

public static void main(String[] args) {String hashAlgorithName = "MD5";String password = "5678";int hashIterations = 1024;ByteSource credentialsSalt = ByteSource.Util.bytes("cat");Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);System.out.println(obj);}

6. 用户登录的Controller

@Controller@RequestMapping("/user")public class UserController {/** * * @param user * @param request * @return */@RequestMapping("/login")public String login(User user,HttpServletRequest request){//获取当前登录用户Subject subject=SecurityUtils.getSubject();//封装表单中提交的用户名和密码UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(), user.getPassword());try{//调用login方法,传入封装好的tokensubject.login(token);//登录成功跳转success.jspreturn "redirect:/success.jsp";}catch(Exception e){e.printStackTrace();//登录失败就重新登录request.setAttribute("errorMsg", "登录失败");return "login";}}}

7. login.jsp

<!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><form action="${pageContext.request.contextPath }/user/login" method="post">userName:<input type="text" name="userName" value="${user.userName }"/><br/>password:<input type="password" name="password" value="${user.password }"><br/><input type="submit" value="login"/><font color="red">${errorMsg }</font></form></body></html>

过程梳理:
在controller中获取到前端表单输入的用户名和密码,封装到token中,然后调用subject的login的方法,传入这个token,这个token就会把数据带到realm中,realm中再调service层查询,根据前端token中携带的用户名去查询数据库中记录,进行密码比对。

这里解释spring-shiro.xml中/login=anon的作用,这行代码的意思就是不拦截登录方法,可以匿名访问。

以上就是验证登录的整个流程。

授权:

需求描述:
1. 指定角色:
AdminController只有具有admin角色(tom)才能访问;
GuestController只有具有guest角色(cat)才能访问;

2. 指定权限:
PermissionController只有具有create权限(tom和cat)的用户才能访问;

涉及知识点:
1. 在spring-shiro.xml中进行授权验证
2. 注解方式授权验证
3. jsp中授权验证
4. 多级路由匹配规则

正式开始:

1. AdminController.java

@Controller@RequestMapping("/admin")public class AdminController {/** * * @param user * @param request * @return */@RequestMapping("/test")public String adminTest() {//Subject subject = SecurityUtils.getSubject();//if(subject.hasRole("admin")) {return "admin";//}else {//return "login";//}}}

这个授权验证写在spring-shiro.xml中: /admin/**=authc,roles[admin],这样就表示要有admin这个角色的用户才能访问。同时这个也是双重路由,/admin/**就表示拦截admin开头的路由。

2. GuestController.java

@Controllerpublic class GuestController {@RequiresRoles("guest")@RequestMapping("/guest")public String guestTest() {return "guest";}}

这个是使用注解方式进行权限验证的,@RequiresRoles("guest")就表示要有guest这个角色才能访问这个路由。

3. PermissionsController.java

@Controllerpublic class PermissionsController {/** * * @param user * @param request * @return */@RequestMapping("/permissions")public String permissionTest(){Subject subject = SecurityUtils.getSubject();if(subject.isPermitted("create")) {return "permission";}else {return "login";}}}

这个是直接在Controller中获取到登录用户,然后用Subject对象的isPermitted方法就行判断。subject.isPermitted("create")用来判断登录的用户是否有create权限,有就true,没有就是false。

4. 接下来看看前端页面:

①success.jsp
登录成功跳转到这个页面

<!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>欢迎你!<br><a href="http://localhost:8080/shiroweb/admin/test">有admin角色才能访问</a><br><a href="http://localhost:8080/shiroweb/permissions">有create权限就可以访问</a><br><a href="http://localhost:8080/shiroweb/guest">guest才能访问</a></body></html>

这里定义了三个链接,分别指向上面那三个controller。

②admin.jsp

<!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>  欢迎有admin角色的你!</body></html>

③guest.jsp

    <!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>   <shiro:hasRole name="guest"> 欢迎guest!(只有guest角色登录进来才能显示这段话)<shiro:principal/>    </shiro:hasRole> <shiro:hasRole name="admin"> 欢迎admin!(只有admin角色登录进来才能显示这段话)<shiro:principal/>    </shiro:hasRole></body></html>

这里就用到了jsp标签形式判断角色, xxx就是用来判断当前用户有没有#角色,有#角色才会显示xxx内容。

注意: 使用shiro的jsp标签要在jsp页面中添加

④permission.jsp

<!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>  欢迎有你! <shiro:hasPermission name="delete">     只有拥有delete权限的人登录进来才能显示这段话.<shiro:principal/>    </shiro:hasPermission></body></html>

这里用到了xxx来判断权限,只有具有#权限的用户才会显示xxx内容。

⑤unauthor.jsp

    <!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>  对不起,你没有操作权限!  <a href="login.jsp">点此登录</a></body></html>

权限认证失败时跳转到此页面,因为在spring-shiro.xml中配置过。

四、项目测试

1. 用tom登录
分析: tom具有admin角色和所有权限,所以success.jsp页面的前两个链接可以访问,且permission.jsp的标签里面的话会显示。第三个链接不能访问,会抛出异常,因为需要guest角色才能访问。
结果:
可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了

2. 用cat登录
分析: cat具有guest角色和create 以及query权限,所以success.jsp的第一个链接不能访问,会跳转到unauthor.jsp,第二个链接可以访问,但是不能显示标签里面的话,第三个链接可以访问,且能显示里面的话。

结果:

可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了

可能是最完整的spring整合shiro的demo了

所有结果符合预期,测试通过!

五、知识点补充

1. url匹配规则:

/admin?=authc

表示admin1需要认证,admin2也需要,但是admin不一定,因为问号表示单个字符

/admin*=authc

表示admin1需要认证,admin21需要,admin也需要,*号表示一个或多个字符

/admin/**=authc

可以匹配多路径,比如admin/a/b

2. Subject对象除了isPermitted(“#”)判断是否拥有#权限,还有hasRole(“#”)判断是否有#角色,hasRoles(Arrays.asList(“role1”,“role2”))来判断是否有role1和role2角色。

3. 其他注释:

@RequiresAuthentication
表示要验证通过才能被访问

@RequiresGuest
表示之前session中没有被验证过才能访问

@RequiresPermissions(“create”)
表示有create权限才能访问

@RequiresRoles(“admin”)
只有admin这个角色才能访问

@RequiresUser
指定用户才能访问