how2j.cn

-->
下载区
文件名 文件大小
lib.rar 1m
shiro.rar 1m

解压rar如果失败,请用5.21版本或者更高版本的winrar

点击下载 winrar5.21
步骤 1 : 先运行,看到效果,再学习   
步骤 2 : 模仿和排错   
步骤 3 : 新建Web动态项目   
步骤 4 : jar包   
步骤 5 : web.xml   
步骤 6 : User DAO DatabaseRealm   
步骤 7 : LoginServlet   
步骤 8 : shiro.ini   
步骤 9 : style.css   
步骤 10 : index.jsp   
步骤 11 : login.jsp   
步骤 12 : listProduct.jsp   
步骤 13 : deleteOrder.jsp   
步骤 14 : deleteProduct.jsp   
步骤 15 : noRoles.jsp   
步骤 16 : noPerms.jsp   
步骤 17 : 重启tomcat 访问测试   

步骤 1 :

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

edit
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
本项目是用的 Eclipse 动态Web项目风格,如果用的是 IDEA ,那么请参考 如何 idea 导入 web 动态项目
本项目建立在 数据库支持 的基础之上,所以假设已经在数据库中创建了相关表结构以及表中的数据了
运行测试效果期望如下:

1. 如图所示,下面3个链接都要求登陆,未登陆点击会提示错误信息
2. zhang3 登录之后,因为有对应的角色和权限,所以可以访问后两个超链
3. li4 登录后,因为没有对应的角色和权限,所以访问后两个超链,会提示错误信息
先运行,看到效果,再学习
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。

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

新建Web动态项目

edit
新建Web动态项目 shiro
新建Web动态项目
右上角的lib.rar里,有本项目需要的jar包,都复制到 WebContent/WEB-INF/lib 下面去
jar包
修改web.xml,在里面加了个过滤器。 这个过滤器的作用,简单的说,就是 Shiro 入门里的TestShiro 这部分的工作,悄悄的干了

//加载配置文件,并获取工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//获取安全管理者实例
SecurityManager sm = factory.getInstance();
//将安全管理者放入全局对象
SecurityUtils.setSecurityManager(sm);
<web-app> <listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <context-param> <param-name>shiroEnvironmentClass</param-name> <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini --> </context-param> <context-param> <param-name>shiroConfigLocations</param-name> <param-value>classpath:shiro.ini</param-value> </context-param> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
步骤 6 :

User DAO DatabaseRealm

edit
这3个类是在前面的数据库支持 知识点就讲解过的,不再赘述
package com.how2java; 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; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashSet; import java.util.Set; public class DAO { public DAO() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8", "root", "admin"); } public String getPassword(String userName) { String sql = "select password from user where name = ?"; try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) { ps.setString(1, userName); ResultSet rs = ps.executeQuery(); if (rs.next()) return rs.getString("password"); } catch (SQLException e) { e.printStackTrace(); } return null; } public Set<String> listRoles(String userName) { Set<String> roles = new HashSet<>(); String sql = "select 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 = ?"; try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) { ps.setString(1, userName); ResultSet rs = ps.executeQuery(); while (rs.next()) { roles.add(rs.getString(1)); } } catch (SQLException e) { e.printStackTrace(); } return roles; } public Set<String> listPermissions(String userName) { Set<String> permissions = new HashSet<>(); String sql = "select 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 =?"; try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) { ps.setString(1, userName); ResultSet rs = ps.executeQuery(); while (rs.next()) { permissions.add(rs.getString(1)); } } catch (SQLException e) { e.printStackTrace(); } return permissions; } public static void main(String[] args) { System.out.println(new DAO().listRoles("zhang3")); System.out.println(new DAO().listRoles("li4")); System.out.println(new DAO().listPermissions("zhang3")); System.out.println(new DAO().listPermissions("li4")); } }
package com.how2java; 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; public class DatabaseRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //能进入到这里,表示账号已经通过验证了 String userName =(String) principalCollection.getPrimaryPrincipal(); //通过DAO获取角色和权限 Set<String> permissions = new DAO().listPermissions(userName); Set<String> roles = new DAO().listRoles(userName); //授权对象 SimpleAuthorizationInfo s = new SimpleAuthorizationInfo(); //把通过DAO获取到的角色和权限放进去 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 = new DAO().getPassword(userName); //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息 if(null==passwordInDB || !passwordInDB.equals(password)) throw new AuthenticationException(); //认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName()); return a; } }
LoginServlet 映射路径/login的访问。
获取账号和密码,然后组成UsernamePasswordToken 对象,扔给Shiro进行判断。 如果判断不报错,即表示成功,客户端跳转到根目录,否则返回login.jsp,并带上错误信息
登录成功后还会把subject放在shiro的session对象里,shiro的这个session和httpsession是串通好了的,所以在这里放了,它会自动放在httpsession里,它们之间是同步的。
package com.how2java; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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; @WebServlet(name = "loginServlet", urlPatterns = "/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("name"); String password = req.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(name, password); try { subject.login(token); Session session=subject.getSession(); session.setAttribute("subject", subject); resp.sendRedirect(""); } catch (AuthenticationException e) { req.setAttribute("error", "验证失败"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }
[main]
1. 指定了用数据库进行验证和授权判断
2. 指定了未登录,无角色,无权限分别跳转到哪个页面去

#当访问需要验证的页面,但是又没有验证的情况下,跳转到login.jsp
authc.loginUrl=/login.jsp
#当访问需要角色的页面,但是又不拥有这个角色的情况下,跳转到noroles.jsp
roles.unauthorizedUrl=/noRoles.jsp
#当访问需要权限的页面,但是又不拥有这个权限的情况下,跳转到noperms.jsp
perms.unauthorizedUrl=/noPerms.jsp

[users]
用户,角色和权限都放在数据库里了,所以这里是空的
[urls]

#doLogout地址就会进行退出行为
/doLogout=logout
#login.jsp,noroles.jsp,noperms.jsp 可以匿名访问
/login.jsp=anon
/noroles.jsp=anon
/noperms.jsp=anon


#查询所有产品,需要登录后才可以查看
/listProduct.jsp=authc
#删除商品不仅需要登录,而且要拥有 productManager 权限才可以操作
/deleteProduct.jsp=authc,roles[productManager]
#删除订单,不仅需要登录,而且要拥有 deleteOrder 权限才可以操作
/deleteOrder.jsp=authc,perms["deleteOrder"]
[main] #使用数据库进行验证和授权 databaseRealm=com.how2java.DatabaseRealm securityManager.realms=$databaseRealm #当访问需要验证的页面,但是又没有验证的情况下,跳转到login.jsp authc.loginUrl=/login.jsp #当访问需要角色的页面,但是又不拥有这个角色的情况下,跳转到noroles.jsp roles.unauthorizedUrl=/noRoles.jsp #当访问需要权限的页面,但是又不拥有这个权限的情况下,跳转到noperms.jsp perms.unauthorizedUrl=/noPerms.jsp #users,roles和perms都通过前面知识点的数据库配置了 [users] #urls用来指定哪些资源需要什么对应的授权才能使用 [urls] #doLogout地址就会进行退出行为 /doLogout=logout #login.jsp,noroles.jsp,noperms.jsp 可以匿名访问 /login.jsp=anon /noroles.jsp=anon /noperms.jsp=anon #查询所有产品,需要登录后才可以查看 /listProduct.jsp=authc #删除商品不仅需要登录,而且要拥有 productManager 权限才可以操作 /deleteProduct.jsp=authc,roles[productManager] #删除订单,不仅需要登录,而且要拥有 deleteOrder 权限才可以操作 /deleteOrder.jsp=authc,perms["deleteOrder"]
新建个样式文件,后面的 jsp 会用得着
本文件位于: WebContent/static/css/style.css 下
style.css
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;
}
1. 通过 ${subject.principal} 来判断用户是否登录,如果登录过了就显示退出,如果未登录就显示登录按钮
2. 提供3个超链,分别要 登录后才可以查看,有角色才能看,有权限才能看,便于进行测试
注: subject 是在 LoginServlet 里放进session的
index.jsp
<%@ 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.jsp">登录</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.jsp">查看产品</a><span class="desc">(登录后才可以查看) </span><br> <a href="deleteProduct.jsp">删除产品</a><span class="desc">(要有产品管理员角色, zhang3没有,li4 有) </span><br> <a href="deleteOrder.jsp">删除订单</a><span class="desc">(要有删除订单权限, zhang3有,li4没有) </span><br> </div> </body> </html>
登陆页面,如果有错误返回会显示错误. 账号密码也写在下面,方便输入
login.jsp
<%@ 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>
根据 shiro.ini 里的配置信息:

/listProduct.jsp=authc

这个页面要在登录之后才能访问
<%@ 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>
根据 shiro.ini 里的配置信息:

/deleteProduct.jsp=authc,roles[admin]

这个页面要在登录之后,并且有角色的前提下才能访问
<%@ 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>
步骤 14 :

deleteProduct.jsp

edit
根据 shiro.ini 里的配置信息:

/deleteOrder.jsp=authc,perms["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"> 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>
根据 shiro.ini 里的配置信息:

roles.unauthorizedUrl=/noRoles.jsp

当没有角色的时候,就会跳转到这里来
<%@ 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"> 角色不匹配 <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">

	角色不匹配
	<br>
	<a href="#" onClick="javascript:history.back()">返回</a>
</div>
根据 shiro.ini 里的配置信息:

perms.unauthorizedUrl=/noPerms.jsp

当没有权限的时候,就会跳转到这里来
<%@ 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"> 权限不足 <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">

	权限不足
	<br>
	<a href="#" onClick="javascript:history.back()">返回</a>
</div>
步骤 17 :

重启tomcat 访问测试

edit
重启tomcat 访问测试

http://localhost:8080/shiro/
重启tomcat 访问测试


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


问答区域    
2021-02-08 shiro中的匿名设置其jsp页面应该要修改。。。。。
很倒霉的死小孩




站长,shiro中的配置匿名访问的没有角色,没有权限页面第二个单词应该是大写。。。。 不然就相当于没有写。。。。。。。 详情看代码
#login.jsp,noroles.jsp,noperms.jsp 可以匿名访问
/login.jsp=anon
/noroles.jsp=anon
/noperms.jsp=anon

改为:
/login.jsp=anon
/noRoles.jsp=anon
/noPerms.jsp=anon

							





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





2021-02-02 应该再加一个/*=authc注释的,否则设置匿名访问就没有意义了
鸡你太美噢北北




[main] #使用数据库进行验证和授权 databaseRealm=com.how2java.DatabaseRealm securityManager.realms=$databaseRealm #当访问需要验证的页面,但是又没有验证的情况下,跳转到login.jsp authc.loginUrl=/login.jsp #当访问需要角色的页面,但是又不拥有这个角色的情况下,跳转到noroles.jsp roles.unauthorizedUrl=/noRoles.jsp #当访问需要权限的页面,但是又不拥有这个权限的情况下,跳转到noperms.jsp perms.unauthorizedUrl=/noPerms.jsp #users,roles和perms都通过前面知识点的数据库配置了 [users] #urls用来指定哪些资源需要什么对应的授权才能使用 [urls] #doLogout地址就会进行退出行为 /doLogout=logout #login.jsp,noroles.jsp,noperms.jsp 可以匿名访问 /noRoles.jsp=anon /noPerms.jsp=anon /login.jsp=anon #登录url设为白名单,其实这边还可以把静态文件,例如css,js都设为白名单 /login=anon #查询所有产品,需要登录后才可以查看 /listProduct.jsp=authc #增加商品不仅需要登录,而且要拥有 productManager 角色才可以操作 /deleteProduct.jsp=authc,roles[productManager] #删除商品,不仅需要登录,而且要拥有 deleteProduct 权限才可以操作 /deleteOrder.jsp=authc,perms["deleteOrder"] /*=authc
加载中

							

							


1 个答案

尹文
答案时间:2021-09-26
好像确实是哈



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





2020-08-21 关于DatabaseRealm的问题
2020-07-22 “#users,roles和perms都通过前面知识点的数据库配置了”,在哪里配置了呀?
2020-02-20 注意:web集成的教材没有用MD5加密解密,希望后来者注意


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

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

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

上传截图