开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 04 天,点击查看活动详情
Apache Shiro 是一款非常流行的开源安全框架,提供了认证、授权、会话管理和密码系统等功能。 安全问题历来是非常复杂,但却是系统开发者必须关注的问题之一。 Shiro 框架设计的主要目的之一就是帮助开发者解决系统中的安全问题。 今天,我将演示下如何把 Shiro 集成到 Spring Boot 应用中。
01-Shiro 中的基本概念
Shiro 主要特性可以分为四部分:
- 认证。简单理解就是提供登录功能,验证用户身份。
- 授权。简单理解就是控制用户可以访问系统中的哪些资源。
- 会话管理。管理用户会话。
- 密码系统。使用加密算法确保数据安全。
下图是 Apache Shiro 官网中给出的系统架构图。
我简单介绍下图中各个模块的主要作用:
- Subject,指认证系统中的用户,可以是人类、其他服务器。
- Security Manager,是 Shiro 框架的核心,负责认证、授权、会话管理、缓存等。
- Authenticator,负责处理“用户”的登录请求。
- Authorizer,负责确定“用户”的访问权限。
- Session Manager & Session DAO,负责“用户”会话管理及持久化。 例如,在 Single Sign-On(SSO、单点登录)中,可通过 DAO 对象将会话持久化到 Redis 或数据库中。
- Cache Manager,负责缓存。
- Realms,可以理解为数据访问对象(DAO),它能够访问用户的相关信息,例如密码、角色、权限信息等。它是 Shiro 与业务之前的“桥梁”。
- Cryptography,是 Shiro 中的密码系统,它封装了一系列的加密算法,并提供易用的 API。
注:Authenticator 和 Authorizer 都是通过一个或多个 Realms 来验证、获取用户相关的信息。
01.1-Shiro 中身份验证的步骤
在 Shiro 中,用户身份验证的主要步骤可以概括为:
- 收集可以识别用户(Subject)身份的属性(Principals)和加密数据(Credentials)。
- 将步骤1中收集的信息提交到认证系统(表现为各种 Realms)。
- 认证系统返回验证结果,例如通过、重试或拒绝访问等。
“用户”的身份属性由用户自己提供,例如他的登录名、密码。 用户的资格信息或者身份信息由 Realm 提供,即根据用户名或 ID 去数据库或其他第三方系统中查询。 Shiro 在收集完毕用户的登录信息和对应的资格信息后,会验证这些信息是否匹配。 然后,根据匹配结果,Shiro 确定是否拒绝访问或允许访问,或者是重定向用户到登录界面等操作。
下图是 Shiro 官网提供的一个简单认证流程。
接下来,我将在 Spring Boot 应用中实现这个流程。 实现步骤并不会严格按照图中标号的顺序进行,串联好后,可以运行一遍体会 Shiro 框架的工作原理。
02-Spring Boot 集成 Shiro 框架实现用户认证功能
首先,将 Shiro 引入到 Spring Boot 应用中。 通过 starter 工程,这一步骤是比较方便的。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
</dependency>
接下来,我们要定义一个 UserRealm,用来存储用户的密码等信息(图中的步骤5)。
@Component
public class SimpleUserRealm extends SimpleAccountRealm {
public SimpleUserRealm() {
super();
/**
* 定义三个用户,user1\user2\user3
*/
this.addAccount("user1", "password1");
this.addAccount("user2", "password2");
this.addAccount("user3", "password3");
}
}
我通过继承 org.apache.shiro.realm.SimpleAccountRealm 类来实现了一个业务类 SimpleUserRealm,里面有三个用户。
在执行用户认证时,Realm 的主要功能是向 Shiro 提供用户的信息。换句话说,它知道去哪里读取用户的信息,比如密码。
在 SimpleUserRealm 中,用户的密码就写死在 Realm 的初始化函数中。
通常来说,系统中的 Realms 可以是从数据库中查找用户信息,也可以是从 LDAP 等系统中查找用户。
@Bean
@ConditionalOnMissingBean
@Override
protected SessionsSecurityManager securityManager(List<Realm> realms) {
return super.securityManager(realms);
}
然后,需要初始化 Shiro 中的关键部分,即 SecurityManager(上图中的 2-4)。
@Configuration
public class AuthConfig {
@Bean
public SessionsSecurityManager securityManager(List<Realm> realms) { // 能够从容器中拿到之前定义的 SimpleUserRealm
DefaultSecurityManager securityManager = new DefaultSecurityManager();
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
subjectDAO.setSessionStorageEvaluator(new DefaultSessionStorageEvaluator());
securityManager.setSubjectDAO(subjectDAO);
securityManager.setSubjectFactory(new DefaultSubjectFactory());
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
securityManager.setAuthenticator(authenticator);
securityManager.setAuthorizer(new ModularRealmAuthorizer());
securityManager.setRealms(realms); // 设置到 securityManager 中
DefaultSessionManager sessionManager = new DefaultSessionManager();
sessionManager.setSessionDAO(new MemorySessionDAO());
sessionManager.setSessionFactory(new SimpleSessionFactory());
sessionManager.setDeleteInvalidSessions(true);
securityManager.setSessionManager(sessionManager);
securityManager.setEventBus(new DefaultEventBus());
return securityManager;
}
}
有了这些,接下来就可以将拼图完成,编写用户验证测试了。
@SpringBootApplication
public class AuthApplication implements CommandLineRunner {
@Autowired
private SimpleUserRealm userRealm;
@Autowired
private SessionsSecurityManager securityManager;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class);
}
@Override
public void run(String... args) throws Exception {
System.out.println(userRealm);
UsernamePasswordToken token = new UsernamePasswordToken("user1", "password12");
SecurityUtils.setSecurityManager(securityManager); // 必须设置使用的 securityManager
Subject subject = SecurityUtils.getSubject();
subject.login(token);
System.out.println(subject.isAuthenticated()); // 如果 token 中的密码是 password1 即我们在 realm 中设置的密码,此处打印 true
subject.logout(); // 登出
}
}
我创建了一个 Spring Boot 应用,并让其实现了 CommandLineRunner 接口。
这样,在容器启动后,会执行 run 方法。
在 run 方法中,我创建了一个 UsernamePasswordToken,它表示用户提交到 Shiro 系统中的用户信息,包括密码。
提交到 Shiro 中以后,SecurityManager 会去拿着 token 到所有的 Realms 中去查找对应用户的认证信息(subject.login(token);),如果验证不通过会抛异常出来。
验证通过后,subject.isAuthenticated() 的返回值为 true。
03-总结
以上就是Spring Boot 应用(非 Web 应用)如何使用 Shiro 框架进行安全认证的示例。 Web 应用于上述有点不太一样,我将在后面的文章中介绍。