认识并使用Shiro技术

news2024/11/27 10:41:43

认识并使用Shiro

  • 一、对Shiro的基本认知
    • 1、Shiro是什么?
    • 2、Shiro的核心组件是?
      • 2.1 Subject
      • 2.2 UsernamePasswordToken
      • 2.3 Realm(重点是:AuthorizingRealm用于授权、AuthenticatingRealm用于认证)
      • 2.4 SecurityManager
      • 2.5 ShiroFilterFactoryBean(重点)
  • 二、使用Shiro(通过Spring Boot整合Shiro)
    • 0、需求与思路
      • 0.1 需求
      • 0.2 思路
    • 1、通过脚手架快速创建Spring Boot项目
    • 2、构造db表,为用户登录做准备
    • 3、Spring Boot整合MySQL数据库
      • 3.1 添加依赖
      • 3.2 实现简单的DAO
        • 3.2.1 配置MySQL和mybatis-plus
        • 3.2.2 entity
        • 3.2.3 mapper
        • 3.2.4 单测
    • 4、实现Service层
    • 5、实现Controller层
      • 5.1 用户输入127.0.0.1:8080/main,服务端返回main.html
        • 5.1.1 实现
        • 5.1.2 效果
      • 5.2 同理,编写剩余的html
    • 5、重点:如何使用Shiro安全框架实现认证和授权?
      • 5.1 思路与实现
        • 5.1.1 定义xxxRealm
        • 5.1.2 定义ShiroConfig
            • 5.1.2.1 ShiroFilterFactoryBean (设置过滤器)
            • 5.1.2.2 过滤效果
            • 5.1.2.2 不想用官方的login.jsp来登录,怎么办?
        • 5.1.3 登录(认证)
          • 5.1.3.1 实现
          • 5.1.3.1 效果
        • 5.1.4 授权
      • 5.2 补充:登出
        • 5.2.1 修改index.html
        • 5.2.2 修改UserContoller.java
        • 5.2.3 效果
    • 6、Shiro整合Thymeleaf
      • 6.1 pom.xml中引入依赖
      • 6.2 提供ShiroDialect组件
      • 6.3 修改index.html
      • 6.4 效果
  • 三、[学习资料](https://www.bilibili.com/video/BV16C4y187S9/?p=1&vd_source=4e39b07bacf3530cded08060a2567e22)

一、对Shiro的基本认知

1、Shiro是什么?

  • Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. 【Apache Shiro是一个强大且易用的Java安全框架,它用于认证、授权、加密、对话管理。】官方文档

咱目前主要关注如何通过Shiro实现认证、授权和对话管理。

  • Spring MVC通过DispachServlet来控制请求(/xxx/yyy)路由到xxxController中的哪个方法。类似地,Apache Shiro的核心通过Filter来实现。 先将请求拦截,然后判断是否要认证(要的话,就让用户输入用户名和密码)、是否需要权限(有权限才能访问)。

2、Shiro的核心组件是?

2.1 Subject

  • Subject是安全领域的抽象概念,暂且可以简单理解为当前用户。

官方文档:实际上,我们想把它称为 “User”,因为这样 “更合理”,但我们决定不这么做:太多应用程序的现有 API 已经有了自己的 User 类/框架,我们不想与它们发生冲突。【但实际上叫Subject,依然会有冲突:)】

  • 服务端收到一个请求,xxxController中的对应方法会来处理。
// 当前用户,在整个Shiro框架运作的过程中,通过拿到subject就能拿到用户信息
Subject subject = SecurityUtils.getSubject();

2.2 UsernamePasswordToken

  • 见名知意,封装用户的username和password。
subject.login(new UsernamePasswordToken(username, password));

2.3 Realm(重点是:AuthorizingRealm用于授权、AuthenticatingRealm用于认证)

  • Shiro安全框架为了实现认证和授权,定义了2个抽象方法:
// AuthorizingRealm是授权Realm
public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
	......
	protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection var1);
	......
}
// AuthenticatingRealm是认证Realm
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
	......
	protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}
  • 很显然,这就需要用户去定义xxxRealm,并实现这两个抽象方法。

2.4 SecurityManager

  • 将自定义的xxxRealm组件交给DefaultWebSecurityManager,从而实现认证和授权。

2.5 ShiroFilterFactoryBean(重点)

  • 拦截请求,做一系列处理。例如,某请求要获取xxx.html页面,前提是先登录,那么就跳转到登录页面。

要让Shiro框架运转起来,先要定义出需要的组件,然后按要求组装起来,这就需要ShiroConfig.java。详见:5、重点:如何使用Shiro安全框架实现认证和授权?

二、使用Shiro(通过Spring Boot整合Shiro)

0、需求与思路

0.1 需求

  • 假设我们的网站有4个页面,分别是index.html(首页)、main.html、manage.html、administrator.html
  • 并且,我们需要对用户的访问进行限制:
    (1)用户必须登录才能访问main.html
    (2)用户必须拥有manage权限才能访问manage.html
    (3)用户必须拥有administrator角色才能访问administrator.html

0.2 思路

  • 首先,用户登录时,会给服务端传递username和password信息。服务端拿到这些信息后,就要校验username和password(和当初注册时,落入db表的username、password进行比对)。这就要用到Shiro提供的认证服务。
  • 认证完成后,用户顺利登录咱们的网站,接下来,他会去访问一些页面,而用户能不能访问,就要看用户具有哪些权限以及何种角色。这就要用到Shiro提供的授权服务。例如,授权manage.html页面,要求“用户必须拥有manage权限才能访问manage.html”。

开干吧!

1、通过脚手架快速创建Spring Boot项目

在这里插入图片描述
在这里插入图片描述

  • 添加shiro依赖:
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
</dependency>

2、构造db表,为用户登录做准备

这里假设db表里面已经有一些用户信息了,当用户登录时,会将传入的username和password和db表里的信息进行比对。

  • 利用IDEA的Database连一下本地的MySQL
    在这里插入图片描述
    在这里插入图片描述
  • 连上以后,咱先新建一个database:CREATE DATABASE learn_shiro_mysql;
  • 使用该database,并新建User表:
use learn_shiro_mysql;
CREATE TABLE User (
    id INT PRIMARY KEY,
    username VARCHAR(32),
    password VARCHAR(64),
    permission VARCHAR(32),
    role VARCHAR(32)
);
  • 插入3条记录:
INSERT INTO User (id, username, password)
VALUES (1, '张飞', '123');

INSERT INTO User (id, username, password, permission)
VALUES (2, '关羽', '456', 'manage');

INSERT INTO User (id, username, password, permission, role)
VALUES (3, '刘备', '789', 'manage', 'administrato`在这里插入代码片`r');
  • 查看db表:
    在这里插入图片描述
  • 这张表的设计是不合理的。原因在于,一个用户可能拥有多个permission,即username和permission不是一对一的关系,不适合放一张表里。
  • 但本文重点在于“认识并使用Shiro技术”,所以暂且不在db表的设计上花费功夫。

3、Spring Boot整合MySQL数据库

3.1 添加依赖

<!-- MySQL驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

引入了DAO层框架:mybatis-plus

3.2 实现简单的DAO

3.2.1 配置MySQL和mybatis-plus
# 数据库配置
spring:
  datasource:
    # Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'.
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/learn_shiro_mysql
    username: root
    password: xxx

# mybatis-plus配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

learn_shiro_mysql是上面建好的database。

3.2.2 entity

与User表一一对应

@Data
@TableName("User")
public class User {
    private Integer id;
    private String username;
    private String password;
    private String permission;
    private String role;
}
3.2.3 mapper

通过mybatis-plus进行增删改查

/**
 * 不添加@Repository注解,当通过@Autowired注入时:<br>
 * <p>
 *    @Autowired <br>
 *    private UserMapper userMapper; <br>
 * </p>
 * IDEA会给userMapper打上红色下划线,提示:Could not autowire. No beans of 'UserMapper' type found. <br>
 * 这是因为UserMapper是接口,不能被实例化。但实际上,运行时会生成一个代理对象,代理对象会继承UserMapper接口,所以可以被@Autowired注入。<br>
 * 为了消除红色下划线,所以添加@Repository注解。
 * @Autowired
 * private UserMapper userMapper;
 */

@Repository
public interface UserMapper extends BaseMapper<User> {

}
3.2.4 单测
@SpringBootTest
public class UserMapperTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectAllUser() {
        userMapper.selectList(null).forEach(System.out::println);
    }

}
User(id=1, username=张飞, password=123, permission=null, role=null)
User(id=2, username=关羽, password=456, permission=manage, role=null)
User(id=3, username=刘备, password=789, permission=manage, role=administrator)

4、实现Service层

  • 接口
public interface IUserService {
    User selectByUsername(String username);
}
  • 实现类
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public @Nullable User selectByUsername(String username) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        return userMapper.selectOne(queryWrapper);
    }
}
  • 单测
@SpringBootTest
public class UserServiceImplTest {
    @Autowired
    private IUserService userService;

    @Test
    public void testSelectByUsername() {
        String username = "刘备";
        System.out.println(userService.selectByUsername(username));
    }
}
User(id=3, username=刘备, password=789, permission=manage, role=administrator)

5、实现Controller层

5.1 用户输入127.0.0.1:8080/main,服务端返回main.html

5.1.1 实现
@Controller
public class UserController {
    @GetMapping("/{url}")
    public String redirect(@PathVariable String url) {
        // 例如,如果url的值为"main",就返回"main"视图
        return url;
    }
}
  • 在Spring MVC中,如果一个xxxController方法返回一个字符串,那么这个字符串通常被解析为一个视图名(View Name),Spring MVC会基于这个视图名来选择相应的视图(例如一个JSP页面或者一个HTML页面)进行渲染。
  • 在Spring Boot项目的"src/main/resources/templates"目录下创建一个"main.html"文件。这个文件就是要返回的Thymeleaf模板。
  • 当访问"/main"时,Spring MVC就会找到并渲染名为"main"的Thymeleaf模板文件(即"main.html"),并用它来渲染响应。
5.1.2 效果

在这里插入图片描述

  • 服务端有异常:org.thymeleaf.exceptions.TemplateInputException: Error resolving template [favicon.ico], template might not exist or might not be accessible by any of the configured Template Resolvers
  • 解决办法:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
</head>
<body>
<h1>I am main.html !</h1>
</body>
</html>

如上所述,补上<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
参考资料

5.2 同理,编写剩余的html

  • manage.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
</head>
<body>
    <h1>I am manage.html !</h1>
</body>
</html>
  • administrator.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
</head>
<body>
    <h1>I am administrator.html !</h1>
</body>
</html>
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
</head>
<body>
    <h1>I am index.html !</h1>
    <a href="/main">main</a> | <a href="/manage">manage</a> | <a href="/administrator">administrator</a>

</body>
</html>

每个html中都要补上<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>,着实很离谱。

// 其实还可以这么做:
@GetMapping("favicon.ico")
@ResponseBody
void favicon() {
    // 方法体为空即可
}

当然了,正常情况下, 是会提供resources/favicon.ico。所以,不如去网上找一个favicon.ico。

5、重点:如何使用Shiro安全框架实现认证和授权?

目前,用户没有登录,便可以访问上述所有html,这显然是不安全的。因此,我们要使用Shiro技术来认证和授权。

5.1 思路与实现

  • 我理解,任何框架的使用思路都是,主流程交给框架,配置交给开发者。
5.1.1 定义xxxRealm
  • Shiro安全框架为了实现认证和授权,定义了2个抽象方法:
// AuthorizingRealm是授权Realm
public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
	......
	protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection var1);
	......
}
// AuthenticatingRealm是认证Realm
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
	......
	protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}
  • 很显然,这就需要用户去定义xxxRealm,并实现这两个抽象方法。
public class UserRealm extends AuthorizingRealm {
	/**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    	// 暂时 return null;
		return null;
    }

	/**
     * 认证,即校验用户的用户名和密码
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    	// 暂时 return null;
		return null;
    }
}
  • 定义好了UserRealm,Shiro怎么感知到呢?–> 当然是配置UserRealm啊,有Spring的帮助下,我们靠ShiroConfig来配置。
5.1.2 定义ShiroConfig
@Configuration
public class ShiroConfig {

    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(userRealm);
        return defaultWebSecurityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        return shiroFilterFactoryBean;
    }
}
5.1.2.1 ShiroFilterFactoryBean (设置过滤器)
  • 满足需求的代码:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    
    // 设置过滤器
    Map<String, String> filterChainDefinitionMap = new HashMap<>();
    filterChainDefinitionMap.put("/main", "authc");
    filterChainDefinitionMap.put("/manage", "perms[manage]");
    filterChainDefinitionMap.put("/administrator", "role[administrator]");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
    return shiroFilterFactoryBean;
}
  • 理论补充:
    (1)认证过滤器:
    1)anon: 无需认证
    2)authc: 必须认证
    3)authcBasic: 需要通过HTTP Basic认证
    4)user: 只要曾经被Shiro记录即可,比如:记住我

    (2)授权过滤器
    1)perms: 必须拥有某个权限才能访问
    2)roles: 必须拥有某个角色才能访问
    3)port: 请求的端口必须是指定值才可以
    4)rest: 请求必须基于RESTful (POST、PUT、GET、DELETE)
    5)ssl: 必须是安全的URL请求(协议HTTPS)

5.1.2.2 过滤效果

在这里插入图片描述

不登录,就没法看有限制的页面了!

5.1.2.2 不想用官方的login.jsp来登录,怎么办?
  • 设置登录页面
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ......
    
    // 设置登录页面
    shiroFilterFactoryBean.setLoginUrl("/login");
    
    ......
}
  • 提供login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <form action="/login" method="post">
    <input type="text" name="username" placeholder="username">
    <input type="password" name="password" placeholder="password">
    <input type="submit" value="login">
  </form>
</body>
</html>
  • 效果:
    在这里插入图片描述
5.1.3 登录(认证)
5.1.3.1 实现
  • Controller层
@PostMapping("/login")
public String login(String username, String password, Model model) {
    // 将用户登录的认证过程交给Shiro
    Subject subject = SecurityUtils.getSubject();
    try {
        subject.login(new UsernamePasswordToken(username, password));
        return "index";
    } catch (UnknownAccountException | IncorrectCredentialsException e) {
        // 重新返回登录页面
        model.addAttribute("error", "用户名不存在 或者 密码错误");
        return "login";
    } catch (Exception e) {
        // 重新返回登录页面
        model.addAttribute("error", "登录出错");
        return "login";
    }
}

使用Model对象将错误信息传递给前端显示。Model是Spring MVC中一个非常重要的对象,它封装了视图显示所需要的数据,在视图中可以很方便的取出Model中的数据。

  • 完善“5.1.1 定义xxxRealm”中的UserRealm类的doGetAuthenticationInfo方法
@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (authenticationToken instanceof UsernamePasswordToken) {
            UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
            User user = userService.selectByUsername(upToken.getUsername());
            if (user != null) {
                return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
            }
        }
        return null;
    }

当执行 subject.login(new UsernamePasswordToken(username, password));时,会执行到doGetAuthenticationInfo方法中。

根据用户传入的username去查User表,
(1)没查到,返回null
(2)查到了,但用户传入的密码和User表中的密码不匹配,抛异常
(3)查到了,密码也匹配上了,登录成功

5.1.3.1 效果
  • 登录失败
    在这里插入图片描述
  • 登录成功后,点击“manage”
    在这里插入图片描述

接下来,说明还未授权,接下来就要准备授权了。

  • 这里先做一下优化,当未授权时,返回一个用户易读的信息。

(1)ShiroConfig.java

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ......
    
    // 设置未授权页面
    shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
    return shiroFilterFactoryBean;
}

(2)UserController

@GetMapping("/unauthorized")
@ResponseBody
public String unauthorized() {
    return "您没有权限访问该页面!";
}

(3)效果
在这里插入图片描述

5.1.4 授权
  • 实现:
/**
 * 授权
 * @param principalCollection
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    Subject currentUser = SecurityUtils.getSubject();
    Object tmpUser = currentUser.getPrincipal();
    if (tmpUser instanceof User) {
        User user = (User) tmpUser;

        // 角色
        Set<String> roles = new HashSet<>();
        roles.add(user.getRole());

        // 权限
        Set<String> perms = new HashSet<>();
        perms.add(user.getPermission());

        SimpleAuthorizationInfo saInfo = new SimpleAuthorizationInfo();
        saInfo.setRoles(roles);
        saInfo.setStringPermissions(perms);

        return saInfo;
    } else {
        return null;
    }
}

5.2 补充:登出

(1)登录时,我需要知道当前页面是谁在访问,此需求可以简化为:登录后,显示“欢迎xxx回家~”
(2)然后,要提供登出的按钮。

5.2.1 修改index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>I am index.html !</h1>
    <div th:if="${session.user != null}">
        <p>Welcome, <span th:text="${session.user.username}"></span>!</p>
    </div>
    <a href="/main">main</a> | <a href="/manage">manage</a> | <a href="/administrator">administrator</a>
    <div>
        <a href="/logout">logout</a>
    </div>
</body>
</html>

IDEA会在user下标注黄色波浪线,看着真难受啊~

5.2.2 修改UserContoller.java
@PostMapping("/login")
public String login(String username, String password, Model model) {
    // 将用户登录的认证过程交给Shiro
    Subject subject = SecurityUtils.getSubject();
    try {
        subject.login(new UsernamePasswordToken(username, password));
        subject.getSession().setAttribute("user", subject.getPrincipal());
        return "index";
    } catch (UnknownAccountException | IncorrectCredentialsException e) {
        ...
    } catch (Exception e) {
        ...
    }
}

@GetMapping("/logout")
public String logout() {
    // 退出登录
    SecurityUtils.getSubject().logout();
    return "login";
}

(1)当执行subject.login(new UsernamePasswordToken(username, password));时,会执行UserRealm类的doGetAuthenticationInfo方法,如果登录成功,那么return new SimpleAuthenticationInfo(user, user.getPassword(), getName());此时,user赋予了principal。
(2)因此subject.getPrincipal()取出的便是user。

5.2.3 效果

在这里插入图片描述

6、Shiro整合Thymeleaf

刘备拥有main.html、manage.html、administrator.html的权限,所以可以看到3个main、manage、administrator的链接,那么对于没有权限的人,咱希望他不要看到对应的链接。

6.1 pom.xml中引入依赖

<!-- Thymeleaf整合Shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>

6.2 提供ShiroDialect组件

@Bean
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}

6.3 修改index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>I am index.html !</h1>
    <div th:if="${session.user != null}">
        <p>Welcome, <span th:text="${session.user.username}"></span>! <a href="/logout">logout</a></p>
    </div>
    <a href="/main">main</a>
    <div shiro:hasPermission="manage">
        <a href="/manage">manage</a>
    </div>
    <div shiro:hasRole="administrator">
        <a href="/administrator">administrator</a>
    </div>
</body>
</html>

6.4 效果

在这里插入图片描述

三、学习资料

讲得真不错~ 感谢老师~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1399608.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NLP论文阅读记录 - 2021 | WOS 基于多头自注意力机制和指针网络的文本摘要

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.问题定义和解决问题的假设问题定义解决问题的假设 三.本文方法3.1 总结为两阶段学习3.1.1 基础系统 3.2 重构文本摘要 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4…

面试官:什么是泛型擦除、泛型上界、泛型下界、PECS原则?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 问题1&#xff1a;什么是PECS原则&#xff1f; 说说具体怎么…

回溯法:回溯法通用模版以及模版应用

从一个问题开始 给定两个整数 n 和 k&#xff0c;返回 1 ... n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4] ] 很容易想到 用两个for循环就可以解决。 如果n为100&#xff0c;k为50呢&#xff0c;那就50层for循…

文字的baseLine算法

使用canvas的drawText方法时候&#xff0c;除了要传入画笔和text还需要传入一个x坐标和y坐标。这边的x和y坐标是Baseline的坐标。 public void drawText(NonNull String text, float x, float y, NonNull Paint paint) {super.drawText(text, x, y, paint);} top:是 baseLine到…

微信小程序之WXML 模板语法之数据绑定、事件绑定、wx:if和列表渲染

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

消息中间件之Kafka(二)

1.Kafka线上常见问题 1.1 为什么要对topic下数据进行分区存储? 1.commit log文件会受到所在机器的文件系统大小的限制&#xff0c;分区之后可以将不同的分区放在不同的机器上&#xff0c; 相当于对数据做了分布式存储&#xff0c;理论上一个topic可以处理任意数量的数据2.提…

OpenHarmony 应用开发入门 (二、应用程序包结构理解及Ability的跳转,与Android的对比)

在进行应用开发前&#xff0c;对程序的目录及包结构的理解是有必要的。如果之前有过android开发经验的&#xff0c;会发现OpenHarmony的应用开发也很简单&#xff0c;有很多概念是相似的。下面对比android分析总结下鸿蒙的应用程序包结构&#xff0c;以及鸿蒙对比android的诸多…

【报错】Arco新建工程时 Error: spawnSync pnpm.cmd ENOENT

文章目录 安装环境开始安装选择技术栈选择pro项目遇到的问题 安装步骤&#xff1a;https://arco.design/vue/docs/pro/start 安装环境 npm i -g arco-cli开始安装 arco init hello-arco-pro选择技术栈 ? 请选择你希望使用的技术栈React❯ Vue选择pro项目 ? 请选择一个分类业…

智谱AI发布新一代国产文本生成模型:GLM-4,“宣称”性能逼近GPT-4 (怎么又是GPT )

希望别又是一个只顾着跑分数不注重性能的东西。。。 智谱AI GLM-4介绍体验网址链接&#xff1a;智谱AI开放平台 更多消息&#xff1a;AI人工智能行业动态&#xff0c;aigc应用领域资讯 智谱AI是一家专注于人工智能技术研发和应用的公司&#xff0c;致力于打造全球领先的大模型…

2024年美赛数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

第二课:BERT

文章目录 第二课&#xff1a;BERT1、学习总结&#xff1a;为什么要学习BERT&#xff1f;预训练模型的发展历程BERT结构BERT 输入BERT EmbeddingBERT 模型构建BERT self-attention 层BERT self-attention 输出层BERT feed-forward 层BERT 最后的Add&NormBERT EncoderBERT 输…

深入剖析 Git 对象底层原理

一、引言 在我们日常使用 Git 时&#xff0c;通常的操作是&#xff1a; 在写完一段代码后&#xff0c;执行 git add命令&#xff0c;将这段代码添加到暂存区中然后再执行 git commit和 git push 命令&#xff0c;将 本地 Git 版本库中的提交同步到服务器中的版本库中 Git 在…

phpStorm 设置终端为git bash

环境&#xff1a; windows , PhpStorm 2022 为自己的终端配置git样式的使用&#xff0c; 默认终端样式 一、打开设置&#xff0c;选择git bin 二、重新打开终端 不加--login -i 的终端 加了--login -i 的终端 最重要的一点是什么&#xff0c;他可以像mac一样支持 ctrlv 复…

【学习记录】Ouster雷达运行fastlio提示 Failed to find match for field ‘ring‘ 的解决办法

本文仅用于个人记录。 在使用ouster雷达运行fastlio代码时&#xff0c;提示 Failed to find match for field ‘ring’ 但ouster雷达确实是发布了ring信息&#xff0c;可以从启动的rviz里面看到包括ring。 进一步检查&#xff0c;发现ouster对ring的定义是 uint_16t&#xf…

Redis: Redis介绍

文章目录 一、redis介绍二、通用的命令三、数据结构1、字符串类型&#xff08;String&#xff09;&#xff08;1&#xff09;介绍&#xff08;2&#xff09;常用命令&#xff08;3&#xff09;数据结构 2、列表&#xff08;List&#xff09;&#xff08;1&#xff09;介绍&…

【Linux的权限命令详解】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 shell命令以及运行原理 Linux权限的概念 Linux权限管理 一、什么是权限&#xff1f; 二、权限的本质 三、Linux中的用户 四、linux中文件的权限 4.1、文件访问…

Minio文件分片上传实现

资源准备 MacM1Pro 安装Parallels19.1.0请参考 https://blog.csdn.net/qq_41594280/article/details/135420241 MacM1Pro Parallels安装CentOS7.9请参考 https://blog.csdn.net/qq_41594280/article/details/135420461 部署Minio和整合SpringBoot请参考 https://blog.csdn.net/…

BlueBunny:基于低功耗蓝牙的Bash bunny命令控制C2框架

关于BlueBunny BlueBunny是一款功能强大的命令控制框架&#xff0c;该工具基于低功耗蓝牙实现数据通信&#xff0c;可以帮助广大研究人员直接通过蓝牙将控制指令发送给Bash Bunny。 什么是Bash Bunny Bash Bunny是一款类似于USB Rubber Ducky的多功能USB攻击工具&#xff08;…

认识并使用LlamaIndex

认识并使用LlamaIndex 一、认识LlamaIndex1、是什么2、为什么要搞Llama Index&#xff1f;3、怎么搞Llama Index&#xff1f;3.1 方案1&#xff1a;用你的数据对LLM进行微调&#xff08;fine-tune&#xff09;3.2 方案2&#xff1a;[检索增强生成&#xff08;RAG&#xff09;](…

实验六 模式对象管理与安全管理

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…