Apache Shiro(四)-登录认证和权限管理WEB支持(Servlet)

时间:2023-03-09 09:31:22
Apache Shiro(四)-登录认证和权限管理WEB支持(Servlet)

新建web项目

  Apache Shiro(四)-登录认证和权限管理WEB支持(Servlet)

web.xml

  修改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>

User DAO DatabaseRealm

这三个类和前面一样,不用多说。

User.java

 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;
} }

点击展开

DAO

 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"));
}
}

点击展开

DatabaseRealm

 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

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);
}
}
}

shiro.ini

 [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]
#删除商品,不仅需要登录,而且要拥有 deleteProduct 权限才可以操作
/deleteOrder.jsp=authc,perms["deleteOrder"]

style.css

新建个样式文件,后面的 jsp 会用得着
本文件位于: WebContent/static/css/style.css 下

Apache Shiro(四)-登录认证和权限管理WEB支持(Servlet)

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;
}

index.jsp

1. 通过 ${subject.principal} 来判断用户是否登录,如果登录过了就显示退出,如果未登录就显示登录按钮
2. 提供3个超链,分别要 登录后才可以查看,有角色才能看,有权限才能看,便于进行测试

注: subject 是在 LoginServlet 里放进session的

Apache Shiro(四)-登录认证和权限管理WEB支持(Servlet)

 <%@ 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

登陆页面,如果有错误返回会显示错误. 账号密码也写在下面,方便输入

Apache Shiro(四)-登录认证和权限管理WEB支持(Servlet)

 <%@ 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>

listProduct.jsp

根据 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>

deleteOrder.jsp

根据 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>

deleteProduct.jsp

根据 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>

noRoles.jsp

根据 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>

noPerms.jsp

根据 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>

最后

启动tomcat,试试各个功能吧!并理一理其中的逻辑以及思考它的原理。

代码地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/shiro-web.zip