Shiro基本使用
1、什么是Shiro
Shiro是Apache的java安全框架。Apache Shiro是一个功能强大、灵活的开源安全框架,可以清晰地处理身份验证、授权、企业会话管理和加密。
2、基础功能
以下是shiro的产品特性:
- Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份
- Authorization:授权,验证已认证的用户有哪些角色和资源访问权限
- Session Management:会话管理,管理特定于用户的会话,即使是非web或JavaSE 环境
- Cryptography:密码/加密,使用密码算法保持数据安全,同时仍易于使用
以上四个部分组成shiro主要的安全保障。还有其他的支持辅组主要功能:
Web Support:Web 支持,可以非常容易的集成到 Web 环境
Caching:缓存,用户登陆信息、拥有的角色信息、权限,减少查询次数,提高效率。
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限
自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3、体系结构(工作流程)
- Subject:主体,即当前和应用程序交互的对象。
- SecurityManager:安全管理器,所有的Subject都交给委托给它。
- Realms:域,是Shiro和应用数据的连接点,提供认证用户和授权的API,由开发人员维护。
4、源码分析
源码地址:https://github.com/apache/shiro
4.1、Authentication认证流程
参考源码提供的quickstart创建maven工程。
pom.xml引入依赖
<dependencies>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.0</version>
</dependency>
<!-- 日志logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
先看一下配置类,首先是简单的日志打印级别和输出格式的设置
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
然后是主要的shiro.ini配置
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
看到有[user]和[roles]两个配置。[user]配置的是用户的账号对应的密码和角色,如root用户的密码是secret,角色是admin。而[roles]则对应的是角色的权限,admin拥有所有权限。
接下来新建一个TestShiro.java类
public class TestShiro {
// 日志打印
private static final Logger log = LoggerFactory.getLogger(TestShiro.class);
public static void main(String[] args) {
// 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2、获取securityManager实例,设置全局securityManager
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
// 4、进行登陆认证
UsernamePasswordToken token = new UsernamePasswordToken("username", "123456");
try {
subject.login(token);
log.info("认证成功!");
} catch (AuthenticationException e) {
//5、异常处理
log.error(e.getMessage());
}
// 用户已经登录
if (subject.isAuthenticated()) {
// 6、退出
subject.logout();
}
}
}
用户认证的步骤:
- 读取我们
resources
下面的shiro.ini
创建SecurityManager
工厂 - 将获取的
SecurityManager
并绑定到SecurityUtils
- 通过
SecurityUtils
得到Subject
, 然后获取身份验证的Token
,如用户名 / 密码; - 调用
subject.login
方法进行登录,其会自动委托给SecurityManager.login
方法进行登录; - 身份验证失败请捕获
AuthenticationException
或其子类,常见的如:UnknownAccountException
(账号错误)、IncorrectCredentialsException
(凭证(密码)错误)
流程图:
subject.login
最终执行到 SimpleAccountRealm类的doGetAuthenticationInfo(AuthenticationToken token)方法
public class SimpleAccountRealm extends AuthorizingRealm {
}
而SimpleAccountRealm
类继承了AuthorizingRealm
类,AuthorizingRealm
继承了AuthenticatingRealm
类。这个类里面就有抽象方法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
执行一下程序,抛出异常,因为我们这边的用户名和密码不符合shiro.ini
里的配置。我们再尝试
UsernamePasswordToken token = new UsernamePasswordToken("root", "secret");
执行程序:
4.2、Authorization授权流程
登陆认证通过的用户有相应的角色,赋予对应的资源访问权限。
UsernamePasswordToken token = new UsernamePasswordToken("root", "secret");
try {
subject.login(token);
log.info("认证成功!");
} catch (AuthenticationException e) {
log.error(e.getMessage());
}
boolean flag = subject.hasRole("schwartz");
System.out.println(flag);
subject.hasRole()
:判断认证用户是否有某个权限subject.isPermitted()
:判断用户是否有某个权限
执行程序
最终还是进入到了SimpleAccountRealm
这个类里面的doGetAuthorizationInfo()
方法,这个方法继承自AuthorizingRealm
类
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection var1);
5、总结
创建subject托管到SecurityManager最终在realms里面进行数据安全的检验和授权。doGetAuthenticationInfo(AuthenticationToken token)
和doGetAuthorizationInfo(PrincipalCollection var1)
两个方法中进行逻辑处理。shiro的例子是在resoueces里配置.ini文件,实际的项目里一般根据RBAC模型设计用户、角色、资源和权限数据库。
参考: