shiro集成web

2021-01-23

第二章:shiro集成web

官网帮助文档:https://shiro.apache.org/web.html

shiro集成web环境搭建

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!-- shiro核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<!-- shiro web依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.2</version>
</dependency>

<!-- slf4j日志依赖开始 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
<scope>compile</scope>
</dependency>
<!-- slf4j日志依赖结束 -->

<!-- servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<!-- 添加jstl支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- 添加日志支持 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>

<!-- 配置shiro监听 -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!-- 添加shiro支持 -->
<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>

shiro.ini

初始化用户数据及角色数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#定义用户
[users]
#用户名=密码,角色(管理员)
kazu=123456,admin
#用户名=密码,角色(教师)
jack=123456,teacher
#用户名=密码
tom=123456
#定义角色
[roles]
#admin管理员角色拥有用户所有权限
admin=user:*
#teacher教师角色拥有学生所有权限
teacher=student:*

拦截器

官方文档:https://shiro.apache.org/web.html#Web-defaultfilters

默认拦截器

Filter Name Class
anon 匿名访问 org.apache.shiro.web.filter.authc.AnonymousFilter
authc 登录后访问 org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout 退出请求 org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms 权限 org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles 角色 org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

案例1:测试匿名访问

创建LoginServlet

创建LoginServlet,完成doGet()请求,使用转发跳转到login登录页面

1
2
3
4
5
6
7
8
9
10
@WebServlet("/login")
public class LoginServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("==================doGet()======================");
req.getRequestDispatcher("/login.jsp").forward(req,resp);
}
}

shiro.ini

修改shiro配置文件,添加以下路径

1
2
3
4
#访问路径
[urls]
#登录请求为anon(匿名请求,不登录也可访问)
/login=anon

案例2:测试登录

创建登录login.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<div>
<font color="red">${errorInfo }</font>
</div>
<form action="login" method="post">
用户名:<input type="text" name="userName"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>

LoginServlet

修改LoginServlet类,重写doPost()方法

image-20191204214802635

案例3:测试身份验证

需求

测试在浏览器中,未登录时访问/admin下所有的请求

创建AdminServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bdqn.web;

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 java.io.IOException;

@WebServlet("/admin")
public class AdminServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("index.jsp");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("index.jsp");
}
}

shiro.ini

修改shiro.ini配置文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[main]
#没有登录的情况下返回的 路径
authc.loginUrl=/login

#省略之前的配置

#访问路径
[urls]
#登录请求为anon(匿名请求,不登录也可访问)
/login=anon
#admin请求为authc(authc必须进行身份验证【登录】才能访问)
/admin=authc

image-20191204215736963

案例4:验证角色

需求

访问/student学生资源,前提是访问学生资源必须拥有教师角色才能访问

shiro.ini

1
2
3
4
5
6
7
8
9
[main]
#没有角色的情况下返回的路径
roles.unauthorizedUrl=/unauthorize.jsp

#省略之前的配置

[urls]
#学生请求(该请求必须拥有教师的角色才能访问)
/student=roles[teacher]

image-20191204221442030

测试

测试没有角色:

  1. 登录没有teacher角色的账户(如:kazu)
  2. 在浏览器中输入/student路径(结果:跳转到没有权限的页面)

测试拥有角色:

  1. 登录拥有teacher角色的账户(如:jack)
  2. 在浏览器中输入/student路径(结果:404请求)

案例5:验证权限

需求

访问/teacher教师资源,创建用户,前提是访问教师资源必须拥有管理员角色才能访问

shiro.ini

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[main]
#没有登录的情况下返回的 路径
authc.loginUrl=/login
#没有角色的情况下返回的路径
roles.unauthorizedUrl=/unauthorize.jsp
#没有权限的情况返回的路径
perms.unauthorizedUrl=/unauthorize.jsp
#定义用户
[users]
#用户名=密码,角色(管理员)
kazu=123456,admin
#用户名=密码,角色(教师)
jack=123456,teacher
#用户名=密码
tom=123456
#定义角色
[roles]
#admin管理员角色拥有用户所有权限
admin=user:*
#teacher教师角色拥有学生所有权限
teacher=student:*

#访问路径
[urls]
#登录请求为anon(匿名请求,不登录也可访问)
/login=anon
#admin请求为authc(authc必须进行身份验证【登录】才能访问)
/admin=authc
#学生请求(该请求必须拥有教师的角色才能访问)
/student=roles[teacher]
#教师请求(该请求必须拥有创建用户的权限才能访问[即:拥有admin角色])
/teacher=perms["user:create"]

image-20191204222126535

测试

测试没有权限:

  1. 登录没有创建用户权限的账户(如:jack)
  2. 在浏览器中输入/teacher路径(结果:跳转到没有权限的页面)

测试拥有权限:

  1. 登录拥有创建用户权限的账户(如:kazu)
  2. 在浏览器中输入/teacher路径(结果:404请求)

URL匹配方式

占位符 含义 说明
? 匹配一个字符/admin? 可以匹配/admin1或/admin2
不可以匹配/admin12
不可以匹配/admin
* 匹配0到n个字符/admin* 可以匹配 /admin或/admin1或 /admin12
不可以匹配/admin/abc
** 匹配零个或者多个路径/admin/** 可以匹配/admin
可以匹配/admin/a
可以匹配/admin/a/b
不可以匹配/adminxxx/a/b

Shiro标签库

官方文档地址:https://shiro.apache.org/web.html#Web-taglibrary

导入shiro标签库

1
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

< shiro:guest >

游客(非登录用户)访问,无需登录即可查看

1
2
3
<shiro:guest>
游客访问 <a href = "login.jsp">登录</a>
</shiro:guest>

< shiro:principal >

principal 标签:显示用户身份信息,默认调用Subject.getPrincipal()获取,即Primary Principal

< shiro:user >

user 标签:用户已经通过认证\记住我 登录后显示响应的内容

1
2
3
<shiro:user>
欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
</shiro:user>

< shiro:hasRole >

判断是否拥有某个角色

1
2
3
<shiro:hasRole name="admin">
欢迎有admin角色的用户!
</shiro:hasRole>

< shiro:hasPermission >

判断是否拥有某个权限

1
2
3
<shiro:hasPermission name="student:create">
欢迎有student:create权限的用户!
</shiro:hasPermission>

< shiro:authenticated >

authenticated标签:用户身份验证通过,即 Subject.login() 登录成功 不是记住我登录的

1
2
3
<shiro:authenticated>
用户[<shiro:principal/>] 已身份验证通过
</shiro:authenticated>

< shiro:notAuthenticated >

notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括”记住我”也属于未进行身份验证

1
2
3
<shiro:notAuthenticated>
未身份验证(包括"记住我")
</shiro:notAuthenticated>

加密

散列算法

​ 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5 解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。

加密测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.bdqn.test;

import org.apache.shiro.crypto.hash.Md5Hash;

import java.util.UUID;

public class PasswordTest {
public static void main(String[] args) {
String pwd = "123456";
//方式一:不设置盐值及散列次数
//不足:可以被解密
Md5Hash md5Hash1 = new Md5Hash(pwd);
System.out.println("加密后的密码:"+md5Hash1);
//方式二:加入盐值
//如果盐值比较复杂,解密比较困难
Md5Hash md5Hash2 = new Md5Hash(pwd, UUID.randomUUID().toString());
Md5Hash md5Hash3 = new Md5Hash(pwd, "bdqn");
System.out.println("加密后的密码:"+md5Hash2);
System.out.println("加密后的密码:"+md5Hash3);
//方式三:加入盐值及散列次数
//加入散列次数,加密更加复杂
Md5Hash md5Hash4 = new Md5Hash(pwd, "bdqn",2);
System.out.println("加密后的密码:"+md5Hash4);
}
}

实际开发中,建议使用加入盐值及散列次数进行密码加密操作

加密工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.bdqn.utils;

import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.Sha1Hash;

public class PasswordUtil {

/**
* 对密码加密 md5
* @param source 要加密的明文
* @param salt 盐
* @param hashIterations 散列次数
* @return
*/
public static String md5(String source, Object salt, Integer hashIterations) {
return new Md5Hash(source, salt, hashIterations).toString();
}


/**
* 对密码加密sha1
* @param source 要加密的明文
* @param salt 盐
* @param hashIterations 散列次数
* @return
*/
public static String sha1(String source, Object salt, Integer hashIterations) {
return new Sha1Hash(source, salt, hashIterations).toString();
}
}