一 Realm
Realm:域,Realm 充当了 Shiro 与应用安全数据间的“桥梁”
或者“连接器”
。
也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro 。当配置 Shiro时,你必须至少指定一个 Realm ,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。
Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。
功能
Realm能做的工作主要有以下几个方面:
- 身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息
- 权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息
- 令牌支持(supports方法)判断该令牌(Token)是否被支持
令牌有很多种类型,例如:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌)
身份认证
根据传进来的 Token
,返回用户的验证信息
。
Token
比如最常见的UsernamePasswordToken
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
private String username;
private char[] password;
private boolean rememberMe;
private String host;
...
用户验证信息
就是用户验证通过后,返回给系统的信息。例如:用户登录验证的话,一般来说,返回给系统的“用户验证信息”就应该是这个用户的“用户名和密码”。但也可以返回其它信息,例如返回用户的“邮箱地址和登录密码”信息,做为“用户验证信息”(失败直接抛出AuthenticationException
异常了)
认证过程
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
// doGetAuthenticationInfo方法的内容,由各个子类来实现。
// 主要是用来取得我们保存的“用户验证信息”,例如DB里保存的密码(具体看JdbcRealm的方法实现)
if (info == null) {
info = doGetAuthenticationInfo(token);
...
}
// 在这里,把用户提交的信息(Token)和我们保存的“用户验证信息”进行比较
// 如果不通过,直接抛出定义好的异常。
if (info != null) {
assertCredentialsMatch(token, info);
} else {
return info;
}
权限认证
public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
...
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
} else {
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
Cache<Object, AuthorizationInfo> cache = this.getAvailableAuthorizationCache();
Object key;
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
key = this.getAuthorizationCacheKey(principals);//先从principals获取key
info = (AuthorizationInfo)cache.get(key);//根据key从缓存中获取AuthorizationInfo
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
}
}
}
if (info == null) {//cache中没有对应用户的缓存数据
info = this.doGetAuthorizationInfo(principals);//查询权限
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
key = this.getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
}
getAuthorizationInfo的执行时机
- subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候
- @RequiresRoles(“admin”) :在方法上加注解的时候;
- 标签:[@shiro.hasPermission name = “admin”][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。
实现
![](https://img.haomeiwen.com/i17148884/0d4a7aa052865d82.png)
**一个只有登陆认证的Realm **
public class MyRealm1 implements Realm {
@Override
public String getName() {
return "myrealm1";
}
@Override
public boolean supports(AuthenticationToken token) {
//仅支持UsernamePasswordToken类型的Token
return token instanceof UsernamePasswordToken;
}
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(!"zhang".equals(username)) {
throw new UnknownAccountException(); //如果用户名错误
}
if(!"123".equals(password)) {
throw new IncorrectCredentialsException(); //如果密码错误
}
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(username, password, getName());
}
}
较完整
@Component
public class LoginRealm extends AuthorizingRealm{
@SuppressWarnings("SpringJavaAutowiringInspection")//忽略警告,下同
@Resource(name = "roleServiceImpl")
private RoleService roleService;
@SuppressWarnings("SpringJavaAutowiringInspection")//忽略警告,下同
@Resource(name = "viewEmployeeMiPsdServiceImpl")
private ViewEmployeeMiPsdService viewEmployeeMiPsdService;
/**
* 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
* 当调用权限验证时,就会调用此方法
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String code = (String) getAvailablePrincipal(principalCollection);
Role role = null;
ViewEmployeeMiPsd viewEmployeeMiPsd = null;
viewEmployeeMiPsd = viewEmployeeMiPsdService.findByCode(code);
//通过用户名从数据库获取角色权限集
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> r = new HashSet<>();
if (role != null) {
String[] roles = role.getRolename().split("\\+");
for(int i = 0;i < roles.length; i++){
r.add(roles[i].toString());
}
//放入该用户权限信息
info.setRoles(r);
}
return info;
}
/**
* 在这个方法中,进行身份验证
* login时调用
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//工号
String code = (String) token.getPrincipal();
//密码
String password = new String((char[])token.getCredentials());
ViewEmployeeMiPsd viewEmployeeMiPsd = null;
viewEmployeeMiPsd = viewEmployeeMiPsdService.findByCode(code);
if (viewEmployeeMiPsd == null) {
//没有该用户
throw new UnknownAccountException();
} else if (!password.equals(viewEmployeeMiPsd.getPsd())) {
//密码错误
throw new IncorrectCredentialsException();
}
//身份验证通过,返回一个身份信息
AuthenticationInfo aInfo = new SimpleAuthenticationInfo(code,password,getName());
return aInfo;
}
}
二 Subject
Subject是一个抽象的概念,通常我们理解为用户,但它可以是任何与系统交互的“东西”
Subject一词是一个安全术语,其基本意思是“当前的操作用户
”
称之为“用户”并不准确
,因为“用户”一词通常跟人相关。在安全领域,术语“Subject”可以是人,也可以是第三方进程、后台帐户(Daemon Account)、定时作业(Corn Job)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
在程序中你都能轻易的获得Subject,允许在任何需要的地方进行安全操作。每个Subject对象都必须与一个SecurityManager
进行绑定,你访问Subject对象其实都是在与SecurityManager里的特定Subject进行交互。
获取方式
Subject subject = SecurityUtils.getSubject()
用途
操作session
Session session = subject.getSession();
session.setAttribute( "someKey", "aValue" );
这里的Session并不是HttpSession,而是shiro为我们提供的,它的操作与HttpSession一样,他们最大的区别就是shiro session不需要依赖http服务器,下图是shiro Session的实现类。
![](https://img.haomeiwen.com/i17148884/3ec0560c8c3d24f8.png)
- 默认情况下shiro Session的实现的是DelegatingSession.
- 而当我们整合HTTP服务器时,shiro Session会自动实现HttpServletSession,再来看一下HttpServletSession的实现
三 SecurityManager
安全管理器,是Shiro的核心
接口继承
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
//登录方法
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
//注销方法
void logout(Subject subject);
//创建subject
Subject createSubject(SubjectContext context);
}
继承的三个接口分别是
- Authenticator(认证器) ,用户登录时候会调用
public interface Authenticator {
//初始的认证方法,入参是一个令牌对象,返回值是一个包装好的身份信息
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;
}
- Authorizer(授权器) 检查是否具有相关的权限.这个接口定义的都是检查权限和角色的方法
- SessionManager(会话管理器)
继承体系
![](https://img.haomeiwen.com/i17148884/9fb5fe2ae5f93baa.png)
与spring整合的默认安全管理器就是图中标注的那个,一般如果不自己定义的话,就会使用这个东西
很有意思的集成体系,每次继承都实现一些接口,或者实现一些特色的功能,
- CacheSecurityManager 和缓存相关
public abstract class CachingSecurityManager implements SecurityManager, Destroyable, CacheManagerAware, EventBusAware
- RealmSecurityManager 这个抽象类保存了所有的Realm实现
public abstract class RealmSecurityManager extends CachingSecurityManager {
private Collection<Realm> realms;
- AuthenticatingSecurityManager 这个方法实现了认证的部分功能
- AuthorizingSecurityManager 完成授权部分功能
- SessionsSecurityManager 注入了会话管理器
- DefaultSecurityManager 默认非web环境的安全管理器
- DefaultWebSecurityManager 默认的继承web层次的安全管理器
网友评论