学习目标
- 9.1 Shiro简介
- 9.1.1 Shiro特性
- 9.1.2 Shiro架构
- 9.2 认证(Authentication)
- 9.2.1 Token
- 9.2.2 快速上手
- 9.2.2.1 添加依赖
- 9.2.2.2 配置shiro.ini
- 9.2.2.3 创建 Realm
- 9.2.2.4 初始化 Shiro
- 9.2.2.5 认证测试
- 9.2.2.6 处理会话
- 9.2.3 认证流程
- 9.2.4 记住我 VS 认证
- 9.2.5 注销 Logout
- 9.3 SpringBoot+Shiro认证
(如果没有了解可以去我主页看看 第一至八章的内容来学习)Apache Shiro 是一个强大易用的Java安全框架,提供了认证、授权、会话管理和加密等功能。对于任何一个应用程序,Shiro都可以提供全面的安全管理服务,并且对于其他安全框架,Shiro要简单易用的多。本章主要给大家介绍Shiro的架构,讲解认证功能并和SpringBoot集成实现动态认证。
9.1 Shiro简介
Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、会话管理和加密等功能。对于任意一个应用程序,Shiro 都可以提供全面的安全管理服务,对比Spring Security,可能没有Spring Security功能强大,但是我们在实际工作中可能并不需要那么复杂的功能,所以使用简单易用的Shiro就已经足够了。本教程也只介绍基本的Shiro使用,不会过多分析源码等,重在使用。
9.1.1 Shiro特性
Apache Shiro 是一个强大的 Java 安全框架,提供了身份验证、授权、加密和会话管理等功能。Shiro 的特性包括:
- 身份验证(Authentication):验证用户身份。
- 授权(Authorization):确定用户是否有权限访问某些资源。
- 加密(Cryptography):保护数据的安全。
- 会话管理(Session Management):管理用户会话。
下面是一个简单的 Java 示例,展示了如何使用 Shiro 进行身份验证和授权。
1. 添加 Shiro 依赖
首先,在你的 Maven 项目中添加 Shiro 依赖。如果你使用的是 Gradle,请相应调整。
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.8.0</version>
</dependency>
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.8.0</version>
</dependency>
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.8.0</version>
</dependency>
2. . 配置 Shiro
创建一个 Shiro 配置类:
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroWebConfiguration;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class ShiroConfig extends ShiroWebConfiguration { @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置自定义的 Realm securityManager.setRealm(myRealm()); return securityManager; } @Bean public MyRealm myRealm() { return new MyRealm(); } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); chainDefinition.addPathDefinition("/login", "anon"); chainDefinition.addPathDefinition("/**", "authc"); return chainDefinition; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
}
3. 创建自定义 Realm
自定义 Realm 用于处理身份验证和授权逻辑:
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.subject.PrincipalCollection; public class MyRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); // 基于用户名获取权限和角色 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(Arrays.asList("user")); // 示例角色 authorizationInfo.setStringPermissions(Arrays.asList("read", "write")); // 示例权限 return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); // 模拟从数据库中获取用户信息 if ("john".equals(username)) { return new SimpleAuthenticationInfo(username, "password123", getName()); } else { throw new UnknownAccountException("No account found for user [" + username + "]"); } }
}
4. 使用 Shiro 进行身份验证和授权
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject; public class ShiroExample { public static void main(String[] args) { // 获取当前的 Subject Subject currentUser = SecurityUtils.getSubject(); // 创建令牌 UsernamePasswordToken token = new UsernamePasswordToken("john", "password123"); try { // 登录 currentUser.login(token); System.out.println("User logged in successfully."); // 检查是否有某个角色 boolean hasRole = currentUser.hasRole("user"); System.out.println("User has role 'user': " + hasRole); // 检查是否有某个权限 boolean isPermitted = currentUser.isPermitted("read"); System.out.println("User is permitted to 'read': " + isPermitted); } catch (AuthenticationException e) { e.printStackTrace(); } finally { // 退出 currentUser.logout(); System.out.println("User logged out."); } }
}
5. 配置 Spring Boot 应用
确保你的 Spring Boot 应用能够扫描到 Shiro 配置类。例如,在 Application 类上添加 @ComponentScan 注解,或者在主类所在的包中创建 Shiro 配置类。
6. 运行应用
运行你的 Spring Boot 应用,访问受保护的资源时,Shiro 将拦截请求并进行身份验证和授权。
以上是一个简单的 Shiro 使用示例,展示了基本的身份验证和授权功能。你可以根据实际需求进行更复杂的配置和扩展。
9.1.2 Shiro架构
Shiro是Apache旗下一个开源的安全框架,它将软件系统的安全认证相关功能抽取出来,实现了用户身份认证、权限授权、加密、会话管理等功能,为Java应用程序提供了强大的安全保障。以下是Shiro架构的详细介绍:
一、Shiro架构的主要组件
- Subject:
- 主体对象,负责提交用户认证和授权信息。
- 在Shiro中,Subject是一个接口,代表了当前操作用户,它可以是用户,也可以是程序。外部应用与Subject进行交互,Subject记录了当前操作用户的信息。
- SecurityManager:
- 安全管理器,Shiro的核心组件。
- 负责对所有的Subject进行安全管理,包括认证、授权等业务。
- 通过SecurityManager,可以完成Subject的认证、授权等操作。
- Realms:
- 领域对象,负责从数据层获取业务数据。
- 是Shiro和应用程序安全数据之间的桥梁。
- 当Shiro需要访问用户的安全数据时(如用户认证和授权信息),会通过系统配置的各种Realm从数据源读取数据。
二、Shiro架构的详细功能组件
- 认证管理对象(Authenticator):
- 负责执行认证操作。
- 认证器,对用户身份进行认证。Authenticator是一个接口,Shiro提供ModularRealmAuthenticator实现类,可以满足大多数需求,也可以自定义认证器。
- 授权管理对象(Authorizer):
- 负责授权检测。
- 用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
- 会话管理对象(SessionManager):
- 负责创建并管理用户Session生命周期,提供一个强有力的Session体验。
- Shiro框架定义了一套会话管理,它不依赖Web容器的session,所以Shiro可以使用在非Web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
- 会话数据访问对象(SessionDAO):
- 代表SessionManager执行Session持久(CRUD)动作。
- 它允许任何存储的数据挂接到session管理基础上。
- 缓存管理对象(CacheManager):
- 提供创建缓存实例和管理缓存生命周期的功能。
- 主要对session和授权数据进行缓存,比如将授权数据通过CacheManager进行缓存管理。
- 加密管理对象(Cryptography):
- 提供了加密方式的设计及管理。
- Shiro提供了一套加密/解密的组件,方便开发,比如提供常用的散列、加/解密等功能。
三、Shiro架构的集成与配置
Shiro可以与各种应用程序框架和技术进行无缝集成,包括Spring、Servlet容器、Web框架等。它提供了与这些框架的集成支持,使开发人员能够轻松地将Shiro与现有的应用程序进行集成。
在集成开发时,需要使用Shiro提供的相应API和插件来集成Shiro到应用程序中。可以根据不同的框架和数据源选择不同的Shiro插件。例如,对于Spring应用程序,可以使用Shiro-Spring插件;对于数据源,可以使用Shiro-JDBC插件或Shiro-LDAP插件等。
总的来说,Shiro架构以其简单易用、综合的安全功能、灵活性和易于集成等特征,为Java应用程序提供了强大的安全保障。
以下是一个简单的Shiro配置示例,它不使用Spring Security,而是直接配置Shiro的组件:
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.util.HashMap;
import java.util.Map; @Configuration
public class ShiroConfig { // 配置Realm(这里需要根据你的实际实现来配置) @Bean public Realm realm() { // 返回你的Realm实现,比如MyCustomRealm return new MyCustomRealm(); } // 配置SecurityManager @Bean public SecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); // 可以根据需要配置CacheManager、SessionManager等 // securityManager.setCacheManager(...); // securityManager.setSessionManager(...); return securityManager; } // 配置Shiro的过滤器链 @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); Map<String, String> filterChainDefinitionMap = new HashMap<>(); // 配置URL模式与对应的过滤器,比如: filterChainDefinitionMap.put("/login", "anon"); // 无需认证即可访问 filterChainDefinitionMap.put("/**", "authc"); // 其他所有URL都需要认证 chainDefinition.setFilterChainDefinitionMap(filterChainDefinitionMap); return chainDefinition; } // 配置Shiro的过滤器工厂Bean @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, ShiroFilterChainDefinition shiroFilterChainDefinition) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 配置登录页面的URL shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainDefinitionMap()); // 可以根据需要配置其他属性 return shiroFilterFactoryBean; } // 配置Spring AOP的权限注解支持 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
}
在这个示例中,你需要实现自己的Realm类(MyCustomRealm),它负责从数据源(如数据库)中加载用户的认证和授权信息。
请注意:这个配置示例使用了Spring的Java配置方式(@Configuration和@Bean注解)。如果你不使用Spring,你需要以不同的方式配置Shiro,比如通过ini配置文件或者编程方式直接创建Shiro的组件。
最后,确保你的Spring Boot应用程序扫描到了这个配置类,并且你已经添加了Shiro的依赖到你的项目中。这样,Shiro就会被正确地配置并集成到你的Java应用程序中。
9.2 认证(Authentication)
在Apache Shiro中,认证(Authentication)过程通常涉及以下几个步骤:
- 获取当前Subject:Subject是当前操作的用户,它封装了用户的安全操作,如登录、注销、检查角色和权限等。
- 构建认证令牌(AuthenticationToken):这个令牌包含了用户提交的认证信息,如用户名和密码。
- 调用SecurityManager进行认证:SecurityManager是Shiro的核心,它管理着所有的安全操作。你需要将认证令牌传递给SecurityManager,后者会将其传递给配置的Realm进行实际的认证处理。
- 处理认证结果:SecurityManager会返回一个结果,表示认证是否成功,以及可能的认证信息(如用户身份和权限)。
以下是一个简单的Java代码示例,展示了如何使用Shiro进行认证:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource; // 假设你已经有一个配置好的SecurityManager和Realm
public class ShiroAuthenticationExample { public static void main(String[] args) { // 获取SecurityManager(这里应该是通过Spring等IoC容器注入的,但为了示例简洁,我们直接创建) SecurityManager securityManager = createSecurityManager(); // 将SecurityManager绑定到当前线程(或者你可以使用SecurityUtils.setSecurityManager(securityManager)) SecurityUtils.setSecurityManager(securityManager); // 获取当前Subject Subject currentUser = SecurityUtils.getSubject(); // 创建认证令牌(这里使用用户名和密码) AuthenticationToken token = new UsernamePasswordToken("username", "password"); try { // 执行登录操作 currentUser.login(token); // 登录成功后的操作 System.out.println("User has logged in successfully."); // 检查用户是否已认证 if (currentUser.isAuthenticated()) { System.out.println("User is authenticated."); // 获取用户的身份信息(这取决于你的Realm实现) Object principal = currentUser.getPrincipal(); System.out.println("User principal: " + principal); } } catch (AuthenticationException ae) { // 处理认证失败的异常 System.out.println("Authentication failed: " + ae.getMessage()); } } private static SecurityManager createSecurityManager() { // 创建自定义的Realm(这里应该是你的实现,为了示例我们省略了具体实现) AuthorizingRealm realm = new AuthorizingRealm() { // 实现doGetAuthenticationInfo和doGetAuthorizationInfo方法 // ... }; // 配置Realm的凭证匹配器(例如,使用SHA-256散列算法) HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(org.apache.shiro.crypto.hash.Sha256Hash.ALGORITHM_NAME); matcher.setHashIterations(1024); // 设置散列迭代次数 realm.setCredentialsMatcher(matcher); // 创建DefaultWebSecurityManager并设置Realm DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm); return securityManager; }
} // 注意:上面的createSecurityManager方法中的Realm实现是省略的,你需要根据你的应用需求来实现它。
// 通常,你会从数据库中获取用户的认证和授权信息,并在Realm中实现doGetAuthenticationInfo和doGetAuthorizationInfo方法。
在实际应用中,SecurityManager和Realm通常是通过Spring等IoC容器来配置的,而不是在代码中直接创建。此外,认证过程通常发生在Web层,例如在一个登录Servlet或Spring MVC控制器中。
上面的代码示例仅用于说明Shiro认证过程的基本原理。在实际项目中,你需要根据你的具体需求来配置Shiro,并实现相应的Realm类来与你的数据源(如数据库)进行交互。
9.2.1 Token
在Apache Shiro中,AuthenticationToken 是一个接口,它封装了提交给 Shiro 进行认证的用户身份信息。最常见的实现是 UsernamePasswordToken,它包含了用户名和密码。
以下是一个简单的 Java 代码示例,展示了如何创建和使用一个 UsernamePasswordToken:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject; // 假设 SecurityManager 已经被正确配置和初始化
public class ShiroTokenExample { public static void main(String[] args) { // 获取当前已经绑定的 SecurityManager SecurityManager securityManager = SecurityUtils.getSecurityManager(); // 获取当前操作的用户 Subject Subject currentUser = SecurityUtils.getSubject(); // 创建认证令牌,包含用户名和密码 AuthenticationToken token = new UsernamePasswordToken("john.doe", "secretPassword"); try { // 执行登录操作,这实际上会触发 SecurityManager 使用配置的 Realm 进行认证 currentUser.login(token); // 登录成功后的逻辑 System.out.println("User 'john.doe' has logged in successfully."); // 检查用户是否已认证 if (currentUser.isAuthenticated()) { System.out.println("User is authenticated."); // 获取用户的身份信息(通常是用户名) Object principal = currentUser.getPrincipal(); System.out.println("Authenticated user principal: " + principal); } } catch (AuthenticationException ae) { // 处理认证失败的异常 System.err.println("Authentication failed for user 'john.doe': " + ae.getMessage()); } }
}
在这个例子中,我们首先获取了当前已经绑定到 Shiro 环境的 SecurityManager 和 Subject。然后,我们创建了一个 UsernamePasswordToken 实例,包含了要认证的用户名和密码。接下来,我们调用 Subject 的 login 方法,并传入这个令牌。如果认证成功,我们可以继续执行一些后续操作,比如访问受保护的资源。如果认证失败,AuthenticationException 会被抛出,我们可以捕获这个异常并处理它。
请注意:这个示例假设 SecurityManager 已经被正确配置和初始化,并且有一个或多个 Realm 被配置来处理认证请求。在实际应用中,这通常是通过配置文件(如 shiro.ini)或 Spring 等 IoC 容器来完成的。
此外,出于安全考虑,密码在传输和存储时应该被加密或散列处理。Shiro 提供了多种散列算法和凭证匹配器来帮助你实现这一点。在你的 Realm 实现中,你应该使用 HashedCredentialsMatcher 或类似的机制来验证用户提交的密码是否与存储在数据库中的散列值匹配。
9.2.2 快速上手
Apache Shiro 是一个强大的 Java 安全框架,它提供了认证、授权、加密和会话管理等功能。要快速上手 Shiro 并编写 Java 代码,你需要完成以下几个步骤:
9.2.2.1 添加依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>你的Shiro版本</version>
</dependency>
<!-- 如果需要Web支持,还需要添加shiro-web依赖 -->
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>你的Shiro版本</version>
</dependency>
确保将 你的Shiro版本 替换为你想要使用的 Shiro 版本号。
9.2.2.2 配置shiro.ini
通常,Shiro 的配置是通过 shiro.ini 文件或 Spring 配置文件来完成的。这里以 shiro.ini 为例:
[main]
# 配置自定义Realm
myRealm = com.example.MyRealm
securityManager.realm = $myRealm # 配置缓存管理器、会话管理器等(可选) [users]
# 配置一些静态用户(通常用于测试)
admin = password, adminRole
guest = guestPassword, guestRole [roles]
# 配置角色和权限(通常用于测试)
adminRole = *
guestRole = somePermission
注意:在实际应用中,你通常会从数据库中加载用户和角色信息,而不是在 shiro.ini 文件中硬编码它们。
9.2.2.3 创建 Realm
Realm 是 Shiro 与安全数据(如用户、角色和权限)之间的桥梁。你需要创建一个自定义的 Realm 类,并实现 AuthorizingRealm 接口(或继承 AuthorizingRealm 的子类):
public class MyRealm extends AuthorizingRealm { // 实现doGetAuthorizationInfo和doGetAuthenticationInfo方法
}
在 doGetAuthorizationInfo 方法中,你需要根据用户名加载角色和权限。在 doGetAuthenticationInfo 方法中,你需要根据用户名加载密码(通常是经过散列的)。
9.2.2.4 初始化 Shiro
在你的 Java 代码中,你需要初始化 Shiro 的 SecurityManager 并将其绑定到当前的 Thread 上:
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
9.2.2.5 认证测试
现在,你可以使用 Shiro 的 Subject API 来进行认证和授权了:
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin", "password");
try { currentUser.login(token); // 认证成功后的逻辑
} catch (AuthenticationException e) { // 处理认证失败的逻辑
} if (currentUser.hasRole("adminRole")) { // 执行需要adminRole权限的操作
} if (currentUser.isPermitted("somePermission")) { // 执行需要somePermission权限的操作
}
9.2.2.6 处理会话
Shiro 还提供了会话管理功能。你可以通过 Subject 的 getSession() 方法来获取当前会话,并进行相应的操作:
Session session = currentUser.getSession();
session.setAttribute("someKey", "someValue");
Object value = session.getAttribute("someKey");
session.removeAttribute("someKey");
集成到 Web 应用中
如果你的应用是一个 Web 应用,你还需要配置 Shiro 的过滤器链。这通常是通过在 web.xml 中添加 Shiro 的 DelegatingFilterProxy 或使用 Spring Boot 的自动配置来完成的。
请注意:上述步骤提供了一个快速上手的概述,但并未涵盖所有细节。在实际开发中,你可能需要根据你的具体需求进行更多的配置和自定义。此外,Shiro 的文档和示例代码是学习和理解 Shiro 的绝佳资源。
9.2.3 认证流程
Apache Shiro 的认证流程涉及几个关键步骤,包括创建 SecurityManager、配置 Realm、执行认证操作以及处理认证结果。以下是一个简化的 Java 代码示例,展示了 Shiro 的认证流程:
添加 Shiro 依赖(已在之前步骤中提及,此处省略)。
配置 Shiro(通常通过 shiro.ini 文件或编程方式配置,这里使用编程方式)。
创建 Realm(实现自定义的 Realm 类,用于从数据源中加载用户信息和验证密码)。
执行认证(使用 Subject 对象进行认证)。
以下是一个简化的代码示例:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.config.IniSecurityManagerFactory; // 自定义Realm(示例中未实现完整逻辑,仅作为占位符)
public class MyCustomRealm extends AuthorizingRealm { // 实现doGetAuthorizationInfo和doGetAuthenticationInfo方法(此处省略)
} public class ShiroAuthDemo { public static void main(String[] args) { // 1. 创建SecurityManager工厂并指定配置文件(这里使用编程方式配置,而非shiro.ini) IniSecurityManagerFactory factory = new IniSecurityManagerFactory(); // 如果使用shiro.ini文件,可以这样做:factory.setIniConfigPath("classpath:shiro.ini"); // 但这里我们直接通过编程方式配置一个简单的Realm factory.setRealm(new IniRealm("classpath:shiro-users.ini")); // 假设shiro-users.ini包含了用户和角色信息 // 2. 创建SecurityManager实例并绑定到SecurityUtils SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 3. 获取当前执行的Subject Subject currentUser = SecurityUtils.getSubject(); // 4. 创建Token(这里使用UsernamePasswordToken) UsernamePasswordToken token = new UsernamePasswordToken("johnDoe", "secret"); try { // 5. 执行登录操作 currentUser.login(token); System.out.println("User logged in successfully."); // 6. 执行需要认证的操作(例如访问受保护的资源) if (currentUser.isAuthenticated()) { System.out.println("User is authenticated."); // ... 执行其他操作 } } catch (AuthenticationException ae) { // 7. 处理认证异常 System.out.println("Authentication failed: " + ae.getMessage()); } // 注意:在实际应用中,应该在适当的时机调用logout()方法注销用户 // currentUser.logout(); }
} // 注意:上面的代码示例中,我们使用了IniRealm和一个假设的shiro-users.ini文件来简化配置。
// 在实际应用中,你应该实现自己的Realm类,并在factory.setRealm()方法中设置你的自定义Realm实例。
// 此外,shiro-users.ini文件应该被替换为实际的用户数据源配置。
重要说明:
- 上面的代码示例中,我们使用了 IniRealm 和一个假设的 shiro-users.ini 文件来简化配置。在实际应用中,你应该实现自己的 Realm 类,并在 factory.setRealm() 方法中设置你的自定义 Realm 实例。
- shiro-users.ini 文件应该包含用户和角色的静态信息,但在生产环境中,这些信息通常是从数据库或其他持久化存储中加载的。
- doGetAuthorizationInfo 和 doGetAuthenticationInfo 方法需要在你的自定义 Realm 类中实现,以提供认证和授权所需的逻辑。
- 在执行完认证操作后,你应该检查 Subject 的认证状态(currentUser.isAuthenticated()),并根据需要执行受保护的操作。
- 最后,别忘了在适当的时机调用 logout() 方法来注销用户。
9.2.4 记住我 VS 认证
在Apache Shiro中,“记住我”(Remember Me)功能和认证(Authentication)是两个不同的概念,但它们都是安全框架中的关键部分。下面我将分别解释这两个功能,并提供相应的Java代码示例。
认证(Authentication)
认证是验证用户身份的过程,通常涉及用户名和密码的验证。在Shiro中,认证通常通过Subject对象进行,该对象代表当前操作的用户。
认证流程:
- 创建一个SecurityManager实例并配置它。
- 创建一个包含用户凭据的AuthenticationToken(例如UsernamePasswordToken)。
- 调用Subject的login方法尝试进行认证。
- 处理认证结果(成功或失败)。
代码示例:
// ...(省略了SecurityManager的配置代码) // 获取当前Subject
Subject currentUser = SecurityUtils.getSubject(); // 创建Token
UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); try { // 执行登录操作 currentUser.login(token); System.out.println("User authenticated successfully.");
} catch (AuthenticationException e) { // 处理认证失败的情况 System.out.println("Authentication failed: " + e.getMessage());
}
记住我(Remember Me)
"记住我"功能允许用户在关闭浏览器后再次访问网站时无需重新登录。Shiro通过创建一个持久的cookie来实现这一功能,该cookie包含用户的唯一标识符(通常不是密码)。
记住我流程:
- 在登录成功时,如果用户选择了"记住我"选项,则创建一个记住我的cookie。
- 当用户再次访问网站时,Shiro会检查是否存在有效的记住我cookie。
- 如果存在有效的cookie,Shiro会自动为用户创建一个新的Subject实例,并将其视为已认证(但可能没有完整的权限信息,这取决于你的Realm实现)。
- 用户可以通过执行某些操作(如访问受保护的资源)来触发完整的认证过程(如果需要的话)。
代码示例(在登录成功时设置记住我cookie):
// ...(省略了部分代码,包括SecurityManager的配置和登录过程) // 假设登录成功
if (currentUser.isAuthenticated()) { // 创建RememberMeAuthenticationToken(这里是一个简化的例子,实际使用时可能需要更复杂的逻辑) RememberMeAuthenticationToken rememberMeToken = new RememberMeAuthenticationToken("rememberMeCookieValue"); // 注意:在实际情况中,你不会直接创建RememberMeAuthenticationToken的实例。 // 相反,Shiro会在用户选择"记住我"选项并成功登录后自动处理这个过程。 // 下面的代码只是为了说明如何在登录成功后设置记住我功能的概念。 // 在实际应用中,你应该在用户选择记住我后,调用Subject的某个方法来设置cookie。 // 例如,使用Shiro的Web支持时,可以通过Shiro的Filter链来自动处理记住我cookie的创建和验证。 // 这里我们假设有一个方法可以设置记住我cookie(这个方法需要你自己实现或找到合适的方式) // setRememberMeCookie(currentUser, rememberMeToken); // 这是一个假设的方法,不是Shiro API的一部分 System.out.println("User will be remembered.");
} // 注意:上面的代码示例并不是Shiro中设置记住我功能的正确方式。
// Shiro的Web支持模块提供了处理记住我cookie的内置机制。
// 你应该配置Shiro的Web过滤器(如FormAuthenticationFilter和RememberMeFilter)来自动处理记住我功能。
重要说明:
- 上面的"记住我"代码示例并不是Shiro中设置记住我功能的实际方法。它只是为了说明概念。
- 在Shiro中,记住我功能通常是通过配置Web过滤器来自动处理的。你需要确保你的Web应用程序已经正确配置了Shiro的Web支持,并且已经包含了处理记住我功能的过滤器。
- 记住我cookie的值通常是由Shiro在登录成功时自动生成的,并且会存储在用户的浏览器中。当用户再次访问网站时,Shiro会检查这个cookie,并根据需要为用户创建一个新的Subject实例。
9.2.5 注销 Logout
在Apache Shiro中,注销(Logout)操作通常涉及销毁当前用户的会话(Session)以及清除与用户相关的任何认证和授权信息。这通常通过Subject对象来完成,该对象代表当前操作的用户。
以下是一个简单的Shiro注销操作的Java代码示例:
// 获取当前Subject
Subject currentUser = SecurityUtils.getSubject(); // 检查用户是否已认证
if (currentUser.isAuthenticated()) { // 注销用户,这会销毁会话并清除认证信息 currentUser.logout(); // 可选:重定向到登录页面或显示注销成功的消息 // 注意:这里的重定向代码取决于你的Web框架(如Spring MVC, Servlet等) // 例如,在Servlet环境中,你可能需要使用HttpServletResponse来重定向 // response.sendRedirect("/login.jsp"); // 这是一个假设的登录页面URL System.out.println("User has been logged out successfully.");
} else { System.out.println("User is not authenticated, no need to logout.");
}
在上面的代码中,我们首先检查用户是否已经认证(即是否已经登录)。如果用户已认证,我们调用Subject的logout方法来注销用户。这会销毁用户的会话,并清除与该会话关联的所有认证和授权信息。
请注意:上面的代码示例是在服务器端执行的。在Web应用程序中,注销操作通常是通过一个Servlet、Controller或类似的组件来触发的,该组件会处理用户的注销请求,并执行上面的代码。
此外,Shiro还提供了Web支持,可以自动处理注销请求。例如,你可以配置一个Shiro的LogoutFilter,它会在接收到特定的注销请求时自动调用Subject的logout方法。你还需要在Web应用程序中配置一个注销URL,当用户访问该URL时,LogoutFilter会拦截请求并执行注销操作。
要配置Shiro的LogoutFilter,你通常需要在Shiro的配置文件中(如shiro.ini或Spring配置文件)定义它,并指定注销URL。例如:
[urls]
/logout = logout
...(其他URL到Filter的映射)
在这个例子中,当用户访问/logoutURL时,Shiro的LogoutFilter会拦截请求并执行注销操作。请注意,这里的/logout是一个假设的URL,你可以根据自己的需要来配置它。
最后,请确保你的Web应用程序已经正确配置了Shiro的Web支持,并且已经包含了处理注销功能的过滤器。如果你使用的是Spring框架,你可能还需要在Spring配置中声明Shiro的Filter链和LogoutFilter。
9.3 SpringBoot+Shiro认证
在Spring Boot项目中集成Apache Shiro进行认证,通常需要按照以下步骤进行:
- 添加依赖:首先,你需要在pom.xml文件中添加Shiro和Spring Boot Starter的依赖。
- 配置Shiro:创建一个Shiro配置类,用于配置SecurityManager、Realm和其他必要的Shiro组件。
- 实现Realm:Realm是Shiro与你的安全数据源(如数据库)之间的桥梁。你需要实现一个Realm类,用于认证和授权。
- 创建登录接口:编写一个控制器来处理登录请求,并在登录成功时创建和返回Shiro的认证Token。
- 配置安全过滤器:使用Shiro的过滤器来拦截和处理请求,如登录、注销和访问控制。
以下是一个简化的Spring Boot + Shiro认证的Java代码示例:
- 添加依赖(pom.xml):
<dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Shiro Spring Boot Starter --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>你的Shiro版本</version> </dependency> <!-- 其他依赖 -->
</dependencies>
- 配置Shiro(ShiroConfig.java):
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class ShiroConfig { @Bean public SecurityManager securityManager(CustomRealm customRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 登录页面URL shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 未授权页面URL // 定义过滤器链 ShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); chainDefinition.addPathDefinition("/login", "anon"); // 允许匿名访问登录页面 chainDefinition.addPathDefinition("/**", "authc"); // 其他所有路径都需要认证 shiroFilterFactoryBean.setFilterChainDefinitionMap(chainDefinition.getChainMap()); return shiroFilterFactoryBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
}
- 实现Realm(CustomRealm.java):
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.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired; public class CustomRealm extends AuthorizingRealm { // 假设你有一个UserService来处理用户相关的操作 @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 从principals中获取用户名(或其他唯一标识) String username = (String) principals.getPrimaryPrincipal(); // 查询用户的权限信息 Set<String> permissions = userService.findPermissionsByUsername(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); // 查询用户信息 User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户名不存在"); } // 创建AuthenticationInfo对象并返回 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( username, // 用户名(principal) user.getPassword(), // 密码 getName() // realm名称 ); return authenticationInfo; }
}
- 创建登录接口(AuthController.java):
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*; @RestController
@RequestMapping("/auth")
public class AuthController { @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return "登录成功"; } catch (AuthenticationException e) { return "登录失败: " + e.getMessage(); } } @GetMapping("/logout") public String logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "注销成功"; }
}
- 配置安全过滤器(已在ShiroConfig中完成)。
请注意:上面的代码示例是一个简化的版本,用于说明如何在Spring Boot中集成Shiro进行认证。在实际项目中,你可能需要添加更多的错误处理、日志记录、权限验证等功能,并根据你的业务需求进行定制。
此外,确保你的UserService和其他相关服务已经正确实现,并且数据库连接等配置已经正确设置。