✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。
个人主页:Java Fans的博客
个人信条:不迁怒,不贰过。小知识,大智慧。
当前专栏:SpringBoot 框架从入门到精通
✨特色专栏:国学周更-心性养成之路
本文内容:一文总结 Shiro 实战教程
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制
,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证
和授权
两部分,简称认证授权
。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
身份认证
,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
授权,即访问控制
,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Shiro 是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
Subject即主体
,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授权相关的方法,外部程序通过subject进行认证授权,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager即安全管理器
,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator即认证器
,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
Authorizer即授权器
,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
Realm即领域
,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
sessionManager即会话管理
,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
SessionDAO即会话dao
,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
CacheManager即缓存管理
,将用户权限数据存储在缓存,这样可以提高性能。
Cryptography即密码管理
,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
是主体(subject)进行身份认证的标识,标识必须具有唯一性
,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
是只有主体自己知道的安全信息,如密码、证书等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEb7L6Z6-1680591385485)(Shiro 实战教程.assets/image-20200521204452288.png)]
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.5.3version>
dependency>
[users]
mosin=1234
tom=1234
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zU9GESA-1680591385485)(Shiro 实战教程.assets/image-20220528172213825.png)]
/**
* @author: mosin
* @version: v1.0
*/
public class ShiroTest {
public static void main(String[] args) {
//创建默认的安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//创建安全管理器需要的realm对象
IniRealm iniRealm = new IniRealm("classpath:realm.ini");
//安全管理器设置realm对象
defaultSecurityManager.setRealm(iniRealm);
//将安全管理器注入安全工具类 用于获取认证的主体
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取认证的主体
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosin", "1234");
try {
//认证 通过没有任何的异常
subject.login(usernamePasswordToken);
//验证是否通过
boolean authenticated = subject.isAuthenticated();
System.out.println("认证通过:"+authenticated);
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!");
}
}
}
DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等
上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。
SimpleAccountRealm的部分源码中有两个方法一个是 认证 一个是 授权
,
public class SimpleAccountRealm extends AuthorizingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = getUsername(principals);
USERS_LOCK.readLock().lock();
try {
return this.users.get(username);
} finally {
USERS_LOCK.readLock().unlock();
}
}
}
/**
* 自定义realm
*/
public class CustomerRealm extends AuthorizingRealm {
//认证方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//授权方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if("mosin".equals(principal)){
return new SimpleAuthenticationInfo(principal,"123",this.getName());
}
return null;
}
}
public class TestAuthenticatorCustomerRealm {
public static void main(String[] args) {
//创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//IniRealm realm = new IniRealm("classpath:realm.ini");
//设置为自定义realm获取认证数据
defaultSecurityManager.setRealm(new CustomerRealm());
//将安装工具类中设置默认安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//创建token令牌
UsernamePasswordToken token = new UsernamePasswordToken("mosin", "1234");
try {
subject.login(token);//用户登录
System.out.println("登录成功");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!!");
}
}
}
实际应用是将盐和散列后的值存在数据库中,自动realm从数据库取出盐和加密后的值由shiro完成密码校验。
/**
* 自定义md5+salt realm
*/
public class CustomerMD5Realm extends AuthorizingRealm {
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
//根据用户名查询数据库
if("mosin".equals(principal)){
// 参数1:用户名 参数2:密码 参数3:盐 参数4:自定义realm的名字
System.out.println(this.getName());
return new SimpleAuthenticationInfo(principal, "800d63a19662b2ba95bc2ffa01ab4804", ByteSource.Util.bytes("mosin"),this.getName());
}
return null;
}
}
public class CustomerMD5RealmTest {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//创建自定义MD5Realm对象
CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm();
//创建密码认证匹配器对象
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("MD5");
//设置散列的次数
md5.setHashIterations(1024);
//设置密码认证匹配器对象
customerMD5Realm.setCredentialsMatcher(md5);
//设置安全管理器的 认证安全数据源
defaultSecurityManager.setRealm(customerMD5Realm);
//设置安全工具类的安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取认证的主体
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosi", "12345");
//登录认证
try {
subject.login(usernamePasswordToken);
System.out.println("认证通过:"+subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!!");
}
}
}
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
授权可简单理解为who对what(which)进行How操作:
Who,即主体(Subject)
,主体需要访问系统中的资源。
What,即资源(Resource)
,如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型
和资源实例
,比如商品信息为资源类型
,类型为t01的商品为资源实例
,编号为001的商品信息也属于资源实例。
How,权限/许可(Permission)
,规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if(subject.hasRole("admin")){
//操作什么资源
}
基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
if(subject.isPermission("user:update:01")){ //资源实例
//对01用户进行修改
}
if(subject.isPermission("user:update:*")){ //资源类型
//对01用户进行修改
}
权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
注解式
@RequiresRoles("admin")
public void hello() {
//有权限
}
标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
注意: Thymeleaf 中使用shiro需要额外集成!
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("primaryPrincipal = " + primaryPrincipal);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addStringPermission("user:update:*");
simpleAuthorizationInfo.addStringPermission("product:*:*");
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if("xiaochen".equals(principal)){
String password = "3c88b338102c1a343bcb88cd3878758e";
String salt = "Q4F%";
return new SimpleAuthenticationInfo(principal,password,
ByteSource.Util.bytes(salt),this.getName());
}
return null;
}
}
public class CustomerMD5RealmTest {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//创建自定义MD5Realm对象
CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm();
//创建密码认证匹配器对象
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
//设置加密的次数
md5.setHashIterations(1024);
//设置密码认证匹配器对象
customerMD5Realm.setCredentialsMatcher(md5);
//设置安全管理器的 认证安全数据源
defaultSecurityManager.setRealm(customerMD5Realm);
//设置安全工具类的安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取认证的主体
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosin", "12345");
//登录认证
try {
subject.login(usernamePasswordToken);
System.out.println("认证通过:"+subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!!");
}
//基于角色的控制
//单角色控制
System.out.println("========hasRole==========");
boolean admin = subject.hasRole("admin");
System.out.println("hash admin role:"+admin);
//多角色控制
System.out.println("========hasAllRoles==========");
List<String> roles = Arrays.asList("admin", "user");
boolean booleans = subject.hasAllRoles(roles);
System.out.println("booleans = " + booleans);
// 基于任意角色的控制
System.out.println("========hasRoles==========");
boolean[] booleans1 = subject.hasRoles(roles);
for (boolean b : booleans1) {
System.out.println("b = " + b);
}
//基于权限字符串的权限控制
System.out.println("========isPermitted==========");
boolean permitted = subject.isPermitted("user:update:*");
System.out.println("permitted = " + permitted);
//分别具有哪些权限
boolean[] permitted1 = subject.isPermitted("user:update:*", "product:update:*");
for (boolean b : permitted1) {
System.out.println("b = " + b);
}
//同时具有哪些权限
boolean permittedAll = subject.isPermittedAll("user:update:*", "product:update:*");
System.out.println("permittedAll = " + permittedAll);
}
}
码文不易,本篇文章就介绍到这里,如果想要学习更多Java系列知识,点击关注博主,博主带你零基础学习Java知识。与此同时,对于日常生活有困扰的朋友,欢迎阅读我的第四栏目:《国学周更—心性养成之路》,学习技术的同时,我们也注重了心性的养成。