SpringBoot3整合Shiro学习
redpomelo Lv2

前言

由于我之前学开发的时候所用的安全框架是Spring Security,没有接触过Shiro这个框架,后面需要学习Shiro的若干反序列化漏洞,遂今天来补一下简单的开发知识

a93530f246851928efba39664a33008

什么是Shiro

Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它为开发人员提供了一种直观,全面的身份验证,授权,加密和会话 Management 解决方案。

img-Shiro

环境搭建

jdk:18

SpringBoot版本:3.6

IDEA选择Spring Boot作为脚手架

image-20241208145644783

发现SpringBoot的官方没有整合Shiro框架,遂勾几个常用的库无脑下一步

Maven导入

踩坑警告!!!

网上大部分博客所教程的都是SpringBoot2的版本,在SpringBoot3后强制使用JDK17从jdk8以后javax.servlet的相关代码包名改成了jakarta.servlet,shiro-spring的版本按照各大博客或者狂神的视频copy的都是低版本,在ShiroFilter中因引入的servlet不一致,导致无法解析,所以一定要把旧的sevlet相关依赖删掉

Maven中手动导入Shiro的依赖

SpringBoot3一定要导这个!!!

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
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<classifier>jakarta</classifier>
<version>2.0.1</version>
<!-- 排除仍使用了javax.servlet的依赖 -->
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入适配jakarta的依赖包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<classifier>jakarta</classifier>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<classifier>jakarta</classifier>
<version>2.0.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>

image-20241208150044916

Controller&&index

我们需要一个页面进行Shiro的测试,所以这里新建一个Controller,并解析到相应的页面

  • 新建一个index页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>喵喵</h1>
    <p th:text="${msg}"></p>
    <hr> <a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
    </body>
    </html>
  • 新建一个add.html页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>add</h1>
    </body>
    </html>
  • 新建一个update.htmp页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>update</h1>
    </body>
    </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
public class TestController {
@RequestMapping({"/", "/index"})
public String ToIndex(Model model){
model.addAttribute("msg","Hello,Shiro!");
return "index"; // Ensure this matches the template file name
}

@RequestMapping("/user/add")
public String add(){
return "add";
}
@RequestMapping("/user/update")
public String update(){
return "update";
}
}

image-20241208165234022

ShiroConfig

与SpringSecuity相同的是,我们需要为它创建COnfig配置文件

新建一个ShiroConfig

1
2
3
4
5
6
7
8
public class ShiroConfig {
// ShiroFilterFactoryBean


// DefaultWebSecurityManager

//创建realm对象
}

然后定义一个realm实体类,它需要继承AuthorizingRealm类,并重写两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}

很眼熟,和SpringSecurity差不多嘛

现在重新再在写ShiroConfig

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
@Configuration
public class ShiroConfig {
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier DefaultWebSecurityManager defaultWebSecurityManager){
// 设置安全管理器
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}

@Bean
// DefaultWebSecurityManager
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}

//创建realm对象
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}

这比SpringSecuity复杂多了,配置好多…

实现登录拦截

要实现登录拦截,要在getShiroFilterFactoryBean里添加代码,我们得先知道权限的管理

1
2
3
4
5
6
7
/*
anon: 无需认证就可以访问
authc: 必须认证了才能访问
user: 必须拥有记住我功能才能用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
1
2
3
4
5
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add", "authc");
filterMap.put("/user/update", "authc");
bean.setFilterChainDefinitionMap(filterMap);

在getShiroFilterFactoryBean里添加如上代码后,就成功的拦截了请求

image-20241208200036280

但是要注意的是Shiro不像SpringSecurity有默认的登录界面,我们得手动写一个,这里就写一个最简单的登录框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面喵</title>
</head>
<body>
<h1>登录喵</h1>
<hr>
<form action="">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p>密码:<input type="submit"></p>
</form>
</body>
</html>

Controller里添加

1
2
3
4
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}

image-20241208200316350

现在当我们访问拦截的请求,就会自动重定向到我们的页面

实现用户认证

Shiro很多的东西都会放到Realm里,权限操作也就是在Realm里

编辑一个Mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();
// 封装用户的登录数据
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try {
subject.login(usernamePasswordToken); // 执行登录方法,如果没有异常就说明OK了
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户名或者密码错误");
return "login";
}
}

然后改一下前端页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org>">
<head>
<meta charset="UTF-8">
<title>登录页面喵</title>
</head>
<body>
<h1>登录喵</h1>
<hr>
<form th:action="@{/login}">
<p th:text = "${msg}" style="color: red"></p>
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p>密码:<input type="submit"></p>
</form>
</body>
</html>

image-20241208201850861

这里点了一下提交后会发现执行了UserRealm里的方法,现在我们就可以在UserRealm里实现具体方法了

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证");
String name = "test";
String password = "123456";
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if (!token.getUsername().equals(name)) {
return null; //抛出异常
}
// 密码认证,shiro做
return new SimpleAuthenticationInfo("", password, "");
}

Shiro整合数据库

接下来连接数据库,密码从数据库里面取

数据库相关环境

Maven里数据库Mysql驱动我一开始导过了

现在导入一下Mybatis的包,以及我习惯用的Lambox

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</dependency>

这里我用以前我SpringSecurity时候创的表格,因为这个密码是SpringSecurity加密的,这里我改成123456

image-20241208203133921

配置数据库连接

配置application.yml

image-20241208205111395

创建mapper软件包,在SpringBoot启动文件上添加包扫描,当然也可以在Mapper文件上添加@Mapper注解,但是推荐还是直接包扫描来的方便

image-20241208203658813

创建UserMapper接口:

1
2
3
4
5
6
7
8
9
10
package com.study.shirostudy.mapper;

import com.study.shirostudy.unti.User;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
@Select("SELECT * FROM user where username = #{username}")
User queryUserByName(String username);
}

创建实体类User:

1
2
3
4
5
6
7
8
src/main/java/com/study/shirostudy/unti/User.java
@Repository
@Data
public class User {
int id;
String username;
String password;
}

UserService&&impl

1
2
3
4
public interface UserService {
public User queryUserByName(String username);
}

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServiceImpl implements UserService {

@Autowired
UserMapper userMapper;

@Override
public User queryUserByName(String username) {
return userMapper.queryUserByName(username);
}
}

Test

1
2
3
4
5
6
7
8
9
10
@SpringBootTest
class ShiroStudyApplicationTests {

@Autowired
UserService mapper;

@Test
void contextLoads() {
System.out.println(mapper.queryUserByName("admin"));
}

测试通过后,就可以把实际业务写入了

Shiro用户授权

ShiroConfig 中的 getShiroFilterFactoryBean 方法添加认证代码

image-20241208215855798

然后在Controller添加上未授权页面

image-20241208220032914

  • ShiroConfig 中的 getShiroFilterFactoryBean 方法中添加
1
2
//未授权页面
bean.setUnauthorizedUrl("/noauth");

UserRealm 类的修改

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
package com.study.shirostudy.controller;


import com.study.shirostudy.Service.UserService;
import com.study.shirostudy.unti.User;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;


public class UserRealm extends AuthorizingRealm {
@Resource
UserService userService;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {


System.out.println("执行授权");
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证");
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
// 连接真实数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) {
return null; // 抛出异常 UnknownAccountException
}

// 密码认证,shiro做
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
}
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 15.4k 访客数 访问量