Shiro教程之五 记住我的功能(rememberMe)

 这个也和 cookie和session相关,

记住我实现步骤

1.applicationContext.xml文件要修改,(在securityManager的bean中除了本来的Realm,再注入rememberMeManager相关bean)

2.用户认证时token设置参数记住我为true

3.测试是:登录后关闭浏览器再打开浏览器看是否登录状态存在(这里有个问题) 重新打开浏览器session无法获取问题

每次打开浏览器访问网址,session_id值回变,因此关掉浏览器再次打开用session_id获取用户信息-->(行不通)
解决办法,自定义一个过滤器,集成FormAuthenticationFilter ,在这里面,把rememberMe的用户信息,再次加入session中返回给客户端.
(这个自定义的过滤器可以模仿applicationContext.xml中Shiro中配置默认一些过滤器链一样,加入进去)

4.测试

【代码】记住我的功能

1.jsp或html页面的form表单中添加一个checkbox元素,选中为记住我,并传值到控制器

  <div class="form-row">
      <div class="form-group col-md-4">
          <label for="rememberme">七天免登录</label>
          <input type="checkbox" class="form-control" id="rememberme" name="rememberme" value="1" >
      </div>
  </div>

 2.applicationContext.xml里面设置给securityManager加入rememberMe的Bean

<!-- 10. Shiro认证权限配置-->
    <!-- ================ Shiro start ================ -->

    <!-- (1). cookie的配置 -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- 应该是表单传过来的值吧 -->
        <constructor-arg value="rememberme"></constructor-arg>
        <!-- 只有http时才能使用cookie-->
        <property name="httpOnly" value="true"></property>
        <!-- 设置cookie的存活时间 7天 单位秒 -->
        <property name="maxAge" value="604800"></property>
    </bean>
    <!-- (2). 声明记住我的管理对象 -->
    <bean name="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie"></property>
    </bean>

    <!-- (3). 声明凭证匹配器(密码加密用)-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!--注入算法-->
        <property name="hashAlgorithmName" value="md5"></property>
        <!--注入散列次数-->
        <property name="hashIterations" value="1"></property>
    </bean>

    <!-- (4).配置Realm-->
    <bean id="shiroReaml" class="com.cc8w.shiro.ShiroRealm">
        <!--注入凭证匹配器-->
        <property name="credentialsMatcher" ref="credentialsMatcher"></property>
    </bean>

    <!-- (5). 创建安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入reaml -->
        <property name="realm" ref="shiroReaml"></property>
        <!-- 注入rememberManager -->
        <property name="rememberMeManager" ref="rememberMeManager"></property>
    </bean>

    <!-- (6). 配置过滤器链-->
    <!-- Shiro 的Web过滤器 id必须和web.xml里面的shiroFilter的 targetBeanName的值一样 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--Shiro的核心安全接口,这个属性是必须的-->
        <property name="securityManager" ref="securityManager"></property>
        <!-- 要求登录时的链接(登录页面地址),非必须属性,属性会自动寻找web工程根目录下的"/login.jsp"页面-->
        <property name="loginUrl" value="/login.jsp"></property>
        <!-- Shiro 的Web过滤器 id必须和web.xml里面的shiroFilter的 targetBeanName的值一样 -->
        <!--property name="successUrl" value="success.do"></property-->
        <!-- 用户访问未对其授权的资源时,所显示的连接 -->
        <property name="unauthorizedUrl" value="unauthorized.jsp"></property>
        <!-- 过滤器链的定义,从上往下顺序执行,一般将/**放在最后 -->
        <property name="filterChainDefinitions">
            <!-- 等于后面是一个filter,可以用自带的也可以继承相应接口自己写 -->
            <value>
                <!-- /**=authc 所有url都必须认证通过才可以访问 -->
                /index.jsp*=anon
                /login/toLogin*=anon
                /login/login*=anon
                <!-- 如果访问/login/logout就是用Shiro注销session-->
                /login/logout=logout
                <!-- /** = anon所有url都可以匿名访问 -->
                <!-- /** = authc -->
                <!-- /*/* = authc -->
                <!-- /** = authc所有url都不可以匿名访问 必须放到最后面 -->
                /** = authc
            </value>
        </property>
    </bean>
<!-- 总结:
        Shiro总体配置:
        1.先配置第6条,用到了securityManager
              2.所以配置 第5条:securityManager, 第5条用到了:realm(用户相关)和rememberMeManager(记住我)

                      3.所以配置 第4条:realm,第4条用到了credentialsMatcher(加密相关)
                                4.所以配置 第3条credentialsMatcher(加密相关)

                      5.在配置rememberMeManager(记住我), 用到了第2条rememberMeManager(cookie的声明)
                                6.设置第2条cookie的时候,用到了第1条rememberMeCookie(设置cookid)
-->

    <!-- ================ Shiro end ================ -->

3.控制器接收值,登录时token设置相应参数

    //三,登录测试(自定义Realm)
    @Test
    public void testRealmLogin(){
        //1.创建一个安全管理器的工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory();
        //2.在工厂中获取安全管理器
        DefaultSecurityManager securityManager = (DefaultSecurityManager) factory.getInstance();

        //---添加认证凭证配置start  --如果Spring环境下(这些都是在xml配置的)
        //2.1 创建自定义Realm注入到安全管理器
        ShiroRealm shiroRealm = new ShiroRealm();//(SpringM在bean控制,并可以配置散列加密相关)
        //2.1.1设置密码学相关加密方式
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //2.1.2设置加密方式
        credentialsMatcher.setHashAlgorithmName("md5");
        //2.1.3设置散列次数
        credentialsMatcher.setHashIterations(1);
        //2.1.4将密码学凭证注入到自定义的Realm类中
        shiroRealm.setCredentialsMatcher(credentialsMatcher);
        //---添加认证凭证配置end  --如果Spring环境下(这些都是在xml配置的)

        securityManager.setRealm(shiroRealm);

        //3.将securityManager绑定到运行环境
        SecurityUtils.setSecurityManager(securityManager);
        //4.获取Subject对象(将要登录的用户)
        Subject subject = SecurityUtils.getSubject();
        //5.获取要登录用户的token,客户端传递过来的用户名和密码
        String username = "zhangsan",password="123456";
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);

        //6.如果有记住我功能(设置参数)
        Integer rememberme= 表单传来的值;
        if(rememberme!=null && rememberme==1){
            token.setRememberMe(true);//说明如果认证成功要记住我
        }

        try{
            //7.登陆(认证)
            subject.login(token);
            logger.info("登录了");

        }catch (IncorrectCredentialsException  e ){
            logger.info("密码不正确");
            logger.info(e);
        }catch (UnknownAccountException e) {
            System.out.println("没有这个帐号");
        }catch (AuthenticationException e) {
            e.printStackTrace();
        }

        //如果登录成功了,可以获取subject中各种状态了
        Boolean isAuth = subject.isAuthenticated();
        System.out.println("认证状态:" + isAuth);

        // 8.授权 分为:基于角色授权 基于资源的授权
        //8.1 基于角色授权
        boolean permited  = subject.hasRole("role1");
        System.out.println("这是授权单个:"+permited);
        boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1","role2","role3"));
        System.out.println("这个授权多个"+hasAllRoles);

        // 使用check方法进行授权,如果授权不通过会抛出异常
        // subject.checkRole("role13");
        try {
            subject.checkRole("roles1");
        }catch (UnauthenticatedException e){
            logger.info("没有这个角色");
            //e.printStackTrace();
        }catch (UnauthorizedException e){
            logger.info("没有这个权限");
            //e.printStackTrace();
        }


        //8.2 基于资源的授权
        //isPermitted传入权限标识符
        boolean isPermitted = subject.isPermitted("user:query");
        System.out.println("单个权限判断:"+isPermitted);

        boolean isPermittedAll = subject.isPermittedAll("user:query","user:adb","user:add");
        System.out.println("多个权限判断"+isPermittedAll);

        // 使用check方法进行授权,如果授权不通过会抛出异常
        try {
            subject.checkPermission("user:adb");
        }catch (UnauthenticatedException e){
            logger.info("没有这个角色");
            //e.printStackTrace();
        }catch (UnauthorizedException e){
            logger.info("没有这个权限");
            //e.printStackTrace();
        }



    }

4.重新打开浏览器,session无法获取问题

这个要创建一个自定义创建RememberMeFilter来集成FormAuthenticationFilter ,在applicationContext.xml配置这个过滤器链
目的:
让访问页面认证的时候,判断subject主体是否认证过了,如果认证过了,并且当前session中用户信息为空,再在subject.getPrincipal()获取一下用户信息给session.

1.因为securityManager设置了记住我时间,token登录的时候subject知道记住我时长.所以subject一直存有用户信息.
2.只是判断一下服务器session没有值了,且subject认证还没有过期,表示重新打开的浏览器,
这时要把subject.getPrincipal()就是自定义Reaml中传的ActiveUser.getUser();复制给session即可.
package com.cc8w.filter;

import com.cc8w.entity.UserActivePojo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class ShiroRememberMeFilter extends FormAuthenticationFilter {

    //过滤器访问,必须经过的方法,返回true表示通过
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {

        //1.获得主体
        Subject subject = SecurityUtils.getSubject();
        //2.通过主体获得session信息
        Session session = subject.getSession();
        //记住我的功能isAuthenticated肯定为false 而isRemembered肯定为true (因为不用短时间不用认证)
        if(!subject.isAuthenticated() && subject.isRemembered() && session.getAttribute("user")==null){
            //说明是记住我的功能
            UserActivePojo activeUser= (UserActivePojo)subject.getPrincipal();
            if(null!=activeUser){
                session.setAttribute("user",activeUser.getUserPojo());
            }
        }


        return true;
    }


}

5. 配置这个过滤器链

修改后的 xml 文件

    <!-- 10. Shiro认证权限配置-->
    <!-- ================ Shiro start ================ -->

    <!-- (1). cookie的配置 -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- 应该是表单传过来的值吧 -->
        <constructor-arg value="rememberme"></constructor-arg>
        <!-- 只有http时才能使用cookie-->
        <property name="httpOnly" value="true"></property>
        <!-- 设置cookie的存活时间 7天 单位秒 -->
        <property name="maxAge" value="604800"></property>
    </bean>
    <!-- (2). 声明记住我的管理对象 -->
    <bean name="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie"></property>
    </bean>

    <!-- (3). 声明凭证匹配器(密码加密用)-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!--注入算法-->
        <property name="hashAlgorithmName" value="md5"></property>
        <!--注入散列次数-->
        <property name="hashIterations" value="1"></property>
    </bean>

    <!-- (4).配置Realm-->
    <bean id="shiroReaml" class="com.cc8w.shiro.ShiroRealm">
        <!--注入凭证匹配器-->
        <property name="credentialsMatcher" ref="credentialsMatcher"></property>
    </bean>

    <!-- (5). 创建安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入reaml -->
        <property name="realm" ref="shiroReaml"></property>
        <!-- 注入rememberManager -->
        <property name="rememberMeManager" ref="rememberMeManager"></property>
    </bean>

    <!-- (6). 配置过滤器链-->
    <!-- 声明自定义记住我过滤器链-->
    <bean id="shirorememberMeFilter" class="com.cc8w.filter.ShiroRememberMeFilter" ></bean>

    <!-- Shiro 的Web过滤器 id必须和web.xml里面的shiroFilter的 targetBeanName的值一样 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--Shiro的核心安全接口,这个属性是必须的-->
        <property name="securityManager" ref="securityManager"></property>
        <!-- 要求登录时的链接(登录页面地址),非必须属性,属性会自动寻找web工程根目录下的"/login.jsp"页面-->
        <property name="loginUrl" value="/login.jsp"></property>
        <!-- Shiro 的Web过滤器 id必须和web.xml里面的shiroFilter的 targetBeanName的值一样 -->
        <!--property name="successUrl" value="success.do"></property-->
        <!-- 用户访问未对其授权的资源时,所显示的连接 -->
        <property name="unauthorizedUrl" value="unauthorized.jsp"></property>
        <!-- 注入自定义过滤器 -->
        <property name="filters">
            <map>
                <entry key="shirorememberMeFilter"  value-ref="shirorememberMeFilter"></entry>
            </map>
        </property>
        <!-- 过滤器链的定义,从上往下顺序执行,一般将/**放在最后 -->
        <property name="filterChainDefinitions">
            <!-- 等于后面是一个filter,可以用自带的也可以继承相应接口自己写 -->
            <value>
                <!-- /**=authc 所有url都必须认证通过才可以访问 -->
                /index.jsp*=anon
                /login/toLogin*=anon
                /login/login*=anon
                <!-- 如果访问/login/logout就是用Shiro注销session-->
                /login/logout=logout
                <!-- 其他页面都要认证,并且过自定义过滤器-->
                /** = shirorememberMeFilter,authc
                /* = authc
                /*/* = authc
                <!-- /** = authc所有url都不可以匿名访问 必须放到最后面 -->

            </value>
        </property>
    </bean>
<!-- 总结:
        Shiro总体配置:
        1.先配置第6条,用到了securityManager
              2.所以配置 第5条:securityManager, 第5条用到了:realm(用户相关)和rememberMeManager(记住我)

                      3.所以配置 第4条:realm,第4条用到了credentialsMatcher(加密相关)
                                4.所以配置 第3条credentialsMatcher(加密相关)

                      5.在配置rememberMeManager(记住我), 用到了第2条rememberMeManager(cookie的声明)
                                6.设置第2条cookie的时候,用到了第1条rememberMeCookie(设置cookid)
-->

    <!-- ================ Shiro end ================ -->

测试:  登录后,再关闭浏览器,在打开.

最后贴一个登录认证,(这时一些类已经交由Spring管理)

/**
     * 完成登陆的方法
     */
    @RequestMapping("login")
    public String login(String username, String password, HttpSession session) {
        // 1,得到主体
        Subject subject = SecurityUtils.getSubject();
        // 2,封装用户名和密码
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            ActiveUser activerUser = (ActiveUser) subject.getPrincipal();
            session.setAttribute("user", activerUser.getUser());
            return "redirect:/user/loadAllUser.action";
        } catch (AuthenticationException e) {
            System.out.println("用户名或密码不正确");
        }    
        return "redirect:index.jsp";
    }
原文地址:https://www.cnblogs.com/fps2tao/p/13572518.html