步骤 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 访问测试
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
本项目是用的 Eclipse 动态Web项目风格,如果用的是 IDEA ,那么请参考 如何 idea 导入 web 动态项目 本项目建立在 数据库支持 的基础之上,所以假设已经在数据库中创建了相关表结构以及表中的数据了 运行测试效果期望如下: 1. 如图所示,下面3个链接都要求登陆,未登陆点击会提示错误信息 2. zhang3 登录之后,因为有对应的角色和权限,所以可以访问后两个超链 3. li4 登录后,因为没有对应的角色和权限,所以访问后两个超链,会提示错误信息
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。 采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。 推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。 这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来 这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
新建Web动态项目 shiro
右上角的lib.rar里,有本项目需要的jar包,都复制到 WebContent/WEB-INF/lib 下面去
修改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>
这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 下 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的 <%@ 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>
登陆页面,如果有错误返回会显示错误. 账号密码也写在下面,方便输入
<%@ 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>
<%@ 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> <%@ 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">
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> <%@ 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> <%@ 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>
重启tomcat 访问测试
http://localhost:8080/shiro/
HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。
问答区域
2021-02-08
shiro中的匿名设置其jsp页面应该要修改。。。。。
回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
2021-02-02
应该再加一个/*=authc注释的,否则设置匿名访问就没有意义了
回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
2020-08-21
关于DatabaseRealm的问题
2020-07-22
“#users,roles和perms都通过前面知识点的数据库配置了”,在哪里配置了呀?
2020-02-20
注意:web集成的教材没有用MD5加密解密,希望后来者注意
提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 9 条以前的提问,请 点击查看
提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
|