新建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