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

新建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 下
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个超链,分别要 登录后才可以查看,有角色才能看,有权限才能看,便于进行测试
注: subject 是在 LoginServlet 里放进session的
index.jsp
 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

登陆页面,如果有错误返回会显示错误. 账号密码也写在下面,方便输入
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

原文地址:https://www.cnblogs.com/fengyuduke/p/10417103.html