文章目录
本篇主要介绍如何快速开始 Shiro 之旅, 并涉及部分源码解析 (包含 Shiro 的基本工作机制).
相关源码参考: demo-shiro-quickstart
ShiroConfiguration
这是开发者定义的 Shiro 的核心配置类, 在其中我们需要完成 Realm
, ShiroFilterFactoryBean
和 WebSecurityManager
的注入.
PART#1: WebSecurityManager
首先, WebSecurityManager
继承自 SecurityManager
, 后者是 Shiro 的核心组件. 所有的交互都是通过 SecurityManager
进行控制, 它管理着所有 Subject
, 且负责进行认证 (Authenticator
) 和授权 (Authorizer
) 及会话缓存 (SessionManager
) 的管理.
如果开发者仅仅注入 WebSecurityManager
, 启动时会报错:
Description:
Parameter 0 of method authorizationAttributeSourceAdvisor in org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration required a bean named 'authorizer' that could not be found.
Action:
Consider defining a bean named 'authorizer' in your configuration.
而 ShiroAnnotationProcessorAutoConfiguration#authorizationAttributeSourceAdvisor(SecurityManager) 方法的目的是在上下文中注入一个 AuthorizationAttributeSourceAdvisor
, 请看 ShiroAnnotationProcessorAutoConfiguration
的部分代码摘录 ↓
@Bean
@ConditionalOnMissingBean
@Override
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
return super.authorizationAttributeSourceAdvisor(securityManager);
}
我们先来看看 AuthorizationAttributeSourceAdvisor
↓
AuthorizationAttributeSourceAdvisor
以往, 在 spring-shiro.xml 中通常会加上一个 aop 配置, 以使 @RequiresPermission
, @RequiresRoles
, @RequiresUser
和 @RequiresGuest
生效, 配置通常形如:
<aop:config />
<!--权限注解的 advisor -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
但是 <aop:config />
中并没有配置通知, 也没有配置切入点, Shiro 的认证注解怎么就起作用了呢?
这里就不得不提到 AuthorizationAttributeSourceAdvisor
这个 “通知器”. 通过其层次结构我们看到, 它实现了 org.springframework.aop.Pointcut 接口, 后者是 Spring 的 “切入点” 抽象 “标准 (接口)”. 而在 AuthorizationAttributeSourceAdvisor
的父类 StaticMethodMatcherPointcut
实现了 Pointcut
的接口方法:
public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {
private ClassFilter classFilter = ClassFilter.TRUE;
public void setClassFilter(ClassFilter classFilter) {
this.classFilter = classFilter;
}
@Override
public ClassFilter getClassFilter() {
return this.classFilter;
}
@Override
public final MethodMatcher getMethodMatcher() {
return this;
}
}
我们注意到 classFilter
的值是 ClassFilter.TRUE
, 跟进去看到说明是 “Canonical instance of a ClassFilter that matches all classes”, 意味着 AuthorizationAttributeSourceAdvisor
会匹配所有类.
再来看 getMethodMatcher()
, 返回的是 this
, 而 AuthorizationAttributeSourceAdvisor
实现了 MethodMatchaer
, 所有返回的就是 AuthorizationAttributeSourceAdvisor
. MethodMatcher
的 boolean matches(Method method, Class<?> targetClass, Object... args)
用来判断方法匹配. 而在 AuthorizationAttributeSourceAdvisor
的 matches 实现中:
public boolean matches(Method method, Class targetClass) {
Method m = method;
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
}
}
return false;
}
可以得出: AuthorizationAttributeSourceAdvisor
会拦截所有类中加了认证注解的方法.
PART#2: ShiroFilterFactoryBean
再来看看 ShiroConfiguration 注入的第二个 Bean: org.apache.shiro.spring.web.ShiroFilterFactoryBean…
ShiroFilterFactoryBean
是创建 ShiroFilter
的 FactoryBean
. Shiro 通过后者来拦截需要安全拦截的 URL 以实现访问控制. 获取 ShiroFilter
的方式则是通过这个工厂 Bean 的 getObject() 方法, 而在 getObject() 方法中, 又调用了 createInstance()
, 我们来看 createInstance()
的实现:
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
...
protected FilterChainManager createFilterChainManager() {
DefaultFilterChainManager manager = new DefaultFilterChainManager();
Map<String, Filter> defaultFilters = manager.getFilters();
//apply global settings if necessary:
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
//Apply the acquired and/or configured filters:
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
manager.addFilter(name, filter, false);
}
}
// set the global filters
manager.setGlobalFilters(this.globalFilters);
//build up the chains:
Map<String, String> chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
manager.createChain(url, chainDefinition);
}
}
// create the default chain, to match anything the path matching would have missed
manager.createDefaultChain("/**");
return manager;
}
...
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
...
}
可以看到创建 SpringShiroFilter
用到了两个组件: WebSecurityManager
和 PathMatchingFilterChainResolver
. 前者在上一节已经介绍过, 我们重点看后者.
FilterChainResolver
PathMatchingFilterChainResolver
是 FilterChainResolver
的实现类, 能够在一次 ServletRequest
中解析 javax.servlet.FilterChain
. 只有一个接口方法: FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain)
, 返回一个对于当前请求来说, 应当被执行的过滤器链否则返回 null
(应用 originalChain). PathMatchingFilterChainResolver
实现了对于 PatternMatcher
的支持 (默认是 org.apache.shiro.util.AntPathMatcher), 让过滤器链解析器拥有通配符匹配的能力并且会在实现接口的方法 getChain 中, 应用 pathMatcher,
public class PathMatchingFilterChainResolver implements FilterChainResolver {
...
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
final String requestURI = getPathWithinApplication(request);
final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);
//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
...
return filterChainManager.proxy(originalChain, pathPattern);
} else {
// in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
// but the pathPattern match "/resource/menus" can not match "resource/menus/"
// user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
pathPattern = removeTrailingSlash(pathPattern);
if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
...
return filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
}
}
}
return null;
}
...
}
FilterChainManager
从上面摘录的 PathMatchingFilterChainResolver#getChain
代码中可以看到, ChainResolver 还包含了 1 个核心组件: FilterChainManager
. 从 ShiroFilterFactoryBean#createInstance() 方法中的 new PathMatchingFilterChainResolver() 跟进去可以看到:
public class PathMatchingFilterChainResolver implements FilterChainResolver {
...
public PathMatchingFilterChainResolver() {
this.pathMatcher = new AntPathMatcher();
this.filterChainManager = new DefaultFilterChainManager();
}
...
}
PathMatchingFilterChainResolver#getChain
方法体中 FilterChainManager
的默认实现是 org.apache.shiro.web.filter.mgt.DefaultFilterChainManager, 其作用是管理 Filter 和Filter 链 (维护着一个过滤器的 Map 以及过滤器链表的 Map),配合 PathMatchingFilterChainResolver
解析出 Filter 链. 接下来我们来看看这个 DefaultFilterChainManager
的实现:
public class DefaultFilterChainManager implements FilterChainManager {
...
public DefaultFilterChainManager() {
this.filters = new LinkedHashMap<String, Filter>();
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
this.globalFilterNames = new ArrayList<>();
addDefaultFilters(false);
}
public DefaultFilterChainManager(FilterConfig filterConfig) {
this.filters = new LinkedHashMap<String, Filter>();
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
this.globalFilterNames = new ArrayList<>();
setFilterConfig(filterConfig);
addDefaultFilters(true);
}
...
#filter#
protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
Filter existing = getFilter(name);
if (existing == null || overwrite) {
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
if (init) {
initFilter(filter);
}
this.filters.put(name, filter);
}
}
...
protected void addDefaultFilters(boolean init) {
for (DefaultFilter defaultFilter : DefaultFilter.values()) {
addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}
}
...
#chain#
public void createChain(String chainName, String chainDefinition) {
...
// first add each of global filters
if (!CollectionUtils.isEmpty(globalFilterNames)) {
globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
}
...
String[] filterTokens = splitChainDefinition(chainDefinition);
//each token is specific to each filter.
//strip the name and extract any filter-specific config between brackets [ ]
for (String token : filterTokens) {
String[] nameConfigPair = toNameConfigPair(token);
//now we have the filter name, path and (possibly null) path-specific config. Let's apply them:
addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
if (!StringUtils.hasText(chainName)) {
throw new IllegalArgumentException("chainName cannot be null or empty.");
}
Filter filter = getFilter(filterName);
...
applyChainConfig(chainName, filter, chainSpecificFilterConfig);
NamedFilterList chain = ensureChain(chainName);
chain.add(filter);
}
protected NamedFilterList ensureChain(String chainName) {
NamedFilterList chain = getChain(chainName);
if (chain == null) {
chain = new SimpleNamedFilterList(chainName);
this.filterChains.put(chainName, chain);
}
return chain;
}
...
#global#
public void setGlobalFilters(List<String> globalFilterNames) throws ConfigurationException {
// validate each filter name
if (!CollectionUtils.isEmpty(globalFilterNames)) {
for (String filterName : globalFilterNames) {
Filter filter = filters.get(filterName);
...
this.globalFilterNames.add(filterName);
}
}
}
}
关于 DefaultFilterChainManager
的 filters, 可以看到, 在构造 DefaultFilterChainManager
的时候, 会首先默认从 DefaultFilter
这个枚举类中将默认的 Filter
添加到 filters 中 (关于 DefaultFilter
以及自定义 Filter, 会在本系列稍后的文章中介绍到), filters 维护着一个过滤器的池, 其中的过滤器用于创建过滤器链.
关于 DefaultFilterChainManager
的 globalFilterNames, 它持有的是对于每一个过滤器链都启用的过滤器的名字, 它在 setGlobalFilters 中被初始化, 而 setGlobalFilters 在 ShiroFilterFactoryBean
的 createInstance 方法中创建 FilterCharinManager
时被调用.
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
...
protected FilterChainManager createFilterChainManager() {
DefaultFilterChainManager manager = new DefaultFilterChainManager();
Map<String, Filter> defaultFilters = manager.getFilters();
//apply global settings if necessary:
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
//Apply the acquired and/or configured filters:
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
//'init' argument is false, since Spring-configured filters should be initialized
//in Spring (i.e. 'init-method=blah') or implement InitializingBean:
manager.addFilter(name, filter, false);
}
}
// set the global filters
manager.setGlobalFilters(this.globalFilters);
//build up the chains:
Map<String, String> chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
manager.createChain(url, chainDefinition);
}
}
// create the default chain, to match anything the path matching would have missed
manager.createDefaultChain("/**"); // TODO this assumes ANT path matching, which might be OK here
return manager;
}
...
}
~ 构建 filterChains 的过程: DefaultFilterChainManager
的 filterChains 维护的是过滤器链名 (实际上就是 URL 的 pattern) 和过滤器的链表的映射关系. 从上面的 ShiroFilterFactoryBean
创建 FilterChainManager
的方法片段也可以看出, filterChains 是在 ShiroFilterFactoryBean
的 createFilterChainManager
中通过接口方法 FilterChainManager#createChain(url, chainDefinition)
构建的.
再来看看构建过滤器链的 “数据源”: getFilterChainDefinitionMap(): 它是 ShiroFilterFactoryBean
的成员属性: filterChainDefinitionMap, 默认是空 Map, 由开发者指定.
紧接着, Shiro 又创建了一个匹配所有 URL (/**) 的默认过滤器链:
public class DefaultFilterChainManager implements FilterChainManager {
...
public void createDefaultChain(String chainName) {
// only create the defaultChain if we don't have a chain with this name already
// (the global filters will already be in that chain)
if (!getChainNames().contains(chainName) && !CollectionUtils.isEmpty(globalFilterNames)) {
// add each of global filters
globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
}
}
...
}
从上面代码片段可以看出, Shiro 将 globalFilterNames
中的过滤器关联到了 /**. 而 globalFilterNames 的初始化在 ShiroFilterFactoryBean
:
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
...
public ShiroFilterFactoryBean() {
this.filters = new LinkedHashMap<String, Filter>();
this.globalFilters = new ArrayList<>();
this.globalFilters.add(DefaultFilter.invalidRequest.name());
this.filterChainDefinitionMap = new LinkedHashMap<String, String>();
}
...
}
最后, 回到 DefaultFilterChainManager
的 createChain
看 filterChains 是怎么构建的:
public class DefaultFilterChainManager implements FilterChainManager {
...
public void createChain(String chainName, String chainDefinition) {
...
// first add each of global filters
if (!CollectionUtils.isEmpty(globalFilterNames)) {
globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
}
...
//parse the value by tokenizing it to get the resulting filter-specific config entries
//
//e.g. for a value of
//
// "authc, roles[admin,user], perms[file:edit]"
//
// the resulting token array would equal
//
// { "authc", "roles[admin,user]", "perms[file:edit]" }
String[] filterTokens = splitChainDefinition(chainDefinition);
//each token is specific to each filter.
//strip the name and extract any filter-specific config between brackets [ ]
for (String token : filterTokens) {
String[] nameConfigPair = toNameConfigPair(token);
//now we have the filter name, path and (possibly null) path-specific config. Let's apply them:
addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}
...
public void addToChain(String chainName, String filterName) {
addToChain(chainName, filterName, null);
}
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
...
Filter filter = getFilter(filterName);
...
applyChainConfig(chainName, filter, chainSpecificFilterConfig);
NamedFilterList chain = ensureChain(chainName);
chain.add(filter);
}
...
protected NamedFilterList ensureChain(String chainName) {
NamedFilterList chain = getChain(chainName);
if (chain == null) {
chain = new SimpleNamedFilterList(chainName);
this.filterChains.put(chainName, chain);
}
return chain;
}
...
}
综上, 初始化 (构建 ShiroFilter
) 时, Shiro 会以 DefaultFilter.invalidRequest
来构建一个 /** 的过滤器链, 并根据开发者指定的 filterChainDefinitionMap 来构建用户自定义的过滤器链.
SpringShiroFilter
最终, 通过 ShiroFilterFactoryBean
的 createInstance: new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver)
securityManager 和 配置了 FilterChainManager
的 FilterCharinResolver
“联系” 在了一起.
最后我们再来看看 ShiroFilter
:
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
...
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
final Subject subject = createSubject(request, response);
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
}
...
}
...
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
...
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
return origChain;
}
FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
chain = resolved;
} else {
...
}
return chain;
}
}
可以看到在 ShiroFilter
的核心过滤方法中, 整个流程职级上就是通过 FilterChainResolver
获取到匹配的 FilterChain
然后 doFilter. 在 FilterChainResolver
的 getChain 中, 也主要是根据 requestURI 去获取匹配的过滤器链:
public class PathMatchingFilterChainResolver implements FilterChainResolver {
...
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
final String requestURI = getPathWithinApplication(request);
final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);
//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
...
return filterChainManager.proxy(originalChain, pathPattern);
} else {
// in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
// but the pathPattern match "/resource/menus" can not match "resource/menus/"
// user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
pathPattern = removeTrailingSlash(pathPattern);
if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
...
return filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
}
}
}
return null;
}
...
}
综上, 这就是 ShiroFilter
的主要内容和源码摘录.
PART#3: Realm
还记得我们在定义 AuthRealm
时, 继承了 AuthorizingRealm
抽象类, 并实现了两个抽象方法:
⑴ AuthorizingRealm
`protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)`
⑵ AuthenticatingRealm
`protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException`
可以对应下图看其中的关系:
还有一个问题: DefaultWebSecurityManager
的 Realm
是如何工作的?
@Configuration
public class ShiroConfiguration {
...
@Bean("defaultWebSecurityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("authRealm") Realm authRealm) {
final DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(authRealm);
return defaultWebSecurityManager;
}
...
}
↑ 在 ShiroConfiguration, 由开发者定义的 Realm
被设值到了 DefaultWebSecurityManager
(通过 DefaultWebSecurityManager#setRealm(Realm)), 所以, 我们从 DefaultWebSecurityManager
开始 ↓
通过跟踪源码可以简单归纳下 Realm
的调用链:
# 关键层级结构
DefaultWebSecurityManager
extends AuthorizingSecurityManager
extends AuthenticationSecurityManager
extends RealmSecurityManager
extends CachingSecurityManager
implements SecurityManager
从调用链可以看出 DefaulutWebSecurityManager#setRealm 实际上是调用超类的了 RealmSecurityManager
的 setRealm 方法, 在方法体最后会执行 afterRealmSet 做一些补充设定, 关键就是在这个 afterRealmSet, 由于 “静态类型” 是 DefaultWebSecurityManager
, 所以实际上会:
- 优先调用
AuthorizingSecurityManager
的 afterRealmSet (从下往上, 实际上是 “动态分配” 的过程), - 然后是
AuthenticatingSecurityManager
的 afterRealmSet (通过AuthorizingSecurityManager
的 afterRealmSet 方法体中的 super.afterRealmsSet()), - 最后才是
RealmSecurityManager
的 afterRealmSet:
public abstract class RealmSecurityManager extends CachingSecurityManager {
...
public void setRealm(Realm realm) {
...
Collection<Realm> realms = new ArrayList<Realm>(1);
realms.add(realm);
setRealms(realms);
}
public void setRealms(Collection<Realm> realms) {
...
this.realms = realms;
afterRealmsSet();
}
...
}
AuthorizingSecurityManager & Authorizer
AuthorizingSecurityManager
是 Shiro 提供的 SecurityManager
的实现, 负责将所有的授权操作 “委派” 给一个 Authorizer
的实例 (ModularRealmAuthorizer
).
好了, 来看看 AuthorizingSecurityManager
的 afterRealmSet…
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
...
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
...
protected void afterRealmsSet() {
super.afterRealmsSet();
if (this.authorizer instanceof ModularRealmAuthorizer) {
((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
}
}
...
}
↑ 在 AuthorizingSecurityManager#afterRealmSet, 我们看到 AuthorizingSecurityManager
持有了一个名为 authorizer 的 ModularRealmAuthorizer
, 透过 ModularRealmAuthorizer
的层次结构我们第一次认识了 Authorizer
:
开发者设置的 Realm
(在本文中, 也就是 AuthRealm
) 被置入到了 ModularRealmAuthorizer
的 realms 属性中, 由名称可以推断, 这是一个 “授权器”. 这个授权器实现了接口 Authorizer
, 后者定义了 Shiro 执行框架授权操作的规范:
org.apache.shiro.authz.Authorizer
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, String)
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, Permission)
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, String...)
org.apache.shiro.authz.Authorizer#isPermitted(PrincipalCollection, List<Permission>)
org.apache.shiro.authz.Authorizer#isPermittedAll(PrincipalCollection, String...)
org.apache.shiro.authz.Authorizer#isPermittedAll(PrincipalCollection, Collection<Permission>)
org.apache.shiro.authz.Authorizer#checkPermission(PrincipalCollection, String)
org.apache.shiro.authz.Authorizer#checkPermission(PrincipalCollection, Permission)
org.apache.shiro.authz.Authorizer#checkPermissions(PrincipalCollection, String...)
org.apache.shiro.authz.Authorizer#checkPermissions(PrincipalCollection, Collection<Permission>)
org.apache.shiro.authz.Authorizer#hasRole
org.apache.shiro.authz.Authorizer#hasRoles
org.apache.shiro.authz.Authorizer#hasAllRoles
org.apache.shiro.authz.Authorizer#checkRole
org.apache.shiro.authz.Authorizer#checkRoles(PrincipalCollection, Collection<String>)
org.apache.shiro.authz.Authorizer#checkRoles(PrincipalCollection, String...)
↑ 这个 Principal 参数是能唯一标识一个用户的标记, 如数据库主键, 用户名等. 而这个 Principal 在运行期间的值是通过开发者配置的 Realm
获取的.
在 ModularRealmAuthorizer
的 setRealms
方法中, 可以看到 applyPermissionResolverToRealms()
和 applyRolePermissionResolverToRealms()
两个调用, 其中:
applyPermissionResolverToRealms()
会把ModularRealmAuthorizer
持有的permissionResolver
设置给Realms
(如果当前 Realm 实现了PermissionResolverAware
接口). 如果没有指定, 就会使用 Realm 默认的解析器, 对于AuthorizingRealm
来说, 就是WildcardPermissionResolver
applyRolePermissionResolverToRealms()
会把ModularRealmAuthorizer
持有的rolePermissionResolver
设置给Realms
(如果当前 Realm 实现了RolePermissionResolverAware
接口)
public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
...
protected Collection<Realm> realms;
protected PermissionResolver permissionResolver;
protected RolePermissionResolver rolePermissionResolver;
...
public void setRealms(Collection<Realm> realms) {
this.realms = realms;
applyPermissionResolverToRealms();
applyRolePermissionResolverToRealms();
}
...
/**
* Sets the internal getPermissionResolver on any internal configured Realms that implement the PermissionResolverAware interface.
* This method is called after setting a permissionResolver on this ModularRealmAuthorizer via the setPermissionResolver method.
* It is also called after setting one or more realms via the setRealms method to allow these newly available realms to be given the PermissionResolver already in use.
*/
protected void applyPermissionResolverToRealms() {
PermissionResolver resolver = getPermissionResolver();
Collection<Realm> realms = getRealms();
if (resolver != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof PermissionResolverAware) {
((PermissionResolverAware) realm).setPermissionResolver(resolver);
}
}
}
}
...
/**
* Sets the internal getRolePermissionResolver on any internal configured Realms that implement the RolePermissionResolverAware interface.
* This method is called after setting a rolePermissionResolver on this ModularRealmAuthorizer via the setRolePermissionResolver method.
* It is also called after setting one or more realms via the setRealms method to allow these newly available realms to be given the RolePermissionResolver already in use.
*/
protected void applyRolePermissionResolverToRealms() {
RolePermissionResolver resolver = getRolePermissionResolver();
Collection<Realm> realms = getRealms();
if (resolver != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof RolePermissionResolverAware) {
((RolePermissionResolverAware) realm).setRolePermissionResolver(resolver);
}
}
}
}
...
}
↑ 从上面可以看出, ModularRealmAuthorizer
实际上也是调用的 realm 的实现于 Authorizer
的授权方法. 而透过本节开头的层级图我们也可以看到 AuthRealm 的父类 AuthorizingRealm
也实现了 Authorizer
接口, 自然也继承了关于授权的 API.
最后一个问题, 授权操作是如何完成的? 我们接着看 AuthorizingRealm
:
public abstract class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
...
public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
super();
if (cacheManager != null) setCacheManager(cacheManager);
if (matcher != null) setCredentialsMatcher(matcher);
this.authorizationCachingEnabled = true;
this.permissionResolver = new WildcardPermissionResolver();
int instanceNumber = INSTANCE_COUNT.getAndIncrement();
this.authorizationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
if (instanceNumber > 0) {
this.authorizationCacheName = this.authorizationCacheName + "." + instanceNumber;
}
}
...
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission); // !
return isPermitted(principals, p);
}
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info); // !
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
...
AuthorizationInfo info = null;
...
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
...
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
...
}
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) {
...
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
protected Collection<Permission> getPermissions(AuthorizationInfo info) {
Set<Permission> permissions = new HashSet<Permission>();
if (info != null) {
Collection<Permission> perms = info.getObjectPermissions();
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
perms = resolvePermissions(info.getStringPermissions());
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
perms = resolveRolePermissions(info.getRoles());
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
}
if (permissions.isEmpty()) {
return Collections.emptySet();
} else {
return Collections.unmodifiableSet(permissions);
}
}
}
上面这段代码摘录简要显示了 Shiro 如何从 realm 中获取授权信息并执行授权验证: 先用默认的 permissionResolver (WildcardPermissionResolver
) 解析出 Permission
(通过 PermissionResolver
), 再调用 realm 的 doGetAuthorizationInfo 获取 AuthorizationInfo
从其中获取 Subject 的权限信息 (借助 RolePermissionResolver
) 最后判断目标资源是否在 realm 的权限范围内 (org.apache.shiro.authz.Permission#implies
).
其中, doGetAuthorizationInfo 正式我们在 AuthRealm 中实现的抽象方法.
总结一下, AuthorizingSecurityManager
提供了授权的支持, 该支持是通过将授权操作委派给 ModularRealmAuthorizer
来完成的 (AuthorizingSecurityManager
实现了 Authorizer
接口, 其对于接口的方法的实现内部本质上是调用了同样实现了 Authorizer
接口的 ModularRealmAuthorizer
的方法), 如:
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
return this.authorizer.isPermitted(principals, permissionString);
}
而 ModularRealmAuthorizer
持有 realms, permissionResolver 以及 rolePermissionResolver, 在 realms 被设置后会被置入 permissionResolver (如果当前 realm 实现了 PermissionResolverAware
) 和 rolePermissionResolver (如果当前 realm 实现了 RolePermissionResolverAware
).
同时由于 ModularRealmAuthorizer
实现了 Authorizer
接口, 它其中也实现了关于授权的全部 API, 但是其方法内部的实现是轮询持有的 realms, 对于实现了 Authorizer
接口的 realm 调用授权的 API, 如:
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
AuthenticatingSecurityManager & Authenticator
看完了授权的操作, 我们再来看看认证逻辑是如何进行的…
与 AuthorizingSecurityManager
同样的, AuthenticationSecurityManager
也委派了 Authenticator
(ModularRealmAuthenticator
) 用于执行认证操作.
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
...
public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
...
protected void afterRealmsSet() {
super.afterRealmsSet();
if (this.authenticator instanceof ModularRealmAuthenticator) {
((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
}
}
...
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
}
↑ 这个 authenticator 默认实际是 ModularRealmAuthenticator
, AuthrnticationSecurityManager
也同样地, 把 realms 设置给了 ModularRealmAuthenticator
. 而这个实际的认证调用 authenticate, 通过源码我们可以看到是调用了 AbstractAuthenticator
的方法. ↓
来看看 AbstractAuthenticator
的 authenticate 究竟做了什么:
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
...
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
...
AuthenticationInfo info;
try {
info = doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this " +
"Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
} catch (Throwable t) {
...
try {
notifyFailure(token, ae);
} catch (Throwable t2) {
...
}
throw ae;
}
...
notifySuccess(token, info);
return info;
}
}
↑ 可以看到真正的认证调用实际上是调用了 AbstractAuthenticator
的抽象方法 doAuthenticator, 而该方法在 ModularRealmAuthenticator
实现:
public class ModularRealmAuthenticator extends AbstractAuthenticator {
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
...
AuthenticationInfo info = realm.getAuthenticationInfo(token);
...
return info;
}
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
...
for (Realm realm : realms) {
try {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
} catch (ShortCircuitIterationException shortCircuitSignal) {
// Break from continuing with subsequnet realms on receiving
// short circuit signal from strategy
break;
}
if (realm.supports(token)) {
...
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
...
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
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;
}
}
↑ 从上面源码我们可以得出, AuthentticatingSecurityManager
委派 ModularRealmAuthenticator
执行认证, 同时也把 realms 传入 ModularRealmAuthenticator
. 而 ModularRealmAuthenticator
内部, 调用 realm 的 doGetAuthenticationInfo 获取 AuthenticationInfo
, 并最终在 ModularRealmAuthenticator
的超类 AbstractAuthenticator
的 authenticate 中, 完成认证.
Conclusion
至此, 本文介绍了 Shiro 的认证和授权逻辑. 现在我们运行 demo-shiro-quickstart 项目应该可以成功访问到唯一的一个接口. 但是也可以看到 AuthRealm 中的两个实现方法都没被调用.
下一篇, 我们将首先介绍如何触发 AuthRealm 的 doGetAuthorizationInfo.
关于 Shiro 对 Subject 的管理以及其工作机制, 留到后面的文章说明…