shiro(9)-rememberMe(自动登录功能)

shiro安全控制目录

一般在登录网站上含有这么一个按钮(X天自动登录),在一次验证成功之后。用户后续X天内在同一个浏览器继续访问该页面时,不用经过认证,便可访问数据。

页面按钮.png

代码配置

SecurityManager作为Shiro核心,协调管理着rememberManager组件进行安全控制,认证流程如图1所示:

图1-认证流程.png

1.1 securityManager配置

在SecurityManager中配置rememberManager,如代码1所示。

代码1:Spring中rememberManager的配置

    
        
        
        
        
    
    
        
        
        
        
    
    
    
        
        
    

1.2 业务代码

设置了token.setRememberMe(true);属性,如代码2所示。

代码2:业务代码中开启remember功能

    @RequestMapping("/login")
    public String login(@Param("username") String username,@Param("password") String password) {
            UsernamePasswordToken token = new UsernamePasswordToken();
            token.setUsername(username);
            token.setPassword(password.toCharArray());
            //开启rememberMe功能
            token.setRememberMe(true);
            //调用验证
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
}

1.3 shiro Filter配置

shiro觉得不能把rememberMe等同于完全认证,这样是很不安全的,即若用户的cookie中rememberMe信息泄露,那么可能会造成安全漏洞。所有针对于rememberMe这个功能,shiro提供了两种Filter级别,代码3所示。

代码3:Shiro Filter的两种配置

//只有经过Realm认证后才不会被拦截
/** = authc
//Realm认证或开启RememberMe均不会被拦截
/** = user

文章参考:shiro(7)-shiroFilter(url层次认证/权限控制)

若想使用rememberMe功能,shiro Filter必须设置为/** = user。

经过认证后,请求中的cookie信息,如图2所示:

图2-cookie信息.png

源码分析

2.1 将rememberMe保存到cookie中

在rememberManager中对principals进行加密(这个值就是自定义Realm返回的SimpleAuthenticationInfo对象的属性)

源码:org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity

    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
        byte[] bytes = serialize(principals);
        if (getCipherService() != null) {
            //配置文件传入的私钥和principals进行加密
            bytes = encrypt(bytes);
        }
        return bytes;
    }

将加密串设置到cookie中

源码:org.apache.shiro.web.mgt.CookieRememberMeManager#rememberSerializedIdentity

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
         //省略部分代码
        //base 64 encode it and store as a cookie:
        String base64 = Base64.encodeToString(serialized);
        Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
        Cookie cookie = new SimpleCookie(template);
        //设置cookie中的rememberMe参数
        cookie.setValue(base64);
        cookie.saveTo(request, response);
    }

2.2 验证cookie中的rememberMe字段

获取cookie的字段,构建subject对象

请求上送时,在Shiro Filter中创建Subject对象,创建Subject对象后,将其加入到ThreadLocal中,而后再获取subject,只需要在线程上下文获取即可。

源码:org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal

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);
            //创建Subject对象(此时,已经获取到rememberMe上送的原始的principal对象)
            final Subject subject = createSubject(request, response);
          }
          //省略部分源码
    }

在createSubject(request, response)方法实现中,实际上获取cookie中上送的rememberMe字段(源码参看org.apache.shiro.web.servlet.SimpleCookie#readValue),然后将其解密获取到原始principal对象。

源码:org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals

 public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
        PrincipalCollection principals = null;
        try {
            //在SimpleCookie中获取到rememberMe字段
            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
            //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
            if (bytes != null && bytes.length > 0) {
                //进行解密,获取到原生的principal对象
                principals = convertBytesToPrincipals(bytes, subjectContext);
            }
        } catch (RuntimeException re) {
            principals = onRememberedPrincipalFailure(re, subjectContext);
        }
        return principals;
    }

此时之后,执行完毕createSubject方法,将subject放入到线程的上下文中。

shiro Filter过滤请求

使用org.apache.shiro.web.filter.authc.UserFilter过滤器,若是含有subject对象中含有principal对象,则放行。那么我们只要cookie中上送正确的rememberMe字段,便可以通过认证。

源码:org.apache.shiro.web.filter.authc.UserFilter#isAccessAllowed

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginRequest(request, response)) {
            return true;
        } else {
            Subject subject = getSubject(request, response);
            // If principal is not null, then the user is known and should be allowed access.
            return subject.getPrincipal() != null;
        }
    }

总结

shiro开启rememberMe功能后,登录成功后,会单独返回客户端一个cookie字段(rememberMe),保存着用户信息(principals对象)。当服务器shiro Filter认证过滤器为/**=user,用户请求的cookie中携带rememberMe字段。服务器并且可以解析rememberMe的加密串,那么则允许请求访问服务器数据。

这样可能会导致安全问题。故针对一些重要的数据,shiro Filter认证过滤器可以设置为/**=authc。

文章参考

Shiro的 rememberMe 功能使用指导(为什么rememberMe设置了没作用?)

你可能感兴趣的:(shiro(9)-rememberMe(自动登录功能))