新建web项目
web.xml
修改web.xml,在里面加了个过滤器。 这个过滤器的作用,简单的说,就是 Shiro 入门里的TestShiro 这部分的工作,悄悄的干了。
//加载配置文件,并获取工厂Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");//获取安全管理者实例SecurityManager sm = factory.getInstance();//将安全管理者放入全局对象SecurityUtils.setSecurityManager(sm);
1 <web-app> 2 <listener> 3 <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> 4 </listener> 5 <context-param> 6 <param-name>shiroEnvironmentClass</param-name> 7 <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini --> 8 </context-param> 9 <context-param> 10 <param-name>shiroConfigLocations</param-name> 11 <param-value>classpath:shiro.ini</param-value> 12 </context-param> 13 <filter> 14 <filter-name>shiroFilter</filter-name> 15 <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> 16 </filter> 17 <filter-mapping> 18 <filter-name>shiroFilter</filter-name> 19 <url-pattern>/*</url-pattern> 20 </filter-mapping> 21 </web-app>
User DAO DatabaseRealm
这三个类和前面一样,不用多说。
User.java
1 package com.how2java; 2 3 public class User { 4 5 private int id; 6 private String name; 7 private String password; 8 public String getName() { 9 return name; 10 } 11 public void setName(String name) { 12 this.name = name; 13 } 14 public String getPassword() { 15 return password; 16 } 17 public void setPassword(String password) { 18 this.password = password; 19 } 20 public int getId() { 21 return id; 22 } 23 public void setId(int id) { 24 this.id = id; 25 } 26 27 }
DAO
1 package com.how2java; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 import java.util.HashSet; 9 import java.util.Set; 10 11 public class DAO { 12 public DAO() { 13 try { 14 Class.forName("com.mysql.jdbc.Driver"); 15 } catch (ClassNotFoundException e) { 16 e.printStackTrace(); 17 } 18 } 19 20 public Connection getConnection() throws SQLException { 21 return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8", "root", 22 "admin"); 23 } 24 25 public String getPassword(String userName) { 26 String sql = "select password from user where name = ?"; 27 try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) { 28 29 ps.setString(1, userName); 30 31 ResultSet rs = ps.executeQuery(); 32 33 if (rs.next()) 34 return rs.getString("password"); 35 36 } catch (SQLException e) { 37 38 e.printStackTrace(); 39 } 40 return null; 41 } 42 43 public Set<String> listRoles(String userName) { 44 45 Set<String> roles = new HashSet<>(); 46 String sql = "select r.name from user u " 47 + "left join user_role ur on u.id = ur.uid " 48 + "left join Role r on r.id = ur.rid " 49 + "where u.name = ?"; 50 try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) { 51 ps.setString(1, userName); 52 ResultSet rs = ps.executeQuery(); 53 54 while (rs.next()) { 55 roles.add(rs.getString(1)); 56 } 57 58 } catch (SQLException e) { 59 60 e.printStackTrace(); 61 } 62 return roles; 63 } 64 public Set<String> listPermissions(String userName) { 65 Set<String> permissions = new HashSet<>(); 66 String sql = 67 "select p.name from user u "+ 68 "left join user_role ru on u.id = ru.uid "+ 69 "left join role r on r.id = ru.rid "+ 70 "left join role_permission rp on r.id = rp.rid "+ 71 "left join permission p on p.id = rp.pid "+ 72 "where u.name =?"; 73 74 try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) { 75 76 ps.setString(1, userName); 77 78 ResultSet rs = ps.executeQuery(); 79 80 while (rs.next()) { 81 permissions.add(rs.getString(1)); 82 } 83 84 } catch (SQLException e) { 85 86 e.printStackTrace(); 87 } 88 return permissions; 89 } 90 public static void main(String[] args) { 91 System.out.println(new DAO().listRoles("zhang3")); 92 System.out.println(new DAO().listRoles("li4")); 93 System.out.println(new DAO().listPermissions("zhang3")); 94 System.out.println(new DAO().listPermissions("li4")); 95 } 96 }
DatabaseRealm
1 package com.how2java; 2 3 import java.util.Set; 4 5 import org.apache.shiro.authc.AuthenticationException; 6 import org.apache.shiro.authc.AuthenticationInfo; 7 import org.apache.shiro.authc.AuthenticationToken; 8 import org.apache.shiro.authc.SimpleAuthenticationInfo; 9 import org.apache.shiro.authc.UsernamePasswordToken; 10 import org.apache.shiro.authz.AuthorizationInfo; 11 import org.apache.shiro.authz.SimpleAuthorizationInfo; 12 import org.apache.shiro.realm.AuthorizingRealm; 13 import org.apache.shiro.subject.PrincipalCollection; 14 15 public class DatabaseRealm extends AuthorizingRealm { 16 17 @Override 18 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 19 //能进入到这里,表示账号已经通过验证了 20 String userName =(String) principalCollection.getPrimaryPrincipal(); 21 //通过DAO获取角色和权限 22 Set<String> permissions = new DAO().listPermissions(userName); 23 Set<String> roles = new DAO().listRoles(userName); 24 25 //授权对象 26 SimpleAuthorizationInfo s = new SimpleAuthorizationInfo(); 27 //把通过DAO获取到的角色和权限放进去 28 s.setStringPermissions(permissions); 29 s.setRoles(roles); 30 return s; 31 } 32 33 @Override 34 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 35 //获取账号密码 36 UsernamePasswordToken t = (UsernamePasswordToken) token; 37 String userName= token.getPrincipal().toString(); 38 String password= new String( t.getPassword()); 39 //获取数据库中的密码 40 String passwordInDB = new DAO().getPassword(userName); 41 42 //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息 43 if(null==passwordInDB || !passwordInDB.equals(password)) 44 throw new AuthenticationException(); 45 46 //认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm 47 SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName()); 48 return a; 49 } 50 51 }
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
1 [main] 2 #使用数据库进行验证和授权 3 databaseRealm=com.how2java.DatabaseRealm 4 securityManager.realms=$databaseRealm 5 6 #当访问需要验证的页面,但是又没有验证的情况下,跳转到login.jsp 7 authc.loginUrl=/login.jsp 8 #当访问需要角色的页面,但是又不拥有这个角色的情况下,跳转到noroles.jsp 9 roles.unauthorizedUrl=/noRoles.jsp 10 #当访问需要权限的页面,但是又不拥有这个权限的情况下,跳转到noperms.jsp 11 perms.unauthorizedUrl=/noPerms.jsp 12 13 #users,roles和perms都通过前面知识点的数据库配置了 14 [users] 15 16 #urls用来指定哪些资源需要什么对应的授权才能使用 17 [urls] 18 #doLogout地址就会进行退出行为 19 /doLogout=logout 20 #login.jsp,noroles.jsp,noperms.jsp 可以匿名访问 21 /login.jsp=anon 22 /noroles.jsp=anon 23 /noperms.jsp=anon 24 25 #查询所有产品,需要登录后才可以查看 26 /listProduct.jsp=authc 27 #增加商品不仅需要登录,而且要拥有 productManager 权限才可以操作 28 /deleteProduct.jsp=authc,roles[productManager] 29 #删除商品,不仅需要登录,而且要拥有 deleteProduct 权限才可以操作 30 /deleteOrder.jsp=authc,perms["deleteOrder"]
style.css
新建个样式文件,后面的 jsp 会用得着
本文件位于: WebContent/static/css/style.css 下
本文件位于: 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; }
index.jsp
1. 通过 ${subject.principal} 来判断用户是否登录,如果登录过了就显示退出,如果未登录就显示登录按钮
2. 提供3个超链,分别要 登录后才可以查看,有角色才能看,有权限才能看,便于进行测试
2. 提供3个超链,分别要 登录后才可以查看,有角色才能看,有权限才能看,便于进行测试
注: subject 是在 LoginServlet 里放进session的
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <html> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 6 7 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 8 9 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 10 11 </head> 12 <body> 13 14 <div class="workingroom"> 15 <div class="loginDiv"> 16 17 <c:if test="${empty subject.principal}"> 18 <a href="login.jsp">登录</a><br> 19 </c:if> 20 <c:if test="${!empty subject.principal}"> 21 <span class="desc">你好,${subject.principal},</span> 22 <a href="doLogout">退出</a><br> 23 </c:if> 24 25 <a href="listProduct.jsp">查看产品</a><span class="desc">(登录后才可以查看) </span><br> 26 <a href="deleteProduct.jsp">删除产品</a><span class="desc">(要有产品管理员角色, zhang3没有,li4 有) </span><br> 27 <a href="deleteOrder.jsp">删除订单</a><span class="desc">(要有删除订单权限, zhang3有,li4没有) </span><br> 28 </div> 29 30 </body> 31 </html>
login.jsp
登陆页面,如果有错误返回会显示错误. 账号密码也写在下面,方便输入
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8" import="java.util.*"%> 3 4 <!DOCTYPE html> 5 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 <div class="errorInfo">${error}</div> 12 <form action="login" method="post"> 13 账号: <input type="text" name="name"> <br> 14 密码: <input type="password" name="password"> <br> 15 <br> 16 <input type="submit" value="登录"> 17 <br> 18 <br> 19 <div> 20 <span class="desc">账号:zhang3 密码:12345 角色:admin</span><br> 21 <span class="desc">账号:li4 密码:abcde 角色:productManager</span><br> 22 </div> 23 24 </form> 25 </div>
listProduct.jsp
根据 shiro.ini 里的配置信息:
/listProduct.jsp=authc
这个页面要在登录之后才能访问
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8" import="java.util.*"%> 3 4 <!DOCTYPE html> 5 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 listProduct.jsp ,能进来,就表示已经登录成功了 12 <br> 13 <a href="#" onClick="javascript:history.back()">返回</a> 14 </div>
deleteOrder.jsp
根据 shiro.ini 里的配置信息:
/deleteProduct.jsp=authc,roles[admin]
这个页面要在登录之后,并且有角色的前提下才能访问。
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8" import="java.util.*"%> 3 4 <!DOCTYPE html> 5 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 deleteOrder.jsp ,能进来,就表示有deleteOrder权限 12 <br> 13 <a href="#" onClick="javascript:history.back()">返回</a> 14 </div>
deleteProduct.jsp
根据 shiro.ini 里的配置信息:
/deleteOrder.jsp=authc,perms["deleteOrder"]
这个页面要在登录之后,并且有权限的前提下才能访问。
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8" import="java.util.*"%> 3 4 <!DOCTYPE html> 5 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 deleteProduct.jsp,能进来<br>就表示拥有 productManager 角色 12 <br> 13 <a href="#" onClick="javascript:history.back()">返回</a> 14 </div>
noRoles.jsp
根据 shiro.ini 里的配置信息:
roles.unauthorizedUrl=/noRoles.jsp
当没有角色的时候,就会跳转到这里来。
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8" import="java.util.*"%> 3 4 <!DOCTYPE html> 5 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 角色不匹配 12 <br> 13 <a href="#" onClick="javascript:history.back()">返回</a> 14 </div>
noPerms.jsp
根据 shiro.ini 里的配置信息:
perms.unauthorizedUrl=/noPerms.jsp
当没有权限的时候,就会跳转到这里来。
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8" import="java.util.*"%> 3 4 <!DOCTYPE html> 5 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 权限不足 12 <br> 13 <a href="#" onClick="javascript:history.back()">返回</a> 14 </div>
最后
启动tomcat,试试各个功能吧!并理一理其中的逻辑以及思考它的原理。
代码地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/shiro-web.zip