Shiro之一

1、引言

最近在公司做的需求是有关于权限方面,而公司使用的事五张表进行权限的分配(用户表、资源表、角色表、用户角色表、资源角色表),反观公司的权限分配,都是对这几张表进行更改,而自己对Shiro有些了解,而Shiro又是对权限及认证等一些组件的一个安全框架,所以工作之余就使用了一番,这里先对Shiro做简单的纪要;

2、图解

 

image.png
  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份
  • Authorization:授权,验证某个已认证的用户是否拥有某个权限
  • Session Manager:会话管理,及用户登录就是一次会话,在没有退出之前,所有信息都在会话中
  • Cryptography:加密,保护数据的安全性
  • Caching:缓存,比如用户登录后,其用户信息拥有的角色、权限不必每次去查,以便提高效率
  • Concurrency:shiro支持多线程应用并发验证,即在一个线程中开启另一个线程,能把权限自动传播过去
  • Testing:提供测试支持
  • Run As:允许一个用户假装另一个用户的身份进行访问
  • Remember Me:记住我,及一次锻炼后,下次不用登陆直接访问注:Shiro不会维护用户、维护权限,这些需要我们自己去设计、提供,然后通过相应的接口注入shiro

3、shiro对外API的核心就是Subject

  • Subject:主体,代表了当前”用户“,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject;即一个抽象的概念,所有的subject都绑定到SecurityManager,与Subject的所有有交互的都会委托给SecurityManager,Subject认为是一个门面,而SecurityManager才是实际的执行者;
  • SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互,且他管理着所有Subject;可以看出他是Shiro的核心,他负责与后边介绍的其他组件进行交互;
  • Realm:域,Shiro从Realm获取安全数据,就是说SecurityManager要验证用户身份,那么他需要从Realm获取相应的用户进行比较以确定身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;

以上的介绍,简而言之,应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager,我们需要给shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断

Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入

3、身份认证

流程如下

  • 首先调用Subject.login(token)进行登录,其会自动委托给SecurityManager,调用之前必须通过SecuritUtils.SetSecurityManager()设置;
  • SecurityManager负责真正的身份验证逻辑,他会委托给Authenticator进行身份验证;
  • Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现
  • Authenticator可能会委托给相应的AuthenticationStrategy进行Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证
  • Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了,此处可以配置多个Realm,将相应的顺序及策略进行访问

4、授权

即在应用中控制谁能访问那些资源,
主体:Subject,即访问应用的用户,在Shiro中使用Subject代表该用户,用户只有授权后才能允许访问相应的资源
资源:Resource,在应用中用户可以访问的任何东西
权限:Permission,安全策略中原子版权授权的单位
角色:Role,代表操作集合,即权限的集合

授权方式:

  • 编程式:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
};
  • 注解式:
@RequiresRoles("admin")
public void hello() {
//有权限
};
  • JSP标签:
<shiro:hasRole name="admin"><!— 有权限 —></shiro:hasRole>;

授权流程:

  1. 首先调用Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而 SecurityManager接着会委托给 Authorizer;
  2. Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
  3. 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
  4. Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如isPermitted*/hasRole*会返回 true,否则返回 false 表示授权失败。

ModularRealmAuthorizer 进行多 Realm 匹配流程:

  • 首先检查相应的 Realm 是否实现了实现了 Authorizer;
  • 如果实现了 Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;
  • 如果有一个 Realm 匹配那么将返回 true,否则返回 false。

如果 Realm 进行授权的话,应该继承 AuthorizingRealm,其流程是:

  • 如果调用hasRole*,则直接获取 AuthorizationInfo.getRoles() 与传入的角色比较即可;首先如果调用如isPermitted(“user:view”),首先通过 PermissionResolver 将权限字符串转换成相应的 Permission 实例,默认使用 WildcardPermissionResolver,即转换为通配符的 WildcardPermission;
  • 通过 AuthorizationInfo.getObjectPermissions() 得到 Permission 实例集合;通过 AuthorizationInfo.getStringPermissions() 得到字符串集合并通过 PermissionResolver 解析为 Permission 实例;然后获取用户的角色,并通过 RolePermissionResolver 解析角色对应的权限集合(默认没有实现,可以自己提供);
  • 接着调用 Permission.implies(Permission p) 逐个与传入的权限比较,如果有匹配的则返回 true,否则 false。

推荐使用 AuthorizingRealm进行继承,因为: AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):表示获取身份验证信息;AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):表示根据用户身份获取授权信息。这种方式的好处是当只需要身份验证时只需要获取身份验证信息而不需要获取授权信息

5、编码/加密

Shiro 提供了 base64 和 16 进制字符串编码 / 解码的 API 支持,方便一些编码解码操作。Shiro 内部的一些数据的存储 / 表示都使用了 base64 和 16 进制字符串。

String str = "hello";
String base64Encoded = Base64.encodeToString(str.getBytes());
String str2 = Base64.decodeToString(base64Encoded);
Assert.assertEquals(str, str2);

6、Realm

定义Realm,代码实现:

public class UserRealm extends AuthorizingRealm {
private UserService userService = new UserServiceImpl();
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String username = (String)principals.getPrimaryPrincipal();
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    authorizationInfo.setRoles(userService.findRoles(username));
    authorizationInfo.setStringPermissions(userService.findPermissions(username));return authorizationInfo;
}

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String)token.getPrincipal();
    User user = userService.findByUsername(username);
    if(user == null) {
    throw new UnknownAccountException();//没找到帐号
    }
    if(Boolean.TRUE.equals(user.getLocked())) {
    throw new LockedAccountException(); //帐号锁定
    }
    //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以在此判断或自定义实现
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
        user.getUsername(), //用户名
        user.getPassword(), //密码ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
        getName()  //realm name
    );
    return authenticationInfo;
    }
};
  • UesreRreRaelaml 父类AuthorirzingRealm将获取Subject相关信息分成两步,获取身份验证信息doGetAuthenticationinfo及授权信息doGetAuthorizationinfo;
  • doGetAuthenticationinfo获取身份验证相关信息:首先根据传入的用户名获取用户user信息,然后后如果user为空,那么抛出账号异常等一系列异常,在组装SimpleAuthenticationinfo信息是,需要传入:身份信息(用户名)、凭证(密文密码)、盐(username+salt),CredentialsMatcher 使用盐加密传入的明文密码和此处的密文密码进行匹配;
  • doGetAuthorizationinfo获取授权信息,PrincipalCollection 是一个身份集合,因为我们现在就一个 Realm,所以直接调用 getPrimaryPrincipal 得到之前传入的用户名即可,然后根据用户名调用 UserService 接口获取角色及权限信息。扩展接口 RememberMeAuthenticationToken:提供了 “boolean isRememberMe()” 现“记住我”的功能; 扩展接口是 HostAuthenticationToken:提供了 “String getHost()” 方法用于获取用户 “主机” 的功能。
  • Shiro 提供了一个直接拿来用的 UsernamePasswordToken,用于实现用户名 / 密码 Token 组,另外其实现了 RememberMeAuthenticationToken 和 HostAuthenticationToken,可以实现记住我及主机验证的支持。

 

还有一些特点就不一一讲解了

7、总结

Subject:是 Shiro 的核心对象,基本所有身份验证、授权都是通过 Subject 完成。

  1. 身份信息获取
Object getPrincipal(); //Primary Principal
PrincipalCollection getPrincipals(); // PrincipalCollection&
  1. 身份验证
void login(AuthenticationToken token) throws AuthenticationException;
boolean isAuthenticated();
boolean isRemembered();

通过 login 登录,如果登录失败将抛出相应的 AuthenticationException,
如果登录成功调用 isAuthenticated就会返回 true,即已经通过身份验证;
如果 isRemembered 返回 true,表示是通过记住我功能登录的而不是调用 login 方法登录的。
isAuthenticated/isRemembered 是互斥的,
即如果其中一个返回 true,另一个返回 false。
  1. 角色授权验证
boolean hasRole(String roleIdentifier);
boolean[] hasRoles(List<String> roleIdentifiers);
boolean hasAllRoles(Collection<String> roleIdentifiers);
void checkRole(String roleIdentifier) throws AuthorizationException;
void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;
void checkRoles(String... roleIdentifiers) throws AuthorizationException;

hasRole * 进行角色验证,验证后返回 true/false;
而 checkRole * 验证失败时抛出 AuthorizationException异常。
  1. 权限授权验证
boolean isPermitted(String permission);
boolean isPermitted(Permission permission);
boolean[] isPermitted(String... permissions);
boolean[] isPermitted(List<Permission> permissions);
boolean isPermittedAll(String... permissions);
boolean isPermittedAll(Collection<Permission> permissions);
void checkPermission(String permission) throws AuthorizationException;
void checkPermission(Permission permission) throws AuthorizationException;
void checkPermissions(String... permissions) throws AuthorizationException;
void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;

isPermitted * 进行权限验证,验证后返回 true/false;
而 checkPermission * 验证失败时抛出 AuthorizationException。
  1. 会话
Session getSession(); //相当于getSession(true)
Session getSession(boolean create);

类似于 Web 中的会话。如果登录成功就相当于建立了会话,
接着可以使用 getSession 获取;
如果 create=false 如果没有会话将返回 null,
而 create=true 如果没有会话会强制创建一个。
  1. 退出
void logout();

对于 Subject 我们一般这么使用:

  • 身份验证(login)
  • 授权(hasRole/isPermitted * 或 checkRole/checkPermission*)
  • 将相应的数据存储到会话(Session)
  • 切换身份(RunAs)/ 多线程身份传播
  • 退出

最后,会在近期使用Springboot+shiro的组合框架形式,来完成一套安全的权限分配功能,届时会将代码贴出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值