Apache Shiro Realm 学习记录1

  最近几天在学习Apache Shiro......看了一些大神们的教程.....感觉收获不少.....但是毕竟教程也只是指引一下方向....即使是精辟教程,仍然有很多东西都没有说明....所以自己也稍微研究了一下...记录了一下我的研究发现....教程点这里

  这篇教程的最后提到了自己去写Realm.....然后给出了4个方法.....但是并没有怎么详细说明.....我想说说我的理解.....(我的理解可能会有很多错误)



  我想先说说登陆验证的大致流程....大致......

  从用户那里收集完用户名密码以后我们会调用subject.login(token)这个方法去登陆.....Subject是一个接口,没有定义login的具体实现.....Shiro里只有一个类实现了这个接口,是DelegatingSubject这个类.这个类里的方法login方法如下:

 1    public void login(AuthenticationToken token) throws AuthenticationException {
 2         clearRunAsIdentitiesInternal();
 3         Subject subject = securityManager.login(this, token);
 4
 5         PrincipalCollection principals;
 6
 7         String host = null;
 8
 9         if (subject instanceof DelegatingSubject) {
10             DelegatingSubject delegating = (DelegatingSubject) subject;
11             //we have to do this in case there are assumed identities - we don‘t want to lose the ‘real‘ principals:
12             principals = delegating.principals;
13             host = delegating.host;
14         } else {
15             principals = subject.getPrincipals();
16         }
17
18         if (principals == null || principals.isEmpty()) {
19             String msg = "Principals returned from securityManager.login( token ) returned a null or " +
20                     "empty value.  This value must be non null and populated with one or more elements.";
21             throw new IllegalStateException(msg);
22         }
23         this.principals = principals;
24         this.authenticated = true;
25         if (token instanceof HostAuthenticationToken) {
26             host = ((HostAuthenticationToken) token).getHost();
27         }
28         if (host != null) {
29             this.host = host;
30         }
31         Session session = subject.getSession(false);
32         if (session != null) {
33             this.session = decorate(session);
34         } else {
35             this.session = null;
36         }
37     }

代码各种复杂=.= ..............我也看不懂....但是我看到第三行,明白了subject其实也是让securityManager来执行login操作的......那我们去看看securityManager吧......

SecurityManager也是一个接口,各种继承,实现其他接口......还好实现类只有一个DefaultSecurityManager类...并且login方法直到这个类才被实现....

 1     public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
 2         AuthenticationInfo info;
 3         try {
 4             info = authenticate(token);
 5         } catch (AuthenticationException ae) {
 6             try {
 7                 onFailedLogin(token, ae, subject);
 8             } catch (Exception e) {
 9                 if (log.isInfoEnabled()) {
10                     log.info("onFailedLogin method threw an " +
11                             "exception.  Logging and propagating original AuthenticationException.", e);
12                 }
13             }
14             throw ae; //propagate
15         }
16
17         Subject loggedIn = createSubject(token, info, subject);
18
19         onSuccessfulLogin(token, info, loggedIn);
20
21         return loggedIn;
22     }

我觉得这里最重要的就是第四行的authenticate方法.......

这个方法是在DefaultSecurityManager的N层父类AuthenticatingSecurityManager里实现的....

1     public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
2         return this.authenticator.authenticate(token);
3     }

这里又可以看出SecurityManager的login方法其实也是委托给认证器authenticator调用authenticate(token)方法来实现的.

public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {

    /**
     * The internal <code>Authenticator</code> delegate instance that this SecurityManager instance will use
     * to perform all authentication operations.
     */
    private Authenticator authenticator;

    /**
     * Default no-arg constructor that initializes its internal
     * <code>authenticator</code> instance to a
     * {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
     */
    public AuthenticatingSecurityManager() {
        super();
        this.authenticator = new ModularRealmAuthenticator();
    }
    ..................
}

认证器authenticator的默认实现是ModularRealmAuthenticator...所以我们再来看看ModularRealmAuthenticator..........

ModularRealmAuthenticator的authenticate方法是继承自父类AbstractAuthenticator的..这个方法还是final的...

 1     public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
 2
 3         if (token == null) {
 4             throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
 5         }
 6
 7         log.trace("Authentication attempt received for token [{}]", token);
 8
 9         AuthenticationInfo info;
10         try {
11             info = doAuthenticate(token);
12             if (info == null) {
13                 String msg = "No account information found for authentication token [" + token + "] by this " +
14                         "Authenticator instance.  Please check that it is configured correctly.";
15                 throw new AuthenticationException(msg);
16             }
17         } catch (Throwable t) {
18             AuthenticationException ae = null;
19             if (t instanceof AuthenticationException) {
20                 ae = (AuthenticationException) t;
21             }
22             if (ae == null) {
23                 //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
24                 //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
25                 String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
26                         "error? (Typical or expected login exceptions should extend from AuthenticationException).";
27                 ae = new AuthenticationException(msg, t);
28             }
29             try {
30                 notifyFailure(token, ae);
31             } catch (Throwable t2) {
32                 if (log.isWarnEnabled()) {
33                     String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
34                             "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
35                             "and propagating original AuthenticationException instead...";
36                     log.warn(msg, t2);
37                 }
38             }
39
40
41             throw ae;
42         }
43
44         log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
45
46         notifySuccess(token, info);
47
48         return info;
49     }

从中我们可以看出比较重要的是第11行 info = doAuthenticate(token);

doAuthenticate(token)方法在ModularRealmAuthenticator之中被override过...

1     protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
2         assertRealmsConfigured();
3         Collection<Realm> realms = getRealms();
4         if (realms.size() == 1) {
5             return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
6         } else {
7             return doMultiRealmAuthentication(realms, authenticationToken);
8         }
9     }

这里还是比较明显的,根据定义的Realm数量来决定是调用doSingleRealmAuthentication方法还是调用doMultiRealmAuthentication方法...

我们来看看doMultiRealmAuthentication方法里到底做了些什么呢..这个方法是定义在ModularRealmAuthenticator里的...

 1     protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
 2
 3         AuthenticationStrategy strategy = getAuthenticationStrategy();
 4
 5         AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
 6
 7         if (log.isTraceEnabled()) {
 8             log.trace("Iterating through {} realms for PAM authentication", realms.size());
 9         }
10
11         for (Realm realm : realms) {
12
13             aggregate = strategy.beforeAttempt(realm, token, aggregate);
14
15             if (realm.supports(token)) {
16
17                 log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
18
19                 AuthenticationInfo info = null;
20                 Throwable t = null;
21                 try {
22                     info = realm.getAuthenticationInfo(token);
23                 } catch (Throwable throwable) {
24                     t = throwable;
25                     if (log.isDebugEnabled()) {
26                         String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
27                         log.debug(msg, t);
28                     }
29                 }
30
31                 aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
32
33             } else {
34                 log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
35             }
36         }
37
38         aggregate = strategy.afterAllAttempts(token, aggregate);
39
40         return aggregate;
41     }

虽然很多看不明白....但是我大致能看懂:

Realm的认证流程是要根据AuthenticationStrategy来决定的....

在调用Realm之前先执行strategy.beforeAllAttempts(realms, token);

然后再进入 for (Realm realm : realms),就是准备去调用每个realm来认证..不过每个Realm认证之前还要调用aggregate = strategy.beforeAttempt(realm, token, aggregate);

然后得到单个Realm的认证结果 info = realm.getAuthenticationInfo(token);

再调用aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);这里可能会把info的认证信息和aggregate合并,就是把principle合并....不过还是要看具体的strategy的.

最后再调用 aggregate = strategy.afterAllAttempts(token, aggregate);

也就是说strategy.beforeAllAttempts(realms, token);调用1次,然后根据realm的数量调用N次strategy.beforeAttempt(realm, token, aggregate);N次realm.getAuthenticationInfo(token);N次 strategy.afterAttempt(realm, token, info, aggregate, t);再调用1次strategy.afterAllAttempts(token, aggregate);



我觉得看到这里我们可以再来看看Shiro已经定义的三种Strategy了...教程里提到过:

FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

这3种策略都继承自AbstractAuthenticationStrategy类..这个抽象类里定义了前面提到的2个after方法和2个before方法还有1个merge方法...

1     protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
2         if( aggregate instanceof MergableAuthenticationInfo ) {
3             ((MergableAuthenticationInfo)aggregate).merge(info);
4             return aggregate;
5         } else {
6             throw new IllegalArgumentException( "Attempt to merge authentication info from multiple realms, but aggregate " +
7                       "AuthenticationInfo is not of type MergableAuthenticationInfo." );
8         }
9     }

merge方法会调用AuthenticationInfo的merge方法....大致过程是:

  public void merge(AuthenticationInfo info) {
        if (info == null || info.getPrincipals() == null || info.getPrincipals().isEmpty()) {
            return;
        }

        if (this.principals == null) {
            this.principals = info.getPrincipals();
        } else {
            if (!(this.principals instanceof MutablePrincipalCollection)) {
                this.principals = new SimplePrincipalCollection(this.principals);
            }
            ((MutablePrincipalCollection) this.principals).addAll(info.getPrincipals());
        }
    ......................
}

大致意思就是把info的principals合并到aggregate的principals上去....

AbstractAuthenticationStrategy的merge方法主要是在afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)里被调用,因为经过一个Realm的认证以后会得到info对象...这个时候不同的认证策略就需要考虑是不是要把当前得到的info合并到已经有的前面几个Realm积累下来的aggregateInfo里去了...

举例:

AllSuccessfulStrategy类

 1     public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
 2         if (!realm.supports(token)) {
 3             String msg = "Realm [" + realm + "] of type [" + realm.getClass().getName() + "] does not support " +
 4                     " the submitted AuthenticationToken [" + token + "].  The [" + getClass().getName() +
 5                     "] implementation requires all configured realm(s) to support and be able to process the submitted " +
 6                     "AuthenticationToken.";
 7             throw new UnsupportedTokenException(msg);
 8         }
 9
10         return info;
11     }

第2行,如果realm不支持token,显然这个realm是认证失败的...而AllSuccessfulStrategy策略需要所有Realm都认证成功才行...果断抛出异常...

 1     public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info, AuthenticationInfo aggregate, Throwable t)
 2             throws AuthenticationException {
 3         if (t != null) {
 4             if (t instanceof AuthenticationException) {
 5                 //propagate:
 6                 throw ((AuthenticationException) t);
 7             } else {
 8                 String msg = "Unable to acquire account data from realm [" + realm + "].  The [" +
 9                         getClass().getName() + " implementation requires all configured realm(s) to operate successfully " +
10                         "for a successful authentication.";
11                 throw new AuthenticationException(msg, t);
12             }
13         }
14         if (info == null) {
15             String msg = "Realm [" + realm + "] could not find any associated account data for the submitted " +
16                     "AuthenticationToken [" + token + "].  The [" + getClass().getName() + "] implementation requires " +
17                     "all configured realm(s) to acquire valid account data for a submitted token during the " +
18                     "log-in process.";
19             throw new UnknownAccountException(msg);
20         }
21
22         log.debug("Account successfully authenticated using realm [{}]", realm);
23
24         // If non-null account is returned, then the realm was able to authenticate the
25         // user - so merge the account with any accumulated before:
26         merge(info, aggregate);
27
28         return aggregate;
29     }

Throwable t, t是从Realm里抛出来的,如果Realm抛出了异常,不管什么原因,认证肯定是失败了...所以Strategy也要抛出异常,即认证失败了......info = null的道理也是一样的....

AtLeastOneSuccessfulStrategy类:

 1     public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
 2         //we know if one or more were able to succesfully authenticate if the aggregated account object does not
 3         //contain null or empty data:
 4         if (aggregate == null || CollectionUtils.isEmpty(aggregate.getPrincipals())) {
 5             throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +
 6                     "could not be authenticated by any configured realms.  Please ensure that at least one realm can " +
 7                     "authenticate these tokens.");
 8         }
 9
10         return aggregate;
11     }

所有Realm的认证都执行完毕之后,如果AuthenticationInfo 的合并结果aggregate还是null或者空的话那么说明所有Realm的认证都失败的...那么就应该抛出异常,说明认证失败了....

FirstSuccessfulStrategy类:

 1 public class FirstSuccessfulStrategy extends AbstractAuthenticationStrategy {
 2
 3     /**
 4      * Returns {@code null} immediately, relying on this class‘s {@link #merge merge} implementation to return
 5      * only the first {@code info} object it encounters, ignoring all subsequent ones.
 6      */
 7     public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
 8         return null;
 9     }
10
11     /**
12      * Returns the specified {@code aggregate} instance if is non null and valid (that is, has principals and they are
13      * not empty) immediately, or, if it is null or not valid, the {@code info} argument is returned instead.
14      * <p/>
15      * This logic ensures that the first valid info encountered is the one retained and all subsequent ones are ignored,
16      * since this strategy mandates that only the info from the first successfully authenticated realm be used.
17      */
18     protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
19         if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) {
20             return aggregate;
21         }
22         return info != null ? info : aggregate;
23     }
24 }

为什么要override beforeAllAttempts方法返回null我也不懂....父类是返回new SimpleAuthenticationInfo();.....

override merge方法是因为FirstSuccessfulStrategy策略只返回第一个验证成功的Realm的AuthenticationInfo ...

如果aggregate不是空的或者null,说明前面的Realm有成功过,那么根据策略,这个时候不应该把本次realm得到的info合并进去.....而是直接返回前面验证成功的AuthenticationInfo ......

时间: 2024-11-08 18:33:55

Apache Shiro Realm 学习记录1的相关文章

org.apache.shiro.realm.AuthorizingRealm - No cache or cacheManager properties have been set. Authorization cache cannot be obtained.

项目中用spring shiro来处理权限的问题,但是启动的时候会打印如下日志 org.apache.shiro.realm.AuthorizingRealm - No cache or cacheManager properties have been set. Authorization cache cannot be obtained. 检查了basicRelam配置如下 <bean id="basicRealm" class="com.ebon.platform

Apache Shiro 学习记录2

写完上篇随笔以后(链接).....我也想自己尝试一下写一个Strategy.....Shiro自带了3个Strategy,教程(链接)里作者也给了2个.....我想写个都不一样的策略.....看来看去....决定写个LastSuccessfulStrategy好了...顾名思义就是返回最后一个Realm验证成功的AuthenticationInfo的信息... 1 package com.github.zhangkaitao.shiro.chapter2.authenticator.strate

Shiro学习总结(3)——Apache Shiro身份认证

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明

Apache Shiro学习笔记(五)Web集成使用JdbcRealm

鲁春利的工作笔记,好记性不如烂笔头 http://shiro.apache.org/web-features.html 前面的示例都是把用户名或密码以及权限信息放在ini文件中,但实际的Web项目开发过程中,实际上一般是user<--->role.role<-->permission进行关联关系的配置,每次登录时加载其拥有的权限或者是每次访问时再判断其权限. jdbc-shiro.ini [main] #默认是/login.jsp authc.loginUrl=/login rol

Apache Shiro学习笔记(三)用户授权自定义Permission

鲁春利的工作笔记,好记性不如烂笔头 Shiro配置文件(shiro-customize-permission.ini) [main] myRealmA=com.invicme.apps.shiro.permission.MyRealmOne myPermissionResolver=com.invicme.apps.shiro.permission.MyPermissionResolver securityManager.authorizer.permissionResolver = $myPe

Apache Shiro学习笔记(二)身份验证

鲁春利的工作笔记,好记性不如烂笔头 身份验证,即在应用中谁能证明他就是他本人,应用系统中一般通过用户名/密码来证明.在 shiro 中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份:    principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号.    credentials:证明/凭

Shiro学习总结(2)——Apache Shiro快速入门教程

第一部分 什么是Apache Shiro 1.什么是 apache shiro : Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理 如同 spring security 一样都是是一个权限安全框架,但是与Spring Security相比,在于他使用了和比较简洁易懂的认证和授权方式. 2.Apache Shiro 的三大核心组件: 1.Subject :当前用户的操作 2.SecurityManager:用于管理所有的Subject 3.R

shiro框架学习-3- Shiro内置realm

1. shiro默认自带的realm和常见使用方法 realm作用:Shiro 从 Realm 获取安全数据 默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm 两个概念 principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等 credential:凭证, 一般就是密码 所以一般我们说 principal + credential 就账号 + 密码 开发中,往往是自定义realm , 即集成 Authorizing

SpringMVC+Apache Shiro+JPA(hibernate)案例教学(二)基于SpringMVC+Shiro的用户登录权限验证

序: 在上一篇中,咱们已经对于项目已经做了基本的配置,这一篇文章开始学习Shiro如何对登录进行验证. 教学: 一.Shiro配置的简要说明. 有心人可能注意到了,在上一章的applicationContext.xml配置文件中,包含以下配置. <!-- 項目自定义的Realm --> <bean id="shiroDbRealm" class="org.shiro.demo.service.realm.ShiroDbRealm" ><