阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用

简单介绍

Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能

AuthorizingRealm的继承关系

Realm->CachingRealm->AuthenticatingRealm->AuthorizingRealm

本文只针对以上关系进行讲解,其余的实现类请自行查看源码

Realm接口类

Realm提供了安全的访问应用的相关实体类,比如用户、角色、权限,对其中的访问应用相应的认证或者授权操作。其提供的主要的方法为AuthenticationInfo#getAuthenticationInfo,涉及的内容是关于信息的认证,这主要由AuthencatingRealm类实现

CachingRealm抽象类

提供缓存功能,简单看下其下的变量以及主要方法。

  • 相关变量如下
	//设置是否允许缓存,构造函数中默认为true
private boolean cachingEnabled;
//设置缓存管理器,需要另外引入
private CacheManager cacheManager;
  • 主要方法
  1. CachingRealm#afterCacheManagerSet

    默认实现为空,供子类调用,在设置cacheManager变量调用此方法
  2. CachingRealm#getAvailablePrincipal
//获取principal对象,一般都是子类在执行授权操作赋予的
protected Object getAvailablePrincipal(PrincipalCollection principals) {
Object primary = null;
if (!CollectionUtils.isEmpty(principals)) {
Collection thisPrincipals = principals.fromRealm(getName());
if (!CollectionUtils.isEmpty(thisPrincipals)) {
primary = thisPrincipals.iterator().next();
} else {
//no principals attributed to this particular realm. Fall back to the 'master' primary:
primary = principals.getPrimaryPrincipal();
}
} return primary;
}

AuthenticatingRealm-验证Realm抽象类

简单分析下其主要私有变量以及方法

  • 主要参数
//与父类的cachingEnabled结合使用,在构造函数里其默认是false
private boolean authenticationCachingEnabled;
//用户凭证验证类,比如校验密码是否一致
private CredentialsMatcher credentialsMatcher;
//认证Token类,默认为UsernamePasswordToken类
private Class<? extends AuthenticationToken> authenticationTokenClass;
  • 主要方法
  1. AuthenticatingRealm#setAuthenticationCachingEnabled-设置缓存是否许可
//可见如果只设置authenticationCachingEnable,其为true,也会设置cachingEnable=true
public void setAuthenticationCachingEnabled(boolean authenticationCachingEnabled) {
this.authenticationCachingEnabled = authenticationCachingEnabled;
if (authenticationCachingEnabled) {
setCachingEnabled(true);
}
}
  1. AuthenticatingRealm#isAuthenticationCachingEnabled
//可见是否应用缓存是根据authenticationCachingEnabled和cachingEnabled联合判断的
public boolean isAuthenticationCachingEnabled() {
return this.authenticationCachingEnabled && isCachingEnabled();
}
  1. AuthenticatingRealm#getAuthenticationInfo

    认证接口实现方法,该方法的回调一般是通过subject.login(token)方法来实现的
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取缓存中的认证信息,其中也会涉及到调用isAuthenticationCachingEnabled
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
//调用doGetAuthenticationInfo方法,此处为抽象类,供子类调用
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
} if (info != null) {
//对获取的认证信息进行校验,一般是比对凭证加密后是否还一致
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
} return info;
}
  1. AuthenticatingRealm#doGetAuthenticationInfo

    获取认证信息方法,抽象方法供子类实现
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException ;
  1. AuthenticatingRealm#assertCredentialsMatch

    对凭证信息的校验,涉及到加密方式
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
//获取私有属性credentialsMatcher
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
//校验方法
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
//可见credentialsMatcher属性必须要设定,否则会抛异常
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
"credentials during authentication. If you do not wish for credentials to be examined, you " +
"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}

AuthorizingRealm-授权抽象类

其中的内容和其父类验证抽象类基本相似,这里就不赘述了,主要提下主要方法

  1. AuthorizingRealm#getAuthorizationInfo
//授权
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) { if (principals == null) {
return null;
} AuthorizationInfo info = null; if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
//是否引用cache
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
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) {
// Call template method if the info was not found in a cache
//调用授权抽象方法,供子类实现
info = doGetAuthorizationInfo(principals);
// If the info is not null and the cache has been created, then cache the authorization info.
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
} return info;
}
  1. AuthorizingRealm#doGetAuthorizationInfo

    真实授权抽象方法,供子类调用
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);

示例

下述示例结合了授权与验证,即继承AuthorizingRealm即可

  1. spring-shiro配置文件样例
<!--缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean>
<!-- 凭证匹配器 -->
<bean id="credentialsMatcher" class="com.jing.test.cas.admin.core.shiro.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
<property name="storedCredentialsHexEncoded" value="true"/>
</bean> <!--密码处理类-->
<bean id="passwordService" class="com.jing.test.cas.admin.core.shiro.ShiroPasswordService"></bean>
<!-- Realm实现 -->
<bean id="shiroDBRealm" class="com.jing.test.cas.admin.core.shiro.ShiroDBRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="false"/>
<property name="shiroPasswordService" ref="passwordService"/>
<property name="shiroUserService" ref="shiroUserService"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroDBRealm"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/index.html"/>
<property name="unauthorizedUrl" value="/403.html"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="perm" value-ref="permissionsAuthorizationFilter"/>
<entry key="captcha" value-ref="captchaValidateFilter"/>
<entry key="sysUser" value-ref="sysUserFilter"/>
<entry key="user" value-ref="userFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<!-- 如果不应用acm cas方案则添加 /logout=logout -->
<value>
/ossmanager/api/** = anon
/test/** = anon
/login = captcha,authc
/logout=logout
/index = anon
/jcaptcha.jpeg = anon
/403.html = anon
/login.html = anon
/favicon.ico = anon
/static/** = anon
/index.html=user,sysUser
/welcome.html=user,sysUser
/admin/user/modifyPwd.html=user,sysUser
/admin/user/updatePassword=user,sysUser
/admin/user/role/list=user,sysUser
/** = user,sysUser,perm
</value>
</property>
</bean> <!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
  1. Realm接口实现类
package com.jing.test.cas.admin.core.shiro;

import com.jing.test.cas.admin.core.exceptions.BizException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Collections;
import java.util.Set;
/**
*Realm接口实现类
*/
public class ShiroDBRealm extends AuthorizingRealm {
/**
* 日志对象
*/
private static final Logger logger = LoggerFactory.getLogger(ShiroDBRealm.class); /**
* 账户禁用
*/
private static final String USER_STATUS_FORBIDDEN = "1"; /**
* 权限相关用户服务接口
*/
private IShiroUserService shiroUserService; /**
* 密码服务类 加密作用
*/
private ShiroPasswordService shiroPasswordService; /**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
// (可能是关闭浏览器,或超时),但此时缓存依旧存在(principals),所以会自己跑到授权方法里。
if (!SecurityUtils.getSubject().isAuthenticated()) {
doClearCache(principalCollection);
SecurityUtils.getSubject().logout();
return null;
}
ShiroUser shiroUser = (ShiroUser)principalCollection.getPrimaryPrincipal();
String userName = shiroUser.getUserName();
if(StringUtils.isNotBlank(userName)){
SimpleAuthorizationInfo sazi = new SimpleAuthorizationInfo();
try {
Set<String> roleIds= shiroUserService.getRoles(userName);
for (String roleId: roleIds){
shiroUser.setRoleId(roleId);
}
sazi.addRoles(roleIds);
sazi.addStringPermissions(shiroUserService.getPermissions(userName));
return sazi;
} catch (Exception e) {
logger.error(e.getMessage(),e);
}
}
return null;
} /**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
ShiroUser shiroUser = shiroUserService.getShiroUser(token.getUsername());
checkUserStatus(shiroUser);
if(shiroUser != null){
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(shiroUser,shiroUser.getPassword(),ByteSource.Util.bytes(shiroUser.getUserName()+shiroPasswordService.getPublicSalt()),getName());
return authenticationInfo;
}
return null;
} /**
* 检查用户状态
* @param shiroUser
*/
private void checkUserStatus(ShiroUser shiroUser) {
if(StringUtils.equalsIgnoreCase(shiroUser.getUserStatus(),USER_STATUS_FORBIDDEN)){
throw new ForbiddenException("用户已被禁用");
}
} /**
* 初始化方法
* 设定Password校验的Hash算法与迭代次数.
**/
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(
shiroPasswordService.getHashAlgorithm());
matcher.setHashIterations(shiroPasswordService.getHashInterations());
setCredentialsMatcher(matcher);
} public void setShiroUserService(IShiroUserService shiroUserService) {
this.shiroUserService = shiroUserService;
} public void setShiroPasswordService(ShiroPasswordService shiroPasswordService) {
this.shiroPasswordService = shiroPasswordService;
}
}

总结

授权与验证的逻辑做了简单的了解,其中授权可通过subject.isPermited()等方法调用;验证则可通过subject.login()等方法来调用

Spring-shiro源码陶冶-AuthorizingRealm用户认证以及授权的更多相关文章

  1. Spring-shiro源码陶冶-DefaultFilter

    阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 Apache Shiro自带的 ...

  2. Spring-shiro源码陶冶-DelegatingFilterProxy和ShiroFilterFactoryBean

    阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 web.xml配置Shiro环 ...

  3. shiro源码篇 - 疑问解答与系列总结,你值得拥有

    前言 开心一刻 小明的朋友骨折了,小明去他家里看他.他老婆很细心的为他换药,敷药,然后出去买菜.小明满脸羡慕地说:你特么真幸福啊,你老婆对你那么好!朋友哭得稀里哗啦的说:兄弟你别说了,我幸福个锤子,就 ...

  4. Spring mybatis源码篇章-MybatisDAO文件解析(一)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 加载指定的mybatis主文件 Mybatis模板文件,其中的属性 ...

  5. Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 首先了解下sql mapper的动态sql语法 具体的动态sql的 ...

  6. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  7. Shiro源码分析之SecurityManager对象获取

    目录 SecurityManager获取过程 1.SecurityManager接口介绍 2.SecurityManager实例化时序图 3.源码分析 4.总结 @   上篇文章Shiro源码分析之获 ...

  8. Spring mybatis源码篇章-动态SQL基础语法以及原理

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis的XML文件加载 前话 前文通过Spring中配置mapperLocations属性来进行对m ...

  9. Spring mybatis源码篇章-Mybatis主文件加载

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 前话 本文承接前文的内容继续往下扩展,通过Spring与Mybatis的 ...

随机推荐

  1. bzoj:1230: [Usaco2008 Nov]lites 开关灯

    Description Farmer John尝试通过和奶牛们玩益智玩具来保持他的奶牛们思维敏捷. 其中一个大型玩具是牛栏中的灯. N (2 <= N <= 100,000) 头奶牛中的每 ...

  2. HDU 1024 Max Sum Plus Plus【动态规划求最大M子段和详解 】

    Max Sum Plus Plus Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  3. 2017广东工业大学程序设竞赛C题爬楼梯

    Description 小时候,我只能一阶一阶得爬楼梯, 后来,我除了能一次爬一阶,还可以一次爬两阶, 到现在,我最多一次可以爬三阶. 那么现在问题来了,我想爬上n层楼,相邻楼层之间有一段楼梯,虽然我 ...

  4. Xshell无法连接到LINUX虚拟机

    首先与遇到的情况是,在虚拟机下安装了Linux后,xshell无法连接远程的虚拟机. 我遇到的情况是虚拟机可以ping 主机,主机确ping不了虚拟机. 使用的VM设置了两个网卡,一个nat  一个h ...

  5. C#、.NET Framework、CLR的关系

    很多人没有将C#..NET Framework(.NET框架).CLR(Common Language Runtime,公共语言运行库)这三者之间的关系区分清楚,认为其版本号是一一对应的.其实不然,. ...

  6. C#历年来最受欢迎功能

    不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: http://www.dotnetcurry.com/csharp/1 ...

  7. java中强,软,弱,虚引用 以及WeakHahMap

    java中强,软,弱,虚引用  以及WeakHahMap   一:强软引用: 参考:http://zhangjunhd.blog.51cto.com/113473/53092/进行分析   packa ...

  8. 久未更 ~ 三之 —— CardView简单记录

    > > > > > 久未更 系列一:CardView 点击涟漪效果实现 //在 cardview 中 实现点击涟漪效果 android:clickable="t ...

  9. Asp.net mvc 中View的呈现(一)

    [toc] 我们知道针对客户端的请求,最终都会转换为对 Controller 中的一个 Action 方法的调用,指定的 Action 方法会返回一个 ActionResult 类型的实例来响应该请求 ...

  10. 【问题解决】java中为什么不建议使用DataInputStream 的readLine()方法

    常用方法 int read(byte[] b) 从包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组 b 中. int read(byte[] b, int off, int len) 从包含 ...