`
suichangkele
  • 浏览: 200394 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

shiro学习29-实现rememberMe功能-RememberMeManager

阅读更多

这个接口是用来实现rememberme的功能的。他的实现类为AbstractRememberMeManager,是一个抽象类,最终的继承类为cookieRememberMeManager,即将信息发送到cookie中。

先看一下defaultWebSecurityManager的构造方法:

 

public DefaultWebSecurityManager() {
        super();
        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
        this.sessionMode = HTTP_SESSION_MODE;
        setSubjectFactory(new DefaultWebSubjectFactory());
        setRememberMeManager(new CookieRememberMeManager());//初始化一个cookieRememberMeManager。
        setSessionManager(new ServletContainerSessionManager());
 }

 

 

 

我们从AbstractRememberMeManager的源码开始看。

这个类中有两个重要的属性:

1Serializer<PrincipalCollection> serializer; //用来将subjectprincipalCollection转化为byte[] 数组的。使用的实现类是DefaultSerializer,里面的逻辑是使用标准的jvm序列化方式来实现的。

2、CipherService cipherService;这个属性用来将序列化的字符串数组进行加密的,我们简单的看一下他的javadoc:他通过一种加密方法(Cipher)将输入的值转化为一个不可读的模式(加密),但是这个转化过程必须使用一个key。这个转化过程是可逆的,但是在逆转化(解密)的时候必须也要知道这个key才可以。CipherService类可以实现加密和解密两个功能。

Cipher分为对称加密和非对称加密,对称加密指的是对于加密和解密的key是一样的,非对称加密是指加密和解密的key是不一样的。(对于这些个加密算法我也没有研究)

因为我们最终使用的CipherServiceAesCipherService,他是一个对称加密的,所以使用的key只有一个,在AbstractRememberMeManager中统一使用byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");作为加密和解密的key

在构造方法有一个方法:setCipherKey,就是将byte[]设置为encryptionCipherKeydecryptionCipherKey。所以这样就可能早成一个问题,我们的最终实现是将pricipalCollection作为cookievalue发送到cookie中取得,如果有人知道我们使用的是shiro,并且知道我们使用的是对称的AesCipherService进行对称加密解密并且使用的keyBase64.decode("kPH+bIxk5D2deZiIxcaaaA=="),那么就可以通过解密来获得之前设置的principalCollection,在AbstracRememberMeManagerjavadoc中也提及了,如果我们的principalCollection对于用户是非常敏感的,那么就要改变加密和解密的key,这样就不会别别人发现了。

 

方法:boolean isRememberMe(AuthenticationToken token),表示当前的token参数是否让记住用户,我们使用的usernamepasswordtoken就是一个AuthenticationToken,但是他的isRememberMe默认返回值是false,所以如果要想记住用户在使用suernamePasswordToken时必须设置setRememberMe(true),否则不会发送cookie到浏览器。

 

方法onSuccessLogin方法,在登陆校验成功后调用,他的做法很简单,先把之前的发送的同样的cookie删掉,然后再重新发送一个cookie,这样做的目的有两个,一个是更新发送的时间,因为cookie有时间限制,第二个是这一次的登录和之前的登录可能不是一个用户。

 

public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
   forgetIdentity(subject);//删掉之前的cookie,
    if (isRememberMe(token)) {
        rememberIdentity(subject, token, info);//重新发送cookie。
    } else {xxx}
}

 

 

 

再看看他是如何发送cookie的:先获得principalCollection,然后加密,然后将加密后的byte[]进行编码发送到cookie中,在CookieRememberMeManager中的rememberSerializedIdentity方法代码如下:

String base64 = Base64.encodeToString(serialized);

Cookie template = getCookie(); //这个是在初始化的时候就生成好的,这里的cookieshiro中重新定义的,并不是http中的cookie,仅用来传值,

Cookie cookie = new SimpleCookie(template);//将魔板中的值复制一份生成一个新的cookie

cookie.setValue(base64);

cookie.saveTo(request, response);:将上面已经赋好值的cookie通过http头保存在response中,起到一个http  cookie的作用。

我们看一下通过这个代码之后生成的cookie的属性:

 Name=rememberme

maxAge=60*60*24*365 表示一年。

version=-1

httpOnly=true

value=xx//就是将加密后的principalCollectionbase64格式化的字符串。

 

我们看一下SimpleCookiesaveTo方法将上面的simpleCookie生成的字符串,这个是用debug方法取得的:

rememberMe=J/s6gHl+YskK93q8+h00uaMrsiDCScq69EWtk/nMAMdoc+ygEcdTwHCwS9tO82fqrTIXpnn1a+XWvzkfauyMkbdiKG4EarZIm0TQgcwFCowKU6xqUjt7r0h3fsFwkQL3N/OGbfyNp1iVw+mPGR0Ef9Y1PpNCdi2bojFmqffYLGdEv9bso+/nPc1P3/HLfyJdiy4LBsPqtUiXkkk2Em44D9RNTS79g3AhToXRvkhO5QA756f+DBCFZgfeztN6QUgK1PS7dvJS+LMOlvtoMHSYRQCNmSq1VqAqU5vXLnN4xipnuCQWXzUSx2DuZIIcDrJR+itH/zkoh9s/Yy5PpAIq+K6tmVzRqVm4tixBluChaJeWgGBQNcBxb/9nlU30w533qraqGYqLidnYTzLSfiejWOlCloex5uFEpiWY4yOrK0M9K+Xm7JsglPaDBr6Up5Kk6t/prUDdJZ+MUTnP/zY/ppeBLxrK7J1dLZ6l39XoTqM=; Path=/shiro; Max-Age=31536000; Expires=Sat, 04-Feb-2017 02:39:08 GMT; HttpOnly

注意这里的path,因为我的contextPathshiro,所以我们得出结论,这个cookie在访问任何资源时都会带着。有效期是一年,namerememberMevalue是加密后的principalCollection

然后将这个字符串作为一个头信息添加到response中:

response.addHeader(COOKIE_HEADER_NAME, headerValue);

 

还有个重要的地方:forgetIdentity的原理就是删掉,删除的步骤和上面的一样,只不过是将max-age设置为0,下面是删除时的字符串:

rememberMe=deleteMe; Path=/shiro; Max-Age=0; Expires=Thu, 04-Feb-2016 02:44:21 GMT

 

 

上面说的是登录成功之后的操作,如果登录校验失败呢,在AbstractRememberMeManager中有一个方法onFailedLogin,说的就是登录校验失败,里面调用的就是forgetIdentity,也即是如果登录校验失败,就会主动废弃掉原来的cookie(如果有的话)

  

现在我们理清了关于rememberme的操作原理了,但是还有一个问题,当用户第一次访问网站时,比如首页,我们如何得到这个cookie呢,这个和我们访问淘宝时一样的原理,不用登录他就知道我们原先的用户名。不用想shiro一定给我们弄好了这一套程序,直接调用subject.getPrincipal即可。里面的调用过程如下:

DefaultSecurityManager.createSubject在创建subejct的时候就会调用resolvrPrincipals方法,这个当前方法内部会先从当前的session中获得的subjectprincipal,如果没有找到再从cookie找,调用的方法是getRememberedInedtity,这个方法就会调用rememberMeManagergetRememberedPrincipals方法,这个方法最终调用的是getRememberedSerializedIdentity方法,就是从cookie中获取的,但是记住获取的是base编码后的,所以要用base64反编码然后再解码才是principalCollection

 

下面说一下用户在之前登陆过网站但是之后一段时间又第一次访问网站是的情形,此时没有session:我们知道shiro会在每一次访问时都会创建一个subject,所以我们就明白了,他在第一次创建的时候就会将cookie中的值(第一次不从session中获得,直到登陆才会接触session)经过处理后作为principalCollection传入到subject中,所以我们直接调用SecurityManager.getSubject.getPrincipal就可以了。但是一定记住,这个不是经过校验的用户,也就是不一定和真实的用户相对应,所以一定要注意不能通过简单的判断有没有用户名而决定是否登录,而是调用subject.isAuthenticaed方法。

注意:在取得principal的时候先从cookie中获得,再从session中获得,源码分析如下:

DefaultSecurityManager中的createSubject方法中被调用

 

protected SubjectContext resolvePrincipals(SubjectContext context) {
    PrincipalCollection principals = context.resolvePrincipals();
    if (CollectionUtils.isEmpty(principals)) {
        log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");
        principals = getRememberedIdentity(context);//如果没有principal,先从cookie中招。
        if (!CollectionUtils.isEmpty(principals)) {
            log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
                    "for subject construction by the SubjectFactory.");
            context.setPrincipals(principals);
        } else {
            log.trace("No remembered identity found.  Returning original context.");
        }
    }
    returncontext;
}

 对于principalCollection是先从cookie中找,如果没有的话就是null,如果有的话就作为构建subject的principalCollection。但是这个可能是不正确的值,所以在用户成功的登陆后会重新发送cookie,并将之前的cookie删掉,于是只要一登录成功,之后的每一次的访问的subject的构建的principalCollection都是来自于cookie。我们看一下subjectFactory创建subject的源码:

 

 

 public Subject createSubject(SubjectContext context) {
    if (!(context instanceof WebSubjectContext)) {
        return super.createSubject(context);
    }
    WebSubjectContext wsc = (WebSubjectContext) context;
    SecurityManager securityManager = wsc.resolveSecurityManager();
    Session session = wsc.resolveSession();
    boolean sessionEnabled = wsc.isSessionCreationEnabled();
    PrincipalCollection principals = wsc.resolvePrincipals();//principalCollection来自于subjectContext。
    boolean authenticated = wsc.resolveAuthenticated();//isAuthenticated也来自于subjectContext
    String host = wsc.resolveHost();
    ServletRequest request = wsc.resolveServletRequest();
    ServletResponse response = wsc.resolveServletResponse();
     return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
            request, response, securityManager);
}

 我们再看一下subjectContext的两个resolve方法

 

1、resolvePrincipals,

 

public PrincipalCollection resolvePrincipals() {
    PrincipalCollection principals = getPrincipals();//先从之前放置的值中查找,这个值是在创建subjectContext时从cookie中获得principalCollection。
    if (CollectionUtils.isEmpty(principals)) {
        //check to see if they were just authenticated:
        AuthenticationInfo info = getAuthenticationInfo();//之前没有防止的话通过判断校验信息,这个不会发生
        if (info != null) {
            principals = info.getPrincipals();
        }
    }
     if (CollectionUtils.isEmpty(principals)) {
        Subject subject = getSubject();
        if (subject != null) {
            principals = subject.getPrincipals();
        }
    }
//这个几乎没有意义,只有当禁用了cookie时才会有意义,但是那样的话就找不到session了,所以最终还是没有意义。分为两种情况说明
1、没有登录,如果cookie中没有principal的话,则一定没有登录,因为登陆后会重新发送cookie,既然没有到登录则session中的PRINCIPALS_SESSION_KEY值也是空,所以没有意义。
2、已经登录,则cookie中一定存在,这个判断条件就不满足。
于是我们得出这个结论,所有的principalCollection都是从cookie中得到的。不会用到session中的principalCollection。只有一种情况例外,在登陆成功后会重新建立subject,这个时候会用session中的值,然后发送cookie,不要忘了只登陆一次。
    if (CollectionUtils.isEmpty(principals)) {
        //try the session:
        Session session = resolveSession();
        if (session != null) {
            principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
        }
    }
    return principals;
}

 2、resolveAuthentication,从源码中可以总结校验状态几乎总是从session中获得。

public boolean resolveAuthenticated() {
    Boolean authc = getTypedValue(AUTHENTICATED, Boolean.class);//先从之前放置的值中取,但是通过debug发现除了在登陆成功后重新重建subject时会在subjectContext中放入AUTHENTICATED的值之外永远不会放置这个值,也就是说在登录访问之外的其他操作,AUTHENTICATED值都是从session中获得的。
    if (authc == null) {
        //see if there is an AuthenticationInfo object.  If so, the very presence of one indicates a successful
        //authentication attempt:
        AuthenticationInfo info = getAuthenticationInfo();
        authc = info != null;
    }
    if (!authc) {
        //fall back to a session check:
        Session session = resolveSession();
        if (session != null) {
            Boolean sessionAuthc = (Boolean) session.getAttribute(AUTHENTICATED_SESSION_KEY);
            authc = sessionAuthc != null && sessionAuthc;
        }
    }
    return authc;
}

 至此我们对subject的创建了如指掌了。

 

 

 

 

 

分享到:
评论

相关推荐

    shiro(shiro-all-1.8.0.jar)

    shiro(shiro-all-1.8.0.jar)

    shiro-attack-4.7.0-SNAPSHOT-all.zip

    例如,如果不正确地配置了RememberMe功能,攻击者可能能够篡改或伪造RememberMe令牌,从而获得长期的系统访问权限。此外,Shiro的Session管理如果处理不当,可能会导致会话劫持或会话固定攻击。因此,使用这样的工具...

    shiro-core-1.4.0-API文档-中文版.zip

    赠送jar包:shiro-core-1.4.0.jar; 赠送原API文档:shiro-core-1.4.0-javadoc.jar; 赠送源代码:shiro-core-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-core-1.4.0.pom; 包含翻译后的API文档:shiro-core...

    shiro-config-core-1.4.0-API文档-中文版.zip

    赠送jar包:shiro-config-core-1.4.0.jar; 赠送原API文档:shiro-config-core-1.4.0-javadoc.jar; 赠送源代码:shiro-config-core-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-config-core-1.4.0.pom; ...

    shiro-jar.zip

    shiro-all-1.7.1.jar,shiro-aspectj-1.7.1.jar,shiro-cache-1.7.1.jar,shiro-config-core-1.7.1.jar,shiro-config-ogdl-1.7.1.jar,shiro-core-1.7.1.jar,shiro-crypto-cipher-1.7.1.jar,shiro-crypto-core-1.7.1.jar...

    shiro-jar-1.7.0.zip相关资源包

    解决:升級1.7后附件...shiro-cas-1.7.0.jar shiro-core-1.7.0.jar shiro-ehcache-1.7.0.jar shiro-spring-1.7.0.jar shiro-web-1.7.0.jar CustomShiroFilterFactoryBean.java spring-context-shiro.xml 修改说明.txt

    shiro-ehcache-1.4.0-API文档-中文版.zip

    赠送jar包:shiro-ehcache-1.4.0.jar; 赠送原API文档:shiro-ehcache-1.4.0-javadoc.jar; 赠送源代码:shiro-ehcache-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-ehcache-1.4.0.pom; 包含翻译后的API文档...

    shiro-crypto-core-1.4.0-API文档-中文版.zip

    赠送jar包:shiro-crypto-core-1.4.0.jar; 赠送原API文档:shiro-crypto-core-1.4.0-javadoc.jar; 赠送源代码:shiro-crypto-core-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-crypto-core-1.4.0.pom; ...

    shiro-crypto-cipher-1.4.0-API文档-中文版.zip

    赠送jar包:shiro-crypto-cipher-1.4.0.jar; 赠送原API文档:shiro-crypto-cipher-1.4.0-javadoc.jar; 赠送源代码:shiro-crypto-cipher-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-crypto-cipher-1.4.0....

    shiro-crypto-core-1.4.0-API文档-中英对照版.zip

    赠送jar包:shiro-crypto-core-1.4.0.jar; 赠送原API文档:shiro-crypto-core-1.4.0-javadoc.jar; 赠送源代码:shiro-crypto-core-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-crypto-core-1.4.0.pom; ...

    shiro-config-core-1.4.0-API文档-中英对照版.zip

    赠送jar包:shiro-config-core-1.4.0.jar; 赠送原API文档:shiro-config-core-1.4.0-javadoc.jar; 赠送源代码:shiro-config-core-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-config-core-1.4.0.pom; ...

    shiro-config-ogdl-1.4.0-API文档-中英对照版.zip

    赠送jar包:shiro-config-ogdl-1.4.0.jar; 赠送原API文档:shiro-config-ogdl-1.4.0-javadoc.jar; 赠送源代码:shiro-config-ogdl-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-config-ogdl-1.4.0.pom; ...

    shiro-cas-1.2.3-API文档-中文版.zip

    赠送jar包:shiro-cas-1.2.3.jar; 赠送原API文档:shiro-cas-1.2.3-javadoc.jar; 赠送源代码:shiro-cas-1.2.3-sources.jar; 赠送Maven依赖信息文件:shiro-cas-1.2.3.pom; 包含翻译后的API文档:shiro-cas-...

    shiro-core-1.3.2-API文档-中文版.zip

    赠送jar包:shiro-core-1.3.2.jar; 赠送原API文档:shiro-core-1.3.2-javadoc.jar; 赠送源代码:shiro-core-1.3.2-sources.jar; 包含翻译后的API文档:shiro-core-1.3.2-javadoc-API文档-中文(简体)版.zip ...

    shiro-1.7.1.zip

    例如,Shiro的RememberMe服务、Session管理以及CSRF防护等功能都在这个模块中实现。 3. **shiro-lang-1.7.1.jar**: 该组件包含了Shiro的表达式语言支持,如在角色和权限定义时使用SpEL(Spring Expression Language...

    shiro-cas-1.2.3-API文档-中英对照版.zip

    赠送jar包:shiro-cas-1.2.3.jar; 赠送原API文档:shiro-cas-1.2.3-javadoc.jar; 赠送源代码:shiro-cas-1.2.3-sources.jar; 赠送Maven依赖信息文件:shiro-cas-1.2.3.pom; 包含翻译后的API文档:shiro-cas-...

    shiro-core-1.7.1 jar

    shiro shiro-core-1.7.1 jar shiro漏洞

    shiro源码(shiro-root-1.8.0-source-release.zip)

    Shiro的Remember Me服务允许用户在一次登录后,下次访问时自动登录,提高用户体验。它使用安全的加密技术存储用户的记住我信息,避免了安全风险。 7. **Caching**: Shiro提供了缓存管理,可以缓存认证和授权信息...

    shiro-cache-1.4.0-API文档-中文版.zip

    赠送jar包:shiro-cache-1.4.0.jar; 赠送原API文档:shiro-cache-1.4.0-javadoc.jar; 赠送源代码:shiro-cache-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-cache-1.4.0.pom; 包含翻译后的API文档:shiro-...

    shiro-cache-1.4.0-API文档-中英对照版.zip

    赠送jar包:shiro-cache-1.4.0.jar; 赠送原API文档:shiro-cache-1.4.0-javadoc.jar; 赠送源代码:shiro-cache-1.4.0-sources.jar; 赠送Maven依赖信息文件:shiro-cache-1.4.0.pom; 包含翻译后的API文档:shiro-...

Global site tag (gtag.js) - Google Analytics