Shiro之认证

发布时间:2024年01月07日

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
Shiro之认证


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

当涉及到应用程序的安全性时,身份验证和授权是至关重要的环节。Shiro 框架以其简单而强大的特性,成为了处理这两个关键任务的流行选择。在这篇博客中,我们将深入探讨 Shiro 框架的认证功能。
通过使用 Shiro,你将能够轻松地管理用户的身份验证过程,确保只有经过授权的用户能够访问受保护的资源。我们将了解 Shiro 的核心概念,如 Subject、Realm 和 Credentials,以及它们在认证过程中的作用。
博客将详细介绍 Shiro 支持的多种认证方式,包括用户名和密码、证书、RememberMe 等。你将学会如何配置和实现这些认证方式,以满足你的应用程序的特定需求。
我们还将探讨 Shiro 的一些高级特性,如权限管理、会话管理和加密等。这些特性将帮助你进一步加强应用程序的安全性,并提供更细粒度的访问控制。
无论你是刚刚开始使用 Shiro,还是已经有一定经验的开发者,这篇博客都将为你提供宝贵的资源和深入的理解。让我们一起探索 Shiro 的认证功能,为你的应用程序构建一个安全可靠的基础。


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是Shiro

Shiro是apache旗下的一个开源安全框架,它可以帮助我们完成身份认证,授权、加密、会话管理等功能。Shiro 的主要目标是让开发者能够轻松地保护他们的应用程序,使其免受未经授权的访问。它有如下特点:

  • 不依赖任何的框架或者容器捆绑,可以独立运行
  • 内置会话管理,适用于Web以及非Web的环境
  • 支持缓存,以提升应用程序的性能
  • 易于理解的API
  • 简单的身份认证,支持多种数据源
  • 简单的认证与授权

二、Shiro的核心功能

Shiro 是一个强大而灵活的 Java 安全框架,其核心功能包括身份验证、授权、会话管理和加密等。以下是 Shiro 的一些核心功能的详细介绍:

  • 身份验证(Authentication):Shiro 支持多种身份验证方式,如用户名和密码、证书、RememberMe 等。它可以与各种数据源集成,如关系型数据库、LDAP 等,以验证用户的身份。
  • 授权(Authorization):一旦用户通过身份验证,Shiro 可以根据用户的角色和权限进行授权。它支持基于角色的访问控制(RBAC)和基于权限的访问控制(PBAC)等授权模型。
  • 会话管理(Session Management):Shiro 提供了会话管理功能,包括创建、维护和销毁会话。它可以与 Web 容器集成,以实现会话的超时和续命。
  • 加密(Encryption):Shiro 支持密码加密、数据加密和签名等加密功能,以确保敏感数据的安全性。
  • 缓存(Caching):Shiro 内置了缓存机制,可以提高性能并减少对数据源的访问。
  • 领域对象(Realm):Shiro 鼓励使用领域对象来表示安全主体(如用户、角色等)和权限,使代码更加清晰和易于维护。
  • 灵活性(Flexibility):Shiro 具有高度的灵活性,可以轻松地与其他框架(如 Spring、Hibernate 等)集成。
  • 可配置性(Configurability):Shiro 提供了灵活的配置选项,可以通过配置文件或注解来定义安全策略。

三、Shiro的核心组件

Shiro 框架有许多核心组件,它们协同工作以提供全面的安全功能。以下是一些 Shiro 的核心组件:

  • Subject:Subject 表示当前执行操作的用户或主体。它持有用户的身份信息和权限信息,并提供了执行安全操作的接口。
  • SecurityManager:SecurityManager 是 Shiro 框架的核心,它负责管理所有的安全操作。它是整个 Shiro 安全体系的入口点,应用程序通过调用 SecurityManager 来进行身份验证、授权等操作。
  • Realm:Realm 是 Shiro 与数据源进行交互的桥梁。它用于存储和检索用户、角色和权限等信息。Shiro 支持多种类型的 Realm,如基于内存的 Realm、基于数据库的 Realm 等。
  • Session:Session 用于管理用户与应用程序之间的会话。它跟踪用户的登录状态,并在需要时提供对用户信息的访问。
  • Authenticator:Authenticator 负责执行用户的身份验证。它可以通过用户名和密码、证书等方式验证用户的身份。
  • Authorizer:Authorizer 负责执行授权操作,即确定用户是否具有访问特定资源或执行特定操作的权限。
  • CachingManager:CachingManager 用于管理 Shiro 的缓存。它可以缓存用户、角色和权限等信息,以提高性能。
  • LogoutFilter:LogoutFilter 用于处理用户的登出操作。它确保在用户登出后清除相关的会话信息。

四、Shiro之认证

项目搭建

1.准备名为myshiro的mysql数据库
2.创建SpringBoot项目,加入相关依赖

<dependencies>
  <!-- SpringMVC -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- Thymeleaf -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <!-- Mysql驱动 -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
  </dependency>
  <!-- MyBatisPlus -->
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
  </dependency>
  <!-- shiro和spring整合包 -->
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.9.0</version>
  </dependency>
  <!-- lombok -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
  <!-- Junit -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <!-- Spring-jdbc -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
</dependencies>

3.编写配置文件application.yml

server:
  port: 80


#日志格式
logging:
  pattern:
   console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'


# 数据源
spring:
  datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql:///myshiro?serverTimezone=UTC
   username: root
   password: root

4.在template文件夹编写项目主页面main.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>主页面</title>
</head>
<body>
<h1>主页面</h1>
</body>
</html>

5.编写页面跳转控制器

@Controller
public class PageController {
  @RequestMapping("/{page}")
  public String showPage(@PathVariable String page) {
    return page;
   }


  // 忽略favicon.ico的获取
  @GetMapping("favicon.ico")
  @ResponseBody
  public void noFavicon() {}
}

自定义Realm

Realm 可以与数据源(如关系型数据库、LDAP 服务器等)进行集成,以获取和验证用户的认证和授权信息。Shiro 提供了多种内置的 Realm 实现,如 IniRealm、JdbcRealm、LdapRealm 等,同时也支持自定义 Realm 以满足特定的需求。
在 Shiro 的安全体系中,Subject 是代表当前用户的对象,它与 Realm 进行交互以进行认证和授权操作。当用户尝试进行身份验证或请求授权时,Subject 会将请求转发给配置的 Realm,Realm 则根据存储的用户信息进行验证和授权决策。

1.准备数据表

CREATE TABLE `users` (
 `uid` int(11) NOT NULL AUTO_INCREMENT,
 `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
 `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
 PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


INSERT INTO `users` VALUES (1, 'zhangsan', '123');

2.编写实体类

@Data
public class Users {
  private Integer uid;
  private String username;
  private String password;
}

3.编写mapper接口

public interface UsersMapper extends BaseMapper<Users> {
}

4.在启动类加载mapper接口

@SpringBootApplication
@MapperScan("com.zhangsan.myshiro1.mapper")
public class Myshiro1Application {
  public static void main(String[] args) {
    SpringApplication.run(Myshiro1Application.class, args);
   }
}

5.编写自定义Realm类

public class MyRealm extends AuthorizingRealm {
  @Autowired
  private UserInfoMapper userInfoMapper;


  // 自定义认证方法
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 1.获取用户输入的用户名
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    String username = token.getUsername();


    // 2.根据用户名查询用户
    QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);
    Users users = usersMapper.selectOne(wrapper);


    // 3.将查询到的用户封装为认证信息
    if (users == null) {
      throw new UnknownAccountException("账户不存在");
     }


    /**
     * 参数1:用户
     * 参数2:密码
     * 参数3:Realm名
     */
    return new SimpleAuthenticationInfo(users,
        users.getPassword(),
        "myRealm");
   }


  // 自定义授权方法
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    return null;
   }
}

容器管理shiro对象

将自定义Realm放入SecurityManager对象中

@Configuration
public class ShiroConfig {
  // 自定义Realm
  @Bean
  public MyRealm myRealm(){
    return new MyRealm();
   }
  
  // SecurityManager对象
  @Bean
  public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
    DefaultWebSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
    // 自定义Realm放入SecurityManager中
    defaultSecurityManager.setRealm(myRealm);
    return defaultSecurityManager;
   }
}

2.创建UserService对象

@Service
public class UsersService {
  @Autowired
  private DefaultWebSecurityManager securityManager;


  public void userLogin(String username, String password) throws AuthenticationException {
    // 1.将SecurityManager对象设置到运行环境中
    SecurityUtils.setSecurityManager(securityManager);
    // 2.获取Subject对象
    Subject subject = SecurityUtils.getSubject();
    // 3.将前端传来的用户名密码封装为Shiro提供的身份对象
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    // 4.Shiro认证
    subject.login(token);
   }
}

3.编写登录控制器方法

@RequestMapping("/user/login2")
public String login2(String username,String password){
  try {
    usersService.userLogin(username,password);
    return "main";
   }catch (AuthenticationException e){
    return "fail";
   }
}

多Realm认证

在实际的开发中,我们的认真逻辑可能不止一种,例如普通用户登录和管理员登录。

1.在数据库创建admin表

CREATE TABLE `admin` (
 `id` int(11) NOT NULL,
 `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
 `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


INSERT INTO `admin` VALUES (1, 'lisi', '123');
INSERT INTO `admin` VALUES (2, 'wangwu', '456');

2.创建Admin实体类和AdminMapper接口

@Data
public class Admin {
  private Integer id;
  private String name;
  private String password;
}


public interface AdminMapper extends BaseMapper<Admin> {
}

3.MyRealm认证User用户,MyRealm2认证Admin用户

public class MyRealm2 extends AuthorizingRealm {
  @Autowired
  private AdminMapper adminMapper;


  // 自定义认证方法
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 1.获取输入的管理员名
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    String username = token.getUsername();


    // 2.根据管理员名查询管理员
    QueryWrapper<Admin> wrapper = new QueryWrapper<Admin>().eq("name", username);
    Admin admin = adminMapper.selectOne(wrapper);


    // 3.将查询到的管理员封装为认证信息
    if (admin == null) {
      throw new UnknownAccountException("账户不存在");
     }


    /**
     * 参数1:管理员
     * 参数2:密码
     * 参数3:Realm名
     */
    return new SimpleAuthenticationInfo(admin,
        admin.getPassword(),
        "myRealm2");
   }


  // 自定义授权方法
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    return null;
   }
}

4.在SecurityManager中配置Realm

// Realm
@Bean
public MyRealm getMyRealm() {
  return new MyRealm();
}
@Bean
public MyRealm2 getMyRealm2() {
  return new MyRealm2();
}


// SecurityManager对象
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm realm, MyRealm2 realm2){
  DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
  // Realm放入SecurityManager中
  List<Realm> realms = new ArrayList();
  realms.add(realm);
  realms.add(realm2);
  defaultSecurityManager.setRealms(realms);
  return defaultSecurityManager;
}

多Realm认证策略

多Realm 认证策略是一种用于身份验证和授权的安全策略,它允许在一个系统或应用程序中使用多个身份验证领域(Realm)来验证用户的身份。认证策略主要使用的是 AuthenticationStrategy接口,这个接口有三个实现类:

策略意义
AtLeastOneSuccessfulStrategy(默认)只要有一个Realm验证成功即可,返回所有成功的认证信息
FirstSuccessfulStrategy只要有一个Realm验证成功即可,只返回第一个成功的认证信息,其他的忽略
AllSuccessfulStrategy所有Realm验证成功才算成功,如果有一个失败则认证失败
// Realm管理者
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
  ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
  //设置认证策略
  modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
  return modularRealmAuthenticator;
}


// SecurityManager对象
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm realm, MyRealm2 realm2){
  DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
  // 设置Realm管理者(需要在设置Realm之前)
  defaultSecurityManager.setAuthenticator(modularRealmAuthenticator());
  List<Realm> realms = new ArrayList();
  realms.add(realm);
  realms.add(realm2);
  defaultSecurityManager.setRealms(realms);
  return defaultSecurityManager;
}

异常处理

当Shiro认证失败后,会抛出AuthorizationException异常,该异常有很多子类,不同的子类意味着认证失败的原本不同,我们可以通过捕获异常确定认证失败的原因。

异常原因
DisabledAccountException账户失效
ConcurrentAccessException竞争次数过多
ExcessiveAttemptsException尝试次数过多
UnknownAccountException用户名不正确
IncorrectCredentialsException凭证(密码)不正确
ExpiredCredentialsException凭证过期
@RequestMapping("/user/login2")
public String login(String username, String password) {
  try {
    usersService.userLogin(username, password);
    return "main";
   } catch (DisabledAccountException e) {
    System.out.println("账户失效");
    return "fail";
   } catch (ConcurrentAccessException e) {
    System.out.println("竞争次数过多");
    return "fail";
   } catch (ExcessiveAttemptsException e) {
    System.out.println("尝试次数过多");
    return "fail";
   } catch (UnknownAccountException e) {
    System.out.println("用户名不正确");
    return "fail";
   } catch (IncorrectCredentialsException e) {
    System.out.println("密码不正确");
    return "fail";
   } catch (ExpiredCredentialsException e) {
    System.out.println("凭证过期");
    return "fail";
   }
}

使用散列算法加密认证

散列算法(Hash Algorithm)是一种用于数据加密、数据完整性验证和数字签名等领域的算法。它的主要目标是将任意长度的数据映射为固定长度的散列值,以便于数据的处理和比较。常见的散列算法包括 MD5、SHA-1、SHA-256 等。这些算法在计算机安全领域得到广泛应用,例如用于文件校验、密码存储、数字签名等。
例如,密码“zhangsan”会产生散列值“23ds2i1sdda97sad8u8asda0d”,但通过 md5 解密网站可以很容易地通过散列值获取到密码“zhangsan”。因此,在加密时我们可以添加一些只有系统知道的干扰数据,这些干扰数据被称为“盐”,并且可以进行多次加密,这样生成的散列值就更难被破解了。

1.修改数据库和实体类,添加盐字段,并修改数据库用户密码为加盐加密后的数据。

@Data
public class Users{
  private Integer uid;
  private String username;
  private String password;
  private String salt;
}

2.修改自定义Realm

@Component
public class MyRealm extends AuthorizingRealm {
  @Autowired
  private UserInfoMapper userInfoMapper;


  // 自定义认证方法
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 1.获取用户输入的用户名
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    String username = token.getUsername();


    // 2.根据用户名查询用户
    QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);
    Users users = usersMapper.selectOne(wrapper);


    // 3.将查询到的用户封装为认证信息
    if (users == null) {
      throw new UnknownAccountException("账户不存在");
     }


    /**
     * 参数1:用户
     * 参数2:密码
     * 参数3:盐
     * 参数4:Realm名
     */
    return new SimpleAuthenticationInfo(users,
        users.getPassword(),
        ByteSource.Util.bytes(users.getSalt()),
        "myRealm");
   }


  // 自定义授权方法
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    return null;
   }
}

3.在注册自定义Realm时添加加密算法

// 配置加密算法
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
  HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
  //加密算法
  hashedCredentialsMatcher.setHashAlgorithmName("md5");
  //加密的次数
  hashedCredentialsMatcher.setHashIterations(5);
  return hashedCredentialsMatcher;
}


// Realm
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
  MyRealm myRealm = new MyRealm();
   // 设置加密算法
  myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
  return myRealm;
}

过滤器

通过配置过滤器,我们可以规定哪些资源是需要认证后才能访问的,哪些资源是不需要认证便可以访问的。Shiro内置了很多过滤器:

过滤器说明
anon配置不需要登录即可访问的资源
authc配置登录认证后才可以访问的资源
user配置登录认证或“记住我”认证后才可以访问的资源
// 配置过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
  // 1.创建过滤器工厂
  ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
  // 2.过滤器工厂设置SecurityManager
  filterFactory.setSecurityManager(securityManager);
  // 3.设置shiro的拦截规则
  Map<String,String> filterMap=new HashMap<>();
  // 不拦截的资源
  filterMap.put("/login.html","anon");
  filterMap.put("/fail.html","anon");
  filterMap.put("/user/login","anon");
  filterMap.put("/css/**","anon");
  // 其余资源都需要用户认证
  filterMap.put("/**","authc");
  // 4.将拦截规则设置给过滤器工厂
  filterFactory.setFilterChainDefinitionMap(filterMap);
  // 5.登录页面
  filterFactory.setLoginUrl("/login.html");
  return filterFactory;
}

获取认证数据

通过subject.getPrincipal();我们可以获得认证后的用户数据

@RequestMapping("/user/getUsername")
@ResponseBody
public String getUsername(){
  Subject subject = SecurityUtils.getSubject();
  // 获取认证数据
  Users users = (Users)subject.getPrincipal();
  return users.getUsername();
}

Shiro会话

Shiro提供了完整的企业级会话管理功能,并且不依赖于Web容器,不管JavaSE还是JavaEE环境都可以使用。

// 使用Shiro提供的会话对象
@RequestMapping("/user/session")
@ResponseBody
public void session(){
  // 1.获取Subject
  Subject subject = SecurityUtils.getSubject();
  // 2.获取会话
  Session session = subject.getSession();


  // 会话id
  System.out.println("会话id:"+session.getId());
  // 会话的主机地址
  System.out.println("会话的主机地址:"+session.getHost());
  // 设置会话过期时间
  session.setTimeout(1000*10);
  // 获取会话过期时间
  System.out.println("会话过期时间:"+session.getTimeout());
  // 会话开始时间
  System.out.println("会话开始时间:"+session.getStartTimestamp());
  // 会话最后访问时间
  System.out.println("会话最后访问时间:"+session.getLastAccessTime());
  // 会话设置数据
  session.setAttribute("name","百战");
}


@RequestMapping("/user/getSession")
@ResponseBody
public void getSession(){
  Subject subject = SecurityUtils.getSubject();
  Session session = subject.getSession();
  System.out.println(session.getAttribute("name"));
}

会话管理器

会话管理器可以对会话对象进行配置和监听

1.创建会话监听器

@Component
public class MySessionListener implements SessionListener {
  //会话创建时触发
  @Override
  public void onStart(Session session) {
    System.out.println("会话创建:" + session.getId());
   }
  //会话过期时触发
  @Override
  public void onExpiration(Session session) {
    System.out.println("会话过期:" + session.getId());
   }
  //退出/会话过期时触发
  @Override
  public void onStop(Session session) {
    System.out.println("会话停止:" + session.getId());
   }
}

2.在会话管理器中配置会话监听器

// 会话管理器
@Bean
public SessionManager sessionManager(MySessionListener sessionListener) {
  // 创建会话管理器
  DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
  // 创建会话监听器集合
  List<SessionListener> listeners = new ArrayList();
  listeners.add(sessionListener);
  // 将监听器集合设置到会话管理器中
  sessionManager.setSessionListeners(listeners);


  // 全局会话超时时间(单位毫秒),默认30分钟,设置为5秒
  sessionManager.setGlobalSessionTimeout(5*1000);
  // 是否开启删除无效的session对象,默认为true
  sessionManager.setDeleteInvalidSessions(true);
  // 是否开启定时调度器进行检测过期session,默认为true
  sessionManager.setSessionValidationSchedulerEnabled(true);


  return sessionManager;
}

3.在SecurityManager中配置会话管理器

@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm,MyRealm2 myRealm2,SessionManager sessionManager){
  DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  // 自定义Realm放入SecurityManager中
  //     securityManager.setRealm(myRealm);
  // 设置Realm管理者(需要设置在Realm之前)
  securityManager.setAuthenticator(modularRealmAuthenticator());
  List<Realm> realms = new ArrayList();
  realms.add(myRealm);
  //     realms.add(myRealm2);
  securityManager.setRealms(realms);
  securityManager.setSessionManager(sessionManager);
  return securityManager;
}

退出登录

当退出登陆后,要删除会话对象和认真数据

1.编写退出登录控制器

@RequestMapping("/user/logout")
public String logout(){
  Subject subject = SecurityUtils.getSubject();
  // 退出登录
  subject.logout();
  // 退出后跳转到登录页
  return "redirect:/login";
}

2.在主页面添加退出登录按钮

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>主页面</title>
</head>
<body>
<h1>主页面</h1>
<h2><a href="/user/logout">退出登录</a></h2>
</body>
</html>

记住我

Shiro的记住我功能可以在用户登录成功后,关闭浏览器,下次再访问系统资源时,无需再执行登录操作。
实现Shiro的记住我功能,一般是通过将用户的一些基本信息(密码)存入浏览器的Cookie,下次登录时优先验证Cookie,后端做处理,以此来实现记住密码的功能。
需注意,若网站对安全性要求较高,一般不建议开启记住密码功能,因为Cookie是保存在本机电脑浏览器里,其他用户可能会使用此电脑拷走Cookie,导入其他电脑继续使用账号登录。

1.序列化所有实体类

@Data
public class Users implements Serializable {
  private Integer uid;
  private String username;
  private String password;
  private String salt;
}

2.配置Cookie生成器和记住我管理器

// Cookie生成器
@Bean
public SimpleCookie simpleCookie() {
  SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
  // Cookie有效时间,单位:秒
  simpleCookie.setMaxAge(20);
  return simpleCookie;
}


// 记住我管理器
@Bean
public CookieRememberMeManager cookieRememberMeManager(SimpleCookie simpleCookie) {
  CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
  // Cookie生成器
  cookieRememberMeManager.setCookie(simpleCookie);
  // Cookie加密的密钥
  cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA=="));
  return cookieRememberMeManager;
}


@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm,
                         MyRealm2 myRealm2,
                         SessionManager sessionManager,
                         CookieRememberMeManager rememberMeManager){
  DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  // 自定义Realm放入SecurityManager中
  //     securityManager.setRealm(myRealm);
  // 设置Realm管理者(需要设置在Realm之前)
  securityManager.setAuthenticator(modularRealmAuthenticator());
  List<Realm> realms = new ArrayList();
  realms.add(myRealm);
  //     realms.add(myRealm2);
  securityManager.setRealms(realms);
  securityManager.setSessionManager(sessionManager);
  securityManager.setRememberMeManager(rememberMeManager);
  return securityManager;
}

3.修改登录表单

<form class="form" action="/user/login" method="post">
  <input type="text" placeholder="用户名" name="username">
  <input type="password" placeholder="密码" name="password">
  <input type="checkbox" name="rememberMe" value="on">记住我<br>
  <button type="submit" id="login-button">登录</button>
</form>

4.修改登录控制器

@RequestMapping("/user/login")
public String login(String username, String password,String rememberMe) {
  try {
    usersService.userLogin(username, password,rememberMe);
    return "main";
   } catch (DisabledAccountException e) {
    System.out.println("账户失效");
    return "fail";
   } catch (ConcurrentAccessException e) {
    System.out.println("竞争次数过多");
    return "fail";
   } catch (ExcessiveAttemptsException e) {
    System.out.println("尝试次数过多");
    return "fail";
   } catch (UnknownAccountException e) {
    System.out.println("用户名不正确");
    return "fail";
   } catch (IncorrectCredentialsException e) {
    System.out.println("密码不正确");
    return "fail";
   } catch (ExpiredCredentialsException e) {
    System.out.println("凭证过期");
    return "fail";
   }
}

5.修改登录Service

@Service
public class UsersService {
  @Autowired
  private DefaultWebSecurityManager securityManager;


  public void userLogin(String username,String password,String rememberMe) throws AuthenticationException {
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token=new UsernamePasswordToken(username,password);
    if (rememberMe != null){
      // 如果用户选择记住我,则生成记住我Cookie
      token.setRememberMe(true);
     }
    subject.login(token);
   }
}

6.配置过滤器,配置可以通过“记住我”访问的资源。

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
  // 1.创建过滤器工厂
  ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
  // 2.过滤器工厂设置SecurityManager
  filterFactory.setSecurityManager(securityManager);
  // 3.设置shiro的拦截规则
  Map<String,String> filterMap=new HashMap<>();
  // 不拦截的资源
  filterMap.put("/login.html","anon");
  filterMap.put("/fail.html","anon");
  filterMap.put("/user/login","anon");
  filterMap.put("/static/**","anon");
  // 其余资源都需要认证,authc过滤器表示需要认证才能进行访问; user过滤器表示配置记住我或认证都可以访问
  //     filterMap.put("/**","authc");
  filterMap.put("/user/pay","authc");
  filterMap.put("/**", "user");
  // 4.将拦截规则设置给过滤器工厂
  filterFactory.setFilterChainDefinitionMap(filterMap);
  // 5.登录页面
  filterFactory.setLoginUrl("/login.html");
  return filterFactory;
}

7.编写支付控制器

// 支付
@RequestMapping("/user/pay")
@ResponseBody
public String pay(){
  return "支付功能";
}


总结

提示:这里对文章进行总结:

Shiro是一个简单且强大的框架,提供了全面的安全管理服务,如认证、授权、加密和会话管理等。总的来说,Shiro的认证功能可以通过realm来实现,并且可以通过自定义realm来满足不同的认证需求。在使用Shiro时,建议遵循最佳实践,并根据你的项目需求进行适当的配置和定制,以确保安全性和灵活性。

文章来源:https://blog.csdn.net/liubopro666/article/details/135439662
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。