how2j.cn

下载区
文件名 文件大小
lib.rar 12m
shiro.rar 12m
步骤 1 : 基于 SSM 的 SHIRO 项目   
步骤 2 : 表结构和表数据   
步骤 3 : 先运行,看到效果,再学习   
步骤 4 : 模仿和排错   
步骤 5 : 一步一步实现   
步骤 6 : 创建 web 动态项目   
步骤 7 : jar 包   
步骤 8 : web.xml   
步骤 9 : applicationContext.xml   
步骤 10 : applicationContext-shiro.xml   
步骤 11 : springMVC.xml   
步骤 12 : log4j.properties   
步骤 13 : PageController   
步骤 14 : index.jsp   
步骤 15 : listProduct.jsp   
步骤 16 : deleteProduct.jsp   
步骤 17 : deleteOrder.jsp   
步骤 18 : login.jsp   
步骤 19 : unauthorized.jsp   
步骤 20 : style.css   
步骤 21 : User 一套   
步骤 22 : Role 一套   
步骤 23 : Permission 一套   
步骤 24 : LoginController   
步骤 25 : DatabaseRealm   
步骤 26 : DefaultExceptionHandler   
步骤 27 : 缺陷   

步骤 1 :

基于 SSM 的 SHIRO 项目

edit
前面的知识点学习了 Web 支持(Servlet),也学习了数据库支持,接下来,在前面的基础之上,把项目改造成为SSM风格。
如果没有SSM基础,请先学习SSM相关教程:SSM 入门,否则一下接触太多的新知识,如下内容是不可能看得懂的。
SSM简单的说,就是springmvc负责web部分,mybatis负责数据库部分。
所以基于 ssm 的 shiro 项目,就是 Servlet部分,修改为了SpringMVC,数据库部分,修改为了Mybatis,然后把shiro集成进去,用这样一个思路来把本项目实现。
步骤 2 :

表结构和表数据

edit
本项目要跑起来,必须依赖 数据库支持 教程中的 表结构表数据,所以尽量先学习前面的教程。
步骤 3 :

先运行,看到效果,再学习

edit
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
启动之后,运行

http://127.0.0.1:8080/shiro/index

可以看到 Web 支持(Servlet) 里一样的效果
先运行,看到效果,再学习
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。

推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
步骤 5 :

一步一步实现

edit
ssm 本身就有一定的复杂性,涉及的内容很多,所以一定要先先运行,看到效果,再学习,不然自己一不留神做错了,都不知道哪里错了,回头还怪项目本身有问题。
步骤 6 :

创建 web 动态项目

edit
创建 web 动态项目,位于 e:\project\shiro 这个位置
创建 web 动态项目
jar 包放在右上角的lib.rar里,解压后复制到 WebContent/WEB-INF/lib
jar 包
web.xml做了如下几件事情
1. 指定spring的配置文件有两个

applicationContext.xml: 用于链接数据库的
applicationContext-shiro.xml: 用于配置shiro的

2. 指定springmvc的配置文件

springMVC.xml

3. 使用shiro过滤器

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<?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" xmlns:web="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" version="2.5"> <!-- spring的配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml, classpath:applicationContext-shiro.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- spring mvc核心:分发servlet --> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- spring mvc的配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- Shiro配置 --> <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>
步骤 9 :

applicationContext.xml

edit
1. 配置数据库的相关信息
2. 扫描mybatis的mapper什么的,虽然目前还没有提供mapper类
<?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:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:annotation-config /> <context:component-scan base-package="com.how2java.service" /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value>admin</value> </property> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="typeAliasesPackage" value="com.how2java.pojo" /> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:com/how2java/mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.how2java.mapper"/> </bean> </beans>
步骤 10 :

applicationContext-shiro.xml

edit
提供shiro的相关配置,简单的说,就是把shiro.ini里的内容搬到这个xml文件里面来了,只是写法不同
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 配置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" /> <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 --> <property name="unauthorizedUrl" value="/unauthorized" /> <!-- 退出 --> <property name="filters"> <util:map> <entry key="logout" value-ref="logoutFilter" /> </util:map> </property> <!-- 权限配置 --> <property name="filterChainDefinitions"> <value> <!-- anon表示此地址不需要任何权限即可访问 --> /login=anon /index=anon /static/**=anon /doLogout=logout <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过登录验证,如果未登录则跳到/login --> /** = authc </value> </property> </bean> <!-- 退出过滤器 --> <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="/index" /> </bean> <!-- 会话ID生成器 --> <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" /> <!-- 会话Cookie模板 关闭浏览器立即失效 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid" /> <property name="httpOnly" value="true" /> <property name="maxAge" value="-1" /> </bean> <!-- 会话DAO --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <property name="sessionIdGenerator" ref="sessionIdGenerator" /> </bean> <!-- 会话验证调度器,每30分钟执行一次验证 ,设定会话超时及保存 --> <bean name="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler"> <property name="interval" value="1800000" /> <property name="sessionManager" ref="sessionManager" /> </bean> <!-- 会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- 全局会话超时时间(单位毫秒),默认30分钟 --> <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" /> <property name="sessionIdCookieEnabled" value="true" /> <property name="sessionIdCookie" ref="sessionIdCookie" /> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="databaseRealm" /> <property name="sessionManager" ref="sessionManager" /> </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> <bean id="databaseRealm" class="com.how2java.realm.DatabaseRealm"> </bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> </beans>
1. springmvc的基本配置
2. 增加了对shiro的支持
这样可以在控制器Controller上,使用像@RequireRole 这样的注解,来表示某个方法必须有相关的角色才能访问
3. 指定了异常处理类DefaultExceptionHandler,这样当访问没有权限的资源的时候,就会跳到统一的页面去显示错误信息
<?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:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <context:annotation-config/> <context:component-scan base-package="com.how2java.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <mvc:annotation-driven /> <mvc:default-servlet-handler /> <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" /> </bean> <!--启用shiro注解 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean> <!-- 控制器异常处理 --> <bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"> </bean> <bean class="com.how2java.exception.DefaultExceptionHandler"/> </beans>
步骤 12 :

log4j.properties

edit
日志配置文件,主要用于显示数据库的SQL语句
# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.com.how2java=TRACE # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.com.how2java=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
因为使用 ssm,所以jsp通常都会放在WEB-INF/jsp 下面,而这个位置是无法通过浏览器直接访问的,所以就会专门做这么一个类,便于访问这些jsp。
比如要访问WEB-INF/jsp/index.jsp文件,那么就通过/index 这个路径来访问。
这个类还有两点需要注意:
1. /login 只支持get方式。 post方式是后续用来进行登录行为的,这里的get方式仅仅用于显示登录页面
2. 权限注解:
通过注解: @RequiresRoles("admin") 指明了 访问 deleteProduct 需要角色"admin"
通过注解:@RequiresPermissions("deleteOrder") 指明了 访问 deleteOrder 需要权限"deleteOrder"
package com.how2java.controller; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; //专门用于显示页面的控制器 @Controller @RequestMapping("") public class PageController { @RequestMapping("index") public String index(){ return "index"; } @RequiresPermissions("deleteOrder") @RequestMapping("deleteOrder") public String deleteOrder(){ return "deleteOrder"; } @RequiresRoles("admin") @RequestMapping("deleteProduct") public String deleteProduct(){ return "deleteProduct"; } @RequestMapping("listProduct") public String listProduct(){ return "listProduct"; } @RequestMapping(value="/login",method=RequestMethod.GET) public String login(){ return "login"; } @RequestMapping("unauthorized") public String noPerms(){ return "unauthorized"; } }
index页面
<%@ 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"> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <link rel="stylesheet" type="text/css" href="static/css/style.css" /> </head> <body> <div class="workingroom"> <div class="loginDiv"> <c:if test="${empty subject.principal}"> <a href="login">登录</a><br> </c:if> <c:if test="${!empty subject.principal}"> <span class="desc">你好,${subject.principal},</span> <a href="doLogout">退出</a><br> </c:if> <a href="listProduct">查看产品</a><span class="desc">(登录后才可以查看) </span><br> <a href="deleteProduct">删除产品</a><span class="desc">(要有产品管理员角色, zhang3没有,li4 有) </span><br> <a href="deleteOrder">删除订单</a><span class="desc">(要有删除订单权限, zhang3有,li4没有) </span><br> </div> </div> </body> </html>
需要登录才能访问的页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /> <div class="workingroom"> listProduct.jsp ,能进来,就表示已经登录成功了 <br> <a href="#" onClick="javascript:history.back()">返回</a> </div>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
 
<!DOCTYPE html>
 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="static/css/style.css" />

<div class="workingroom">

	listProduct.jsp ,能进来,就表示已经登录成功了
	<br>
	<a href="#" onClick="javascript:history.back()">返回</a>
</div>
步骤 16 :

deleteProduct.jsp

edit
需要角色才能访问的页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /> <div class="workingroom"> deleteProduct.jsp,能进来<br>就表示拥有 productManager 角色 <br> <a href="#" onClick="javascript:history.back()">返回</a> </div>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
 
<!DOCTYPE html>
 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="static/css/style.css" />

<div class="workingroom">

	deleteProduct.jsp,能进来<br>就表示拥有 productManager 角色
	<br>
	<a href="#" onClick="javascript:history.back()">返回</a>
</div>
需要权限deleteOrder 才能访问的页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /> <div class="workingroom"> deleteOrder.jsp ,能进来,就表示有deleteOrder权限 <br> <a href="#" onClick="javascript:history.back()">返回</a> </div>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
 
<!DOCTYPE html>
 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="static/css/style.css" />

<div class="workingroom">

	deleteOrder.jsp ,能进来,就表示有deleteOrder权限
	<br>
	<a href="#" onClick="javascript:history.back()">返回</a>
</div>
登陆页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /> <div class="workingroom"> <div class="errorInfo">${error}</div> <form action="login" method="post"> 账号: <input type="text" name="name"> <br> 密码: <input type="password" name="password"> <br> <br> <input type="submit" value="登录"> <br> <br> <div> <span class="desc">账号:zhang3 密码:12345 角色:admin</span><br> <span class="desc">账号:li4 密码:abcde 角色:productManager</span><br> </div> </form> </div>
步骤 19 :

unauthorized.jsp

edit
没有角色,没有权限都会跳转到这个页面来
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /> <div class="workingroom"> 权限不足,具体原因:${ex.message} <br> <a href="#" onClick="javascript:history.back()">返回</a> </div>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
 
<!DOCTYPE html>
 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="static/css/style.css" />

<div class="workingroom">

	权限不足,具体原因:${ex.message}
	<br>
	<a href="#" onClick="javascript:history.back()">返回</a>
</div>
样式文件
span.desc{ margin-left:20px; color:gray; } div.workingroom{ margin:200px auto; width:400px; } div.workingroom a{ display:inline-block; margin-top:20px; } div.loginDiv{ text-align: left; } div.errorInfo{ color:red; font-size:0.65em; }
span.desc{
	margin-left:20px;
	color:gray;
}
div.workingroom{
	margin:200px auto;
	width:400px;
}
div.workingroom a{
	display:inline-block;
	margin-top:20px;
}
div.loginDiv{
	text-align: left;
}
div.errorInfo{
	color:red;
	font-size:0.65em;
}
一套是指实体类,Mapper,xml文件, Service接口,Service实现类
需要说明的是UserMapper是得到一个User对象,但是在UserService里又返回的是这个对象的password属性,这样做是为了和前面教程里DAO 里的做法保持一直
package com.how2java.pojo; public class User { private int id; private String name; private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getId() { return id; } public void setId(int id) { this.id = id; } }
package com.how2java.mapper; import com.how2java.pojo.User; public interface UserMapper { public User getByName(String name); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.how2java.mapper.UserMapper"> <select id="getByName" parameterType="string" resultType="User"> select * from user where name = #{0} </select> </mapper>
package com.how2java.service; public interface UserService { public String getPassword(String name); }
package com.how2java.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.how2java.mapper.UserMapper; import com.how2java.pojo.User; import com.how2java.service.UserService; @Service public class UserServiceImpl implements UserService{ @Autowired UserMapper userMapper; @Override public String getPassword(String name) { // TODO Auto-generated method stub User u = userMapper.getByName(name); if(null==u) return null; return u.getPassword(); } }
Role 一套,与User 一套类似的,RoleMapper返回的是Role的集合,而RoleService里返回的就是String 的集合了
package com.how2java.pojo; public class Role { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Role [id=" + id + ", name=" + name + "]"; } }
package com.how2java.mapper; import java.util.List; import com.how2java.pojo.Role; public interface RoleMapper { public List<Role> listRolesByUserName(String userName); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.how2java.mapper.RoleMapper"> <select id="listRolesByUserName" parameterType="string" resultType="Role"> select r.id, r.name from user u left join user_role ur on u.id = ur.uid left join Role r on r.id = ur.rid where u.name = #{0} </select> </mapper>
package com.how2java.service; import java.util.Set; public interface RoleService { public Set<String> listRoles(String userName); }
package com.how2java.service.impl; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.how2java.mapper.RoleMapper; import com.how2java.pojo.Role; import com.how2java.service.RoleService; @Service public class RoleServiceImpl implements RoleService{ @Autowired RoleMapper roleMapper; @Override public Set<String> listRoles(String userName) { List<Role> roles = roleMapper.listRolesByUserName(userName); Set<String> result = new HashSet<>(); for (Role role: roles) { result.add(role.getName()); } return result; } }
步骤 23 :

Permission 一套

edit
Permission 一套,与User 一套类似的,PermissionMapper返回的是 Permission 的集合,而 PermissionService 里返回的就是String 的集合了
package com.how2java.pojo; public class Permission { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Permission [id=" + id + ", name=" + name + "]"; } }
package com.how2java.mapper; import java.util.List; import com.how2java.pojo.Permission; public interface PermissionMapper { public List<Permission> listPermissionsByUserName(String userName); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.how2java.mapper.PermissionMapper"> <select id="listPermissionsByUserName" parameterType="string" resultType="Permission"> select p.id, p.name from user u left join user_role ru on u.id = ru.uid left join role r on r.id = ru.rid left join role_permission rp on r.id = rp.rid left join permission p on p.id = rp.pid where u.name =#{0} </select> </mapper>
package com.how2java.service; import java.util.Set; public interface PermissionService { public Set<String> listPermissions(String userName); }
package com.how2java.service.impl; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.how2java.mapper.PermissionMapper; import com.how2java.pojo.Permission; import com.how2java.service.PermissionService; @Service public class PermissionServiceImpl implements PermissionService{ @Autowired PermissionMapper permissionMapper; @Override public Set<String> listPermissions(String userName) { List<Permission> permissions = permissionMapper.listPermissionsByUserName(userName); Set<String> result = new HashSet<>(); for (Permission permission: permissions) { result.add(permission.getName()); } return result; } }
进行登录的控制器,和 LoginServlet 一样,获取账号密码进行验证,如果成功了就客户端跳转到index,否则就返回login.jsp页面。
需要注意的是,这里用的是 post 方式

@RequestMapping(value="/login",method=RequestMethod.POST)

区别于PageController里用 get 方式的 login
package com.how2java.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("") public class LoginController { @RequestMapping(value="/login",method=RequestMethod.POST) public String login(Model model,String name, String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(name, password); try { subject.login(token); Session session=subject.getSession(); session.setAttribute("subject", subject); return "redirect:index"; } catch (AuthenticationException e) { model.addAttribute("error", "验证失败"); return "login"; } } }
这里才是真正做登录验证和授权的地方
做法和 数据库支持 教程中的DatabaseRealm 做法一模一样,区别只是在于把DAO 换成了 Service
而这个 DatabaseRealm 的使用,是声明在 applicationContext-shiro.xml 中的:

<bean id="databaseRealm" class="com.how2java.realm.DatabaseRealm"></bean>


<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="databaseRealm" />
<property name="sessionManager" ref="sessionManager" />
</bean>
package com.how2java.realm; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.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.beans.factory.annotation.Autowired; import com.how2java.service.PermissionService; import com.how2java.service.RoleService; import com.how2java.service.UserService; public class DatabaseRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermissionService permissionService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //能进入到这里,表示账号已经通过验证了 String userName =(String) principalCollection.getPrimaryPrincipal(); //通过service获取角色和权限 Set<String> permissions = permissionService.listPermissions(userName); Set<String> roles = roleService.listRoles(userName); //授权对象 SimpleAuthorizationInfo s = new SimpleAuthorizationInfo(); //把通过service获取到的角色和权限放进去 s.setStringPermissions(permissions); s.setRoles(roles); return s; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获取账号密码 UsernamePasswordToken t = (UsernamePasswordToken) token; String userName= token.getPrincipal().toString(); String password= new String( t.getPassword()); //获取数据库中的密码 String passwordInDB = userService.getPassword(userName); //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息 if(null==passwordInDB || !passwordInDB.equals(password)) throw new AuthenticationException(); //认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName()); return a; } }
步骤 26 :

DefaultExceptionHandler

edit
最后是异常处理,当发生UnauthorizedException 异常的时候,就表示访问了无授权的资源,那么就会跳转到unauthorized.jsp,而在unauthorized.jsp 中就会把错误信息通过变量 ex 取出来。

DefaultExceptionHandler 的使用,是声明在 springMVC.xml 的最后几行:

<bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
</bean>
<bean class="com.how2java.exception.DefaultExceptionHandler"/>
package com.how2java.exception; import org.apache.shiro.authz.UnauthorizedException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice public class DefaultExceptionHandler { @ExceptionHandler({UnauthorizedException.class}) @ResponseStatus(HttpStatus.UNAUTHORIZED) public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) { ModelAndView mv = new ModelAndView(); mv.addObject("ex", e); mv.setViewName("unauthorized"); return mv; } }
这种方式直观,哪里需要权限,哪里写注解@RequirePermission就行。 但是,真正项目开发的时候,这种方式就很有局限性了,当权限配置关系发生变化,每次都要修改代码,编译打包重启系统,这肯定是不能够被接受的。
所以,最好的方式,还是通过动态配置,给不同的用户配置不同的角色,权限,修改之后立马生效这种方式。 为了实现这个效果,就需要基于URL配置权限的方式来做了。那么就在下面的教程中展开了。


HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。


问答区域    
2021-01-14 登陆成功跳转问题
海东青

登录成功后如何跳转到配置文件中的successUrl,博主没有讲这个。这个问题也是困扰我最久的一个







回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到




2020-12-28 配置文件的疑惑
鸡你太美噢北北




这里不太理解为什么要相互调用,站长可否解释一下?
加载中
<!-- 会话验证调度器,每30分钟执行一次验证 ,设定会话超时及保存 -->
	<bean name="sessionValidationScheduler"
		class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
		<property name="interval" value="1800000" />
		<property name="sessionManager" ref="sessionManager" />
	</bean>
	<!-- 会话管理器 -->
	<bean id="sessionManager"
		class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
		<!-- 全局会话超时时间(单位毫秒),默认30分钟 -->
		<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" />
		<property name="sessionIdCookieEnabled" value="true" />
		<property name="sessionIdCookie" ref="sessionIdCookie" />
	</bean>

							





回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2020-09-09 用idea运行报bean找不到的记得把xml里面的4.0.xsd都改为3.0xsd
2020-07-14 idea 咋启动
2020-02-05 shiro登录成功后,如何获取到用户


提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 18 条以前的提问,请 点击查看

提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
关于 工具和中间件-Shiro-ssm 的提问

尽量提供截图代码异常信息,有助于分析和解决问题。 也可进本站QQ群交流: 578362961
提问尽量提供完整的代码,环境描述,越是有利于问题的重现,您的问题越能更快得到解答。
对教程中代码有疑问,请提供是哪个步骤,哪一行有疑问,这样便于快速定位问题,提高问题得到解答的速度
在已经存在的几千个提问里,有相当大的比例,是因为使用了和站长不同版本的开发环境导致的,比如 jdk, eclpise, idea, mysql,tomcat 等等软件的版本不一致。
请使用和站长一样的版本,可以节约自己大量的学习时间。 站长把教学中用的软件版本整理了,都统一放在了这里, 方便大家下载: https://how2j.cn/k/helloworld/helloworld-version/1718.html

上传截图