springboot整合shiro

shiro官网:Apache Shiro | Simple. Java. Security.

GitHub网址:GitHub - apache/shiro: Apache Shiro

shiro快速启动

参照网址:https://shiro.apache.org/tutorial.html

GitHub上shiro快速启动: https://github.com/apache/shiro/tree/main/samples/quickstart

建的是一个普通的maven项目,下图是包位置

image-20221030171604719

pom.xml

不要使用上面参照网址的pom.xml,参照网址中的是测试使用的,会报错

image-20221030171414342

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
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.10.0</version>
</dependency>
<!--官方的有坑,会直接报错,而且都是测试用的,不会输出-->
<!--<dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-simple</artifactId>-->
<!-- <version>1.7.21</version>-->
<!-- <scope>test</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>jcl-over-slf4j</artifactId>-->
<!-- <version>1.7.21</version>-->
<!-- <scope>test</scope>-->
<!--</dependency>-->
<!-- configure logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>

log4j2.xml

狂神说使用的是log4j,我使用的是官方github上的

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
59
60
61
62
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->

<Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->

<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="net.sf.ehcache" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache.shiro.util.ThreadContext" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

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
# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

Quickstart.java

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {

private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

public static void main(String[] args) {
log.info("My First Apache Shiro Application");

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

// get the currently executing user:
// 获取当前的用户对象 subject
Subject currentUser = SecurityUtils.getSubject();

// Do some stuff with a Session (no need for a web or EJB container!!!)
// 通过当前用户拿到session , 可以存值取值
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("subject => session [" + value + "]");
}

// let's login the current user so we can check against roles and permissions:
// 测试当前的用户是否被认证
if (!currentUser.isAuthenticated()) {
// token 令牌 , 随机获取,没有设置
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true); // 设置记住我
try {
currentUser.login(token); // 执行了登录操作
} catch (UnknownAccountException uae) { // 用户名不存在
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) { // 密码不对
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { // 用户被锁定(登录失败次数过多)
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) { // 认证异常
//unexpected condition? error?
}
}

//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

//test a role: 测试角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}

// 粗粒度
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

// 细粒度
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

// 注销
//all done - log out!
currentUser.logout();

// 结束系统
System.exit(0);
}
}

运行结果

image-20221030171843912

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取当前的用户对象 subject
Subject currentUser = SecurityUtils.getSubject();

// 通过当前用户拿到session , 可以存值取值
Session session = currentUser.getSession();

// 判断是否被认证
currentUser.isAuthenticated();

// 获取当前用户的认证
currentUser.getPrincipal();

// 是否拥有某个角色
currentUser.hasRole("schwartz");

// 获取当前用户的权限
currentUser.isPermitted("lightsaber:wield");

// 注销
currentUser.logout();

springboot集成shiro

搭建环境

导入shiro和thymeleaf依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--
Subject 用户
SecurityManager 管理所有用户
Realm 连接数据
-->

<!--导入shiro-->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.10.0</version>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

编写shiro的两个核心配置

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.li.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

// ShiroFilterFactoryBean shiro过滤的对象:3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}


// DefaultWebSecurityManager:2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 UserRealm,通过spring来进行关联,如上面的参数 @Qualifier("userRealm") ==》 下面的方法名
securityManager.setRealm(userRealm);


return securityManager;
}


// 创建 realm 对象,需要自定义类:1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}

}

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
package com.li.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

// 自定义的realm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权doGetAuthorizationInfo");
return null;
}

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证doGetAuthorizationInfo");
return null;
}
}

shiro实现登录拦截

修改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
27
28
29
30
31
32
// ShiroFilterFactoryBean  shiro过滤的对象:3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);

// 添加shiro的内置过滤器
/*
anno: 无需认证就能访问
authc:必须认证了才能访问
user: 必须拥有”记住我功能“ 才能使用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/

// 登录拦截
Map<String, String> filterMap = new LinkedHashMap<>();

//filterMap.put("/user/add","authc");
//filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");

bean.setFilterChainDefinitionMap(filterMap);


// 设置登录的请求
bean.setLoginUrl("/toLogin");


return bean;
}

配置登录界面及其controller

1
2
3
4
5
6
7
@Controller
public class MyController {
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
}

用户认证

controller中进行验证,获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RequestMapping("/login")
public String login(String username, String password, Model model){
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();

// 封装用户的登陆数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);

try{
subject.login(token); // 执行登录的方法,如果没有异常就ok
return "index";
}catch (UnknownAccountException e){ // 用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){ //密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}

修改UserRealm中认证的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
System.out.println("执行了认证doGetAuthorizationInfo");


// 用户名,密码 数据库中取得
String name = "root";
String password = "123456";

UsernamePasswordToken userToken = (UsernamePasswordToken) Token;

if(!userToken.getUsername().equals(name)){
return null; // 抛出异常 UnknownAccountException
}

// 密码认证,shiro自己做

return new SimpleAuthenticationInfo("",password,"");
}

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<h1>登录</h1>
<hr>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit"></p>
</form>

</body>
</html>

整合Mybatis

导入mysqllog4jmybatis依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

配置properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 修改日期格式
spring.mvc.format.date=yyyy-MM-dd
# 配置mybatis
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=false&characterEncoding=utf-8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456


#整合mybatis
mybatis.type-aliases-package=com.li.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

剩下的就是pojo, mapper,service的编写了,就不详细描写了。

shiro使用md5 加盐加密

上面已经配置好mybatis了,但是用户认证使用的是自己模拟的密码,我们这里修改成数据库中的密码,同时实现shiromd5加盐加密

如果要使用md5加盐加密,需要在ShiroConfig中添加密码加密的方法

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
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
59
60
61
62
63
64
65
66
67
68
69
70
@Configuration
public class ShiroConfig {

// ShiroFilterFactoryBean shiro过滤的对象:3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);

// 添加shiro的内置过滤器
/*
anno: 无需认证就能访问
authc:必须认证了才能访问
user: 必须拥有”记住我功能“ 才能使用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/

// 登录拦截
Map<String, String> filterMap = new LinkedHashMap<>();

//filterMap.put("/user/add","authc");
//filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");

bean.setFilterChainDefinitionMap(filterMap);


// 设置登录的请求
bean.setLoginUrl("/toLogin");

return bean;
}


// DefaultWebSecurityManager:2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 UserRealm,通过spring来进行关联,如上面的参数 @Qualifier("userRealm") ==》 下面的方法名
securityManager.setRealm(userRealm);


return securityManager;
}


// 创建 realm 对象,需要自定义类:1
@Bean
public UserRealm userRealm(@Qualifier("matcher") HashedCredentialsMatcher matcher){
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(matcher);
return userRealm;
}


// 自定义密码加密
@Bean(name = "matcher")
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置加密规则
matcher.setHashAlgorithmName("md5");
// 设置加密次数
matcher.setHashIterations(10);

return matcher;
}

}

修改 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
41
42
43
44
45
46
47
48
49
50
51
package com.li.config;

import com.li.pojo.User;
import com.li.service.UserService;
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;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

// 自定义的realm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {

@Autowired
UserService userService;

// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权doGetAuthorizationInfo");
return null;
}

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
System.out.println("执行了认证doGetAuthorizationInfo");

UsernamePasswordToken userToken = (UsernamePasswordToken) Token;

// 连接真实的数据库
User user = userService.queryUserByName(userToken.getUsername());
if(user == null){ // 没有这个用户 UnknownAccountException
return null;
}
// 设置 密码的加盐值
ByteSource salt = ByteSource.Util.bytes(user.getName());
// 可以加密, MD5 MD5盐值加密
// 密码认证,shiro自己做
return new SimpleAuthenticationInfo(
user, // 当前用户 ,和上面的doGetAuthenticationInfo方法对应,也可以传 username
user.getPwd(), // 从数据中查处的安全密码
salt, // 用户的密码是加盐md5 的
getName() // 当前 Realm 的名字
);
}
}

出现问题

运行之后,Shiro异常java.lang.IllegalArgumentException: Odd number of characters

原因:我们在使用MD5加盐加密的时候,验证登录密码时,数据库中的密码没有经过加密,还是之前设置的密码,所以会爆出一场。

解决办法:将数据库中的密码也进行加密,然后就可以验证成功


下面这个方法可以查看加密后的密码,以便修改数据库中的密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
void contextLoads() {

User user = userService.queryUserByName("平安");
System.out.println(userService.queryUserByName("平安"));
System.out.println(md5(user.getPwd(), user.getName()));
}


public static final String md5(String password, String salt){
//加密方式
String hashAlgorithmName = "MD5";
//盐:为了即使相同的密码不同的盐加密后的结果也不同
ByteSource byteSalt = ByteSource.Util.bytes(salt);
//密码
Object source = password;
//加密次数
int hashIterations = 10;
SimpleHash result = new SimpleHash(hashAlgorithmName, source, byteSalt, hashIterations);
return result.toString();
}

运行后结果

image-20221030204719821

用户授权

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
27
28
29
30
31
32
33
34
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);

// 添加shiro的内置过滤器
/*
anno: 无需认证就能访问
authc:必须认证了才能访问
user: 必须拥有”记住我功能“ 才能使用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/

// 拦截
Map<String, String> filterMap = new LinkedHashMap<>();

// 授权,正常情况下,没有授权会跳转到未授权界面
filterMap.put("/user/add","perms[user:add]");

//filterMap.put("/user/add","authc");
//filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");

bean.setFilterChainDefinitionMap(filterMap);

// 设置登录的请求
bean.setLoginUrl("/toLogin");
// 未授权界面
bean.setUnauthorizedUrl("/noauth");

return bean;
}

修改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
41
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权doGetAuthorizationInfo");

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();


// 拿到当前登陆的这个对象
Subject subject = SecurityUtils.getSubject();
// 取出当前登录的对象,和下面SimpleAuthenticationInfo传的第一个关联
User currentUser = (User) subject.getPrincipal(); // 拿到user对象
// 设置当前用户的权限,从数据库中读出
info.addStringPermission(currentUser.getPerms());

return info;
}

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
System.out.println("执行了认证doGetAuthorizationInfo");

UsernamePasswordToken userToken = (UsernamePasswordToken) Token;

// 连接真实的数据库
User user = userService.queryUserByName(userToken.getUsername());
if(user == null){ // 没有这个用户 UnknownAccountException
return null;
}
// 设置 密码的加盐值
ByteSource salt = ByteSource.Util.bytes(user.getName());
// 可以加密, MD5 MD5盐值加密
// 密码认证,shiro自己做
return new SimpleAuthenticationInfo(
user, // 当前用户 ,和上面的doGetAuthenticationInfo方法对应,同时向授权部分传递当前用户的信息
user.getPwd(), // 从数据中查处的安全密码
salt, // 用户的密码是加盐md5 的
getName() // 当前 Realm 的名字
);
}

controller设置未授权界面

1
2
3
4
@RequestMapping("/noauth")
public String Unauthorized(){
return "401";
}

整合Thymeleaf

将目前完成的项目整合thymeleaf

导入依赖

1
2
3
4
5
6
<!--整合shiro+thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>

ShiroConfig中设置thymeleaf

1
2
3
4
5
// 整合 ShiroDialect:用来整合shiro+thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}

UserRealm中设置session

在session中设置字符,表示登录成功

1
2
3
4
// 在session中设置字符,表示登录成功
Subject subject1 = SecurityUtils.getSubject();
Session session = subject1.getSession();
session.setAttribute("loginUser",user);

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>首页</h1>
<p th:text="${msg}"></p>
<!--从session中获取值-->
<div th:if="${session.loginUser == null}">
<a th:href="@{/toLogin}">登录</a>
</div>

<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>

springboot集成shiro整合多个Realm

由于在实际项目开发的过程中,不可能只有一个实体类,一定会有多个权限,所以整合多个Realm是一个十分重要的事情

pom.xml我们之前已经配置好了,所以这里就不在赘述了。

配置工具类,编写登录类型的枚举

重写UsernamePasswordToken

自定义认证器,继承ModularRealmAuthenticator

修改具体的每个UserRealm类

修改ShiroConfig

修改controller

配置工具类,编写登录类型的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.li.config.shiroUtils;

// 登录类型的枚举
public enum LoginType {
USER("User"), ADMIN("Admin");

private String type; //定义的是登陆的类型

private LoginType(String type){
this.type=type;
}

@Override
public String toString() {
return this.type.toString();
}
}

重写UsernamePasswordToken

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

import org.apache.shiro.authc.UsernamePasswordToken;

// 对UsernamePasswordToken进行重写
public class CustomToken extends UsernamePasswordToken {

//定义登陆的类型是为了在后面的校验中 去选择使用哪一个realm
private String loginType;

public CustomToken(String userName,String password,String loginType){
super(userName,password);
this.loginType=loginType;
}

public void setLoginType(String loginType) {
this.loginType = loginType;
}

public String getLoginType() {
return loginType;
}

}

自定义认证器

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
package com.li.config.shiroUtils;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;

// 继承shiro配置多个Realm的类
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {

private static final Logger logger = LoggerFactory.getLogger(CustomModularRealmAuthenticator.class);
/**
* 想干一件事
* 就是通过传入数据的类型 来选择使用哪一个Realm
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
//判断getRealm是否为空
assertRealmsConfigured();
//获取前端传递过来的token
CustomToken customToken=(CustomToken)authenticationToken;
//现在就可以获取这个登陆的类型了
String loginType = customToken.getLoginType(); // 登陆类型 1:User Admin
//获取所有的realms()
Collection<Realm> realms = getRealms();
//登陆类型对应的所有realm全部获取到
Collection<Realm> typeRealms=new ArrayList<>();
for (Realm realm:realms){
//realm类型和现在登陆的类型做一个对比
if(realm.getName().contains(loginType.toString())){ //就能分开这两个realm
typeRealms.add(realm);
}
}

if(typeRealms.size()==1){
logger.info("doSingleRealmAuthentication");
return doSingleRealmAuthentication(typeRealms.iterator().next(),customToken);
}else{
logger.info("doMultiRealmAuthentication");
return doMultiRealmAuthentication(typeRealms,customToken);
}
}
}

修改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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.li.config;

import com.li.pojo.User;
import com.li.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

// 自定义的realm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {

@Autowired
UserService userService;

// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

// 判断获取的subject是不是自己得
if(principalCollection.getPrimaryPrincipal() instanceof User){
// 拿到当前登陆的这个对象
Subject subject = SecurityUtils.getSubject();
// 取出当前登录的对象,和下面SimpleAuthenticationInfo传的第一个关联
User currentUser = (User) subject.getPrincipal(); // 拿到user对象
// 设置当前用户的权限,从数据库中读出
info.addStringPermission(currentUser.getPerms());

return info;
}else return null;
}

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {

UsernamePasswordToken userToken = (UsernamePasswordToken) Token;
// 连接真实的数据库
User user = userService.queryUserByName(userToken.getUsername());
if(user == null){ // 没有这个用户 UnknownAccountException
return null;
}

// 在session中设置字符,表示登录成功
Subject subject1 = SecurityUtils.getSubject();
Session session = subject1.getSession();
session.setAttribute("loginUser",user);


// 设置 密码的加盐值
ByteSource salt = ByteSource.Util.bytes(user.getName());
// 可以加密, MD5 MD5盐值加密
// 密码认证,shiro自己做
return new SimpleAuthenticationInfo(
user, // 当前用户 ,和上面的doGetAuthenticationInfo方法对应
user.getPwd(), // 从数据中查处的安全密码
salt, // 用户的密码是加盐md5 的
getName() // 当前 Realm 的名字
);
}
}

修改AdminRealm

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
59
60
61
62
63
package com.li.config;

import com.li.config.shiroUtils.CustomToken;
import com.li.pojo.Admin;
import com.li.service.AdminService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

public class AdminRealm extends AuthorizingRealm {
@Autowired
AdminService adminService;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

if(principalCollection.getPrimaryPrincipal() instanceof Admin){
// 拿到当前登陆的这个对象
Subject subject = SecurityUtils.getSubject();
// 取出当前登录的对象,和下面SimpleAuthenticationInfo传的第一个关联
Admin currentAdmin = (Admin) subject.getPrincipal();
// 设置当前用户的权限,从数据库中读出
info.addStringPermission(currentAdmin.getPerms());
return info;
}else return null;

}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
CustomToken adminToken = (CustomToken) Token;
// 连接真实的数据库
Admin admin = adminService.queryAdminByName(adminToken.getUsername());
if(admin == null){ // 没有这个用户 UnknownAccountException
return null;
}

// 在session中设置字符,表示登录成功,目前还没有学会使用token,所以先使用session
Subject subject1 = SecurityUtils.getSubject();
Session session = subject1.getSession();
session.setAttribute("loginUser",admin);


// 设置 密码的加盐值
ByteSource salt = ByteSource.Util.bytes(admin.getName());
// 可以加密, MD5 MD5盐值加密
// 密码认证,shiro自己做
return new SimpleAuthenticationInfo(
admin, // 当前用户 ,和上面的doGetAuthenticationInfo方法对应
admin.getPassword(), // 从数据中查处的安全密码
salt, // 用户的密码是加盐md5 的
getName() // 当前 Realm 的名字
);
}
}

修改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
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package com.li.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.li.config.shiroUtils.CustomModularRealmAuthenticator;
import com.li.config.shiroUtils.RoleOrFilter;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.*;

@Configuration
public class ShiroConfig {

// ShiroFilterFactoryBean shiro过滤的对象:3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);

// 配置 自定义 过滤器,用来同时进行多个授权
RoleOrFilter roleOrFilter = new RoleOrFilter();
Map<String, Filter> myFilterMap = new HashMap<>();

myFilterMap.put("e-perms", roleOrFilter);//可以配置RoleOrFilter的Bean
bean.setFilters(myFilterMap);

// 添加shiro的内置过滤器
/*
anno: 无需认证就能访问
authc:必须认证了才能访问
user: 必须拥有”记住我功能“ 才能使用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/

// 拦截
Map<String, String> filterMap = new LinkedHashMap<>();

// 授权,正常情况下,没有授权会跳转到未授权界面
filterMap.put("/user/add","e-perms[add|admin]");
filterMap.put("/user/update","e-perms[update|admin]");
filterMap.put("/admin","e-perms[admin]");

//filterMap.put("/user/add","authc");
//filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");

bean.setFilterChainDefinitionMap(filterMap);


// 设置登录的请求
bean.setLoginUrl("/toLogin");
// 未授权界面
bean.setUnauthorizedUrl("/noauth");

return bean;
}


// DefaultWebSecurityManager:2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 UserRealm,通过spring来进行关联,如上面的参数 @Qualifier("userRealm") ==》 下面的方法名
securityManager.setAuthenticator(authenticator());
List<Realm> realms = new ArrayList<Realm>();
realms.add(userRealm());
realms.add(adminRealm());

securityManager.setRealms(realms);


return securityManager;
}


// 创建 realm 对象,需要自定义类:1
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(getHashedCredentialsMatcher());
return userRealm;
}

@Bean
public AdminRealm adminRealm(){
AdminRealm adminRealm = new AdminRealm();
adminRealm.setCredentialsMatcher(getHashedCredentialsMatcher());
return adminRealm;
}


// 自定义密码加密
@Bean(name = "matcher")
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置加密规则
matcher.setHashAlgorithmName("md5");
// 设置加密次数
matcher.setHashIterations(10);

return matcher;
}

//下面就是认证器的配置
@Bean
public CustomModularRealmAuthenticator authenticator(){
CustomModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
//authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return authenticator;
}


// 整合 ShiroDialect:用来整合shiro+thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}

}

修改controller

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
59
60
61
package com.li.controller;

import com.li.config.shiroUtils.CustomToken;
import com.li.config.shiroUtils.LoginType;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {

private static final String LOGIN_TYPE_user= LoginType.USER.toString();
private static final String LOGIN_TYPE_admin= LoginType.ADMIN.toString();

@RequestMapping("/login")
public String login(String username, String password, Model model){
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();

// 封装用户的登陆数据
CustomToken userToken = new CustomToken(username, password,LOGIN_TYPE_user);

try{
subject.login(userToken); // 执行登录的方法,如果没有异常就ok
return "index";
}catch (UnknownAccountException e){ // 用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){ //密码不存在
model.addAttribute("msg","密码错误");
return "login";
}

}

@RequestMapping("/login2")
public String login2(String username, String password, Model model){

// 封装用户的登陆数据
CustomToken adminToken = new CustomToken(username, password,LOGIN_TYPE_admin);
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();

try{
subject.login(adminToken); // 执行登录的方法,如果没有异常就ok
return "index";
}catch (UnknownAccountException e){ // 用户名不存在
model.addAttribute("msg2","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){ //密码不存在
model.addAttribute("msg2","密码错误");
return "login";
}

}
}

登录的界面

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<h1>登录</h1>
<hr>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit"></p>
</form>

<p th:text="${msg2}" style="color: red"></p>
<form th:action="@{/login2}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit"></p>
</form>

</body>
</html>

实现添加多个权限

自定义一个过滤器来实现RoleOrFilter

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
59
60
61
62
63
64
65
66
67
68
69
package com.li.config.shiroUtils;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


// 配置过滤器,能够同时进行多个权限的认证
public class RoleOrFilter extends AuthorizationFilter {


@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = this.getSubject(request, response);
String[] perms = (String[]) ((String[]) mappedValue);
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
if (!isOneOfPermitted(perms[0], subject)) {
isPermitted = false;
}
} else if (!isAllPermitted(perms,subject)) {
isPermitted = false;
}
}
return isPermitted;
}

/**
* 以“,”分割的权限为并列关系的权限控制,分别对每个权限字符串进行“|”分割解析
* 若并列关系的权限有一个不满足则返回false
*
* @param permStrArray 以","分割的权限集合
* @param subject 当前用户的登录信息
* @return 是否拥有该权限
*/
private boolean isAllPermitted(String[] permStrArray, Subject subject) {
boolean isPermitted = true;
for (int index = 0, len = permStrArray.length; index < len; index++) {
if (!isOneOfPermitted(permStrArray[index], subject)) {
isPermitted = false;
}
}
return isPermitted;
}

/**
* 判断以“|”分割的权限有一个满足的就返回true,表示权限的或者关系
*
* @param permStr 权限数组种中的一个字符串
* @param subject 当前用户信息
* @return 是否有权限
*/
private boolean isOneOfPermitted(String permStr, Subject subject) {
boolean isPermitted = false;
String[] permArr = permStr.split("\\|");
if (permArr.length > 0) {
for (int index = 0, len = permArr.length; index < len; index++) {
if (subject.isPermitted(permArr[index])) {
isPermitted = true;
}
}
}
return isPermitted;
}

}

ShiroConfig中进行引用

ShiroFilterFactoryBean

1
2
3
4
5
6
// 配置 自定义 过滤器,用来同时进行多个授权
RoleOrFilter roleOrFilter = new RoleOrFilter();
Map<String, Filter> myFilterMap = new HashMap<>();

myFilterMap.put("e-perms", roleOrFilter);//可以配置RoleOrFilter的Bean
bean.setFilters(myFilterMap);

遇到的问题

两个Realm的数据串了

第一次配置的时候,没有对每一个Realm类的授权进行验证,由于shiro会一次进入到每一个realm中,所以一直报错 Admin类 无法 转化为 User类(类似是这个,没有截图,所以大致说一下)

解决办法:每次进入授权时都进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

if(principalCollection.getPrimaryPrincipal() instanceof Admin){
// 拿到当前登陆的这个对象
Subject subject = SecurityUtils.getSubject();
// 取出当前登录的对象,和下面SimpleAuthenticationInfo传的第一个关联
Admin currentAdmin = (Admin) subject.getPrincipal();
// 设置当前用户的权限,从数据库中读出
info.addStringPermission(currentAdmin.getPerms());
return info;
}else return null;

}

could not be authenticated by any configured realms

在新的项目中添加shiro进行登录验证的时候,发现一直报一个错误Authentication token of type [class com.example.sengineer.config.shiroUtils.CustomToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens.

原因:使用的shiro的时候,Logintype中的type和realm中的不一致。

解决办法:将pojo中的StudentStudentRealmLoginType中的STUDENT(“Student”)三个的名字改为一样的(重点是Studentimage-20221101120216015

当然,真的原因可能只是 StudentRealmLoginType的不一致(我没有细细研究,只是发现这样能够解决问题,如果你有兴趣可以自己去测试),但是为了代码的一致性,所以建议pojo类也跟上面一致

thymeleafform中的数据无法获取

在测试的时候发现thymeleaf的登录账号无法获取

原因:form表单中的 namecontroller 中的参数名字不一致

image-20221101121007259

解决办法:将两个地方的名字改为一致的