权限管理框架之shiro
1.权限管理的概念
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
2.认证
2.1认证的概念
认证: 即用户访问系统的控制,即我们之前俗称的登录。
2.2认证中抽象出的对象
- subject: 主体相当于之前的user,访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体。
- principal: 份信息,是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
- credential: 凭证信息,是只有主体自己知道的安全信息,如密码、证书等。
3.授权
3.1授权的概念
授权: 即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
3.2授权中抽象出的对象
- who: 即主体(Subject),主体需要访问系统中的资源。
- what: 即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括 资源类型 和 资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
- how: 权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。权限分为粗粒度和细粒度,粗粒度权限是指对资源类型的权限,细粒度权限是对资源实例的权限。
4.权限模型
也就是当使用权限管理时的表关系与结构
5.权限控制的方案
5.1基于角色的权限控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
if(主体.hasRole(“总经理角色id”)){
查询工资
}
5.2基于资源的权限控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
if(主体.hasRole(“查询工资的权限”)){
查询工资
}
6.shiro简介
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
7.shiro的核心架构图
8.shiro的第一个案例
注意: 这里以1.3.2版本为例,1.4x版本的相关api不太一样
1.迅速创建一个项目,引入相关jar
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
2.写一个main函数
public static void main(String[] args){
//获取安全管理器工厂
IniSecurityManagerFactory iniSecurityManagerFactory=new IniSecurityManagerFactory("classpath:shiro.ini");
//获取安全管理器
SecurityManager securityManager=iniSecurityManagerFactory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//token就是用户的令牌 包含用户的身份信息和凭证信息
AuthenticationToken token=new UsernamePasswordToken("zhangsan","1234567");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
}
boolean authenticated = subject.isAuthenticated();
}
3.在resources目录下写一个配置文件,名字是shiro.ini
[users]
zhangsan=123456
lisi=123456
注意: shiro中主体是否认证成功是通过抛异常的形式体现的,一般会抛出两个异常。
- UnknownAccountException 账号异常
- IncorrectCredentialsException 密码异常
9.shiro认证连接数据库
9.1源码追踪中发现的一些关键对象
关键类与其之间的依赖关系(记住的话面试中绝对能让你装一B):
public abstract class AuthenticatingRealm {
//该属性为凭证匹配器,是一个接口
private CredentialsMatcher credentialsMatcher;
//该方法为抽象方法 其作用使用来获取数据
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}
public abstract class AuthorizingRealm extends AuthenticatingRealm {
//该方法为抽象方法 其作用使用来进行授权
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection var1) throws AuthorizationException;
}
public class SimpleAccountRealm extends AuthorizingRealm {
//在此类中,分别实现了上边的两个抽象方法
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
//此行代码相当于我们写的根据username查询user对象,还未进行密码的比对工作
SimpleAccount account = this.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 = this.getUsername(principals);
this.USERS_LOCK.readLock().lock();
AuthorizationInfo var3;
try {
var3 = (AuthorizationInfo)this.users.get(username);
} finally {
this.USERS_LOCK.readLock().unlock();
}
return var3;
}
}
密码比对:
public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcher {
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = this.getCredentials(token);
Object accountCredentials = this.getCredentials(info);
return this.equals(tokenCredentials, accountCredentials);
}
}
9.2认证连接数据库(自定义Realm)
1.自定义开发Realm
public class MyRealm extends AuthenticatingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//数据操作 并且根据相应格式返回 不做密码比对
//获取到token中的principal,即username
String principal = (String) authenticationToken.getPrincipal();
//根据principal查询得到user对象,代码略,
//第一个参数为:标准的username
//第二个参数为:标准的password
//第三个参数为:当前类的类名
SimpleAccount simpleAccount = new SimpleAccount(user.getUsername(),user.getPassword,this.getName());
}
}
2.告知shiro使用自定义的Realm,修改shiro.ini配置文件
[main]
#自定义 realm,下边写自定义的MyRealm这个类的全限定类名
customRealm=com.zk.MyRealm
#将realm设置到securityManager(shiro的核心安全管理器)
securityManager.realms=$customRealm
3.运行main函数,就会发现走了自定义的Realm
9.3shiro的加密认证
shiro还帮我们提供的强大的加密认证方式,其提供的加密方式有多种(MD5、MD2、SHA1、SHA256、SHA384、SHA512),并且还可以基于此再进行多次散列,一般开发中散列1024次数,严格的保证密码的安全性。在此使用的是MD5加密方式
1.MyRealm返回密文数据
public class MyRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//仍然需要根据token中的principal查询获取到数据库中的user对象
//将user对象转换为AuthenticationInfo对象返回
//第一个参数为:标准的username
//第二个参数为:标准的password
//第三个参数为:标准的盐值
//第四个参数为:当前类的类名
AuthenticationInfo authenticationInfo =authenticationInfo = new SimpleAuthenticationInfo("zhangsan", "3d53b73c485f523ef2fe45f2b8dd3c58", ByteSource.Util.bytes("ABCD"), this.getName());
return authenticationInfo;
}
}
现在使用的凭证匹配器仍然为SimpleCredentialsMatcher,其密码对比方式为equals,明文和密文当然比对不通过,所以现在需要我们去修改shiro默认的凭证匹配器
2.修改配置文件,加入凭证匹配器的相关配置
[main]
#自定义凭证匹配器
hashedCredentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#自定义 realm
customRealm=com.zk.MyRealm
#自定的MyRealm继承自AuthenticatingRealm,所以CredentialsMatcher该属性也继承了下来,所以应告知它
customRealm.credentialsMatcher=$hashedCredentialsMatcher
#指明所使用的加密方式为MD5
hashedCredentialsMatcher.hashAlgorithmName=MD5
#指明散列的次数为1024次
hashedCredentialsMatcher.hashIterations=1024
#将realm设置到securityManager
securityManager.realms=$customRealm
3.加密认证到此完毕!
10.授权
10.1授权数据来源于数据库
public class MyRealm extends AuthorizingRealm {
//获取数据 获取认证的数据
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){
//数据库操作 基于身份信息查主体
}
//获取数据 获取授权的数据
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
//数据库操作 基于身份信息查主体 基于主体查角色 基于角色查权限
//简写一部分伪代码
//获取主身份,也就是username
String username = principalCollection.getPrimaryPricipal();
//根据username查询数据库获取相关的角色或者资源,取决于使用的到底是基于角色的方式还是基于资源的方式
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加相关的角色,当然或者添加相关的资源权限,完全取决于你的系统所设计的方案
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addStringPermission("user:*");
return simpleAuthorizationInfo;
}
}
10.2java环境下授权的api
- 基于角色
//判断当前主体是否包含此角色
boolean b = subject.hasRole("super");
List<String> list = Arrays.asList("super", "admin");
//判断当前主体是否包含某个角色
boolean[] booleans = subject.hasRoles(list);
//判断当前主体是否包含全部的角色
boolean b = subject.hasAllRoles(list);
- 基于资源
boolean b = subject.isPermitted("admin:delete");
String[] strs={"admin:delete", "admin:add"};
boolean[] permitted = subject.isPermitted(strs);
boolean permittedAll = subject.isPermittedAll(strs);
- 权限标识符
权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
11.SpringBoot环境下的认证和授权
11.1引入相关jar
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
11.2配置shiro的核心过滤器
在java环境之下,所有的相关配置都是写在配置文件当中的,但是在springboot项目中,它是减少了繁重的xml样板化配置,使用java配置这种方式可以使我们更加熟练的去书写相关的配置。
@Configuration
public class ShiroFilterConf {
//shiro过滤器的配置
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//多个过滤器 AnonymousFilter 匿名过滤器 anon ,即不做认证即可访问的数据
// FormAuthenticationFilter 认证过滤器 authc ,即需要认证后才能访问的数据
Map<String, String> map = new HashMap<>();
//设置登录的这个接口不需要认证
map.put("/user/loginUser", "anon");
//设置index.jsp这个页面也是不需要认证的
map.put("/index.jsp", "anon");
//设置所有的数据都需要认证之后才能访问,当然上边的两个除外了
map.put("/**", "authc");
//设置登录入口的路径,当我们访问了一个认证资源后shiro会自动的帮我们跳转到登录页面,默认的登录页面在根目录之下,如果你的登录页面不在这个目录之下的话需要在此进行相关的声明
shiroFilterFactoryBean.setLoginUrl("/main/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//shiro的核心对象 安全管理器
@Bean
public SecurityManager getSecurityManager(Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//自定义realm
@Bean
public Realm getRealm(CredentialsMatcher credentialsMatcher,CacheManager cacheManager) {
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(credentialsMatcher);
MyRealm.setCacheManager(cacheManager);
return myRealm;
}
//选择HashedCredentialsMatcher 凭证匹配器
@Bean
public CredentialsMatcher getCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
@Bean
public CacheManager getCacheManager(){
CacheManager cacheManager = new EhCacheManager();
return cacheManager;
}
}
11.3shiro中的相关标签
shiro在页面中提供了大量的标签,在此罗列的也是其中的一部分,这些标签也是shiro这个权限管理框架强大的一部分原因。
<shiro:principal></shiro:principal> //用户的身份信息
<shiro:authenticated></shiro:authenticated> //认证成功 执行标签体的内容
<shiro:notAuthenticated></shiro:notAuthenticated> //未认证 执行标签体内容
//基于角色的权限管理
<shiro:hasRole name="super"></shiro:hasRole>
<shiro:hasAnyRoles name="admin,super"></shiro:hasAnyRoles>
//基于资源的权限管理
<shiro:hasPermission name="user:delete"></shiro:hasPermission>
最后希望大家都能掌握好shiro这个框架,其中核心架构图一定要看懂理解到位!