• Shrio第二天——认证、授权与其它特性


    一、认证——Authentication

      (即登陆),简单分析之前的HelloWorld的认证:

    1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
    2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated() 
    3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
    1). 创建一个表单页面
    2). 把请求提交到 SpringMVC 的 Handler
    3). 获取用户名和密码. 
    4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法. 
    5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
    1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
    2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法. 
    6. 由 shiro 完成对密码的比对. 
    View Code

       在 shiro 中,用户需要提供 principals (身份)和 credentials(证 明)给 shiro,从而应用能验证用户身份

      接下来我们来实现一下分析的认证流程:

        第3步的创建表单页面:login.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <h3>Hello login!</h3>
        <form action="shiroLogin" method="post">
            username:<input type="text" name="username"/>
            <br/>
            password:<input type="password" name="password"/>
            <br/>
            <input type="submit" value="提交"/>
        </form>
    </body>
    </html>
    View Code

        创建相应的handler(controller):

    package cn.shiro.handler;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @Controller
    public class ShrioHandler {
    
        /**
         * 获取登陆的表单数据
         * @return
         */
        @RequestMapping("/shiroLogin")
        public String login(@RequestParam("username") String username,
                            @RequestParam("password") String password){
            Subject currentUser = SecurityUtils.getSubject();
            if (!currentUser.isAuthenticated()) {
                // 把用户名和密码封装为 UsernamePasswordToken 对象
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                // rememberme
                token.setRememberMe(true);
                try {
                    System.out.println("1. " + token.hashCode());
                    // 执行登录. 
                    currentUser.login(token);
                } 
                // ... catch more exceptions here (maybe custom ones specific to your application?
                // 所有认证时异常的父类. 
                catch (AuthenticationException ae) {
                    //unexpected condition?  error?
                    System.out.println("登录失败: " + ae.getMessage());
                }
            }
            //执行完成后重定向到list页面
            return "redirect:/list.jsp";
        }
    }
    View Code

        将前面创建的Realm改造为继承而不是实现接口的形式来实现自定义Realm:

    package cn.shiro.realms;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    public class ShiroRealm extends AuthorizingRealm {
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
            // TODO Auto-generated method stub
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
            System.out.println("token:"+arg0.hashCode());
            return null;
        }
    
        
    }
    View Code

        当然,我们得在配置文件中配置controller的匿名访问才可以访问:

       <property name="filterChainDefinitions">
                <value>
                    /login.jsp = anon
                    /shiroLogin = anon
                    # everything else requires authentication:
                    /** = authc
                </value>
            </property>

        最后我们访问Login页面,可以看到结果:

     //证明我们的reaml拿到了用户名与密码(token)

         可以过来以后我们进行认证的完善:

      完善ShiroRealm的对比过程:

    package cn.shiro.realms;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.LockedAccountException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    public class ShiroRealm extends AuthorizingRealm {
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
            
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
            //System.out.println("token:"+arg0.hashCode());
            
            //1. 把 AuthenticationToken 转换为 UsernamePasswordToken
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            //2. 从 UsernamePasswordToken 中来获取 username
            String username = upToken.getUsername();
            //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
            System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
            //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
            if("unknown".equals(username)){
                throw new UnknownAccountException("用户不存在!");
            }
            //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常
            if("monster".equals(username)){
                throw new LockedAccountException("用户被锁定");
            }
            //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
            //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
            Object principal = username;
            //2). credentials: 密码. 
            Object credentials = "123";
            //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
            String realmName = getName();
            
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
            //返回信息
            return info;
        }
    
        
    }
    View Code

      输入unknown的测试结果(其他暂不列举)

     

      //成功登陆(即认证)后即可访问其他的需要认证的页面

      我们来大致捋一下认证的流程:

    从表单填写信息——>提交到handler,获取表单的信息——>调用login方法,把用户名密码封装为token对象继续往后传——>在Reaml中调用doGetAuthenticationInfo方法——>由token的用户名去数据库中获取用户信息进行对比,我们把密码封装成SimpleAuthenticationInfo(具体的源码分析:例如何时进行密码比对,如何比对待补充)

       接下来进行密码的加密,因为密码不能是明文,并且不能是可逆的,这里介绍常用的MD5,另外的SHA1待补充:

      1. 如何把一个字符串加密为 MD5
      2. 替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可.

    我们先完成第二步的配置,在配置文件中Realm的配置处配置一个内部的bean:(配置了自定义Realm,当然得让shiro知道我们要使用自定义的Realm)

        <!-- 
            3. 配置 Realm 
            3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
        -->     
        <bean id="jdbcRealm" class="cn.shiro.realms.ShiroRealm">
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <property name="hashAlgorithmName" value="MD5"></property>
                </bean>
            </property>
        </bean>
    View Code

      指定了凭证匹配器后会自动的把前台的明文密码加密为MD5,当然,我们可以指定加密次数:

        <property name="hashAlgorithmName" value="MD5"></property>
                    <property name="hashIterations" value="1024"></property>
    View Code

      当然,即使指定了加密次数,如果两个人的密码一样,那么加密后的密码也是一样的,我们还需要改进,于是我们加入 盐值

    盐值是一个ByteSource接口,一般我们使用唯一的字符串作为盐,比如不可重复的用户名,示例如下:

    @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
            System.out.println("[FirstRealm] doGetAuthenticationInfo");
            
            //1. 把 AuthenticationToken 转换为 UsernamePasswordToken 
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            
            //2. 从 UsernamePasswordToken 中来获取 username
            String username = upToken.getUsername();
            
            //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
            System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
            
            //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
            if("unknown".equals(username)){
                throw new UnknownAccountException("用户不存在!");
            }
            
            //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. 
            if("monster".equals(username)){
                throw new LockedAccountException("用户被锁定");
            }
            
            //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
            //以下信息是从数据库中获取的.
            //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
            Object principal = username;
            //2). credentials: 密码. 
            Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
            if("admin".equals(username)){
                credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
            }else if("user".equals(username)){
                credentials = "098d2c478e9c11555ce2823231e02ec1";
            }
            
            //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
            String realmName = getName();
            //4). 盐值. 
            ByteSource credentialsSalt = ByteSource.Util.bytes(username);
            
            SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
            info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
            return info;
        }
    View Code

      盐值加密小结如下:

    1. 为什么使用 MD5 盐值加密: 
    2. 如何做到:
    1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用
    SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器
    2). 使用 ByteSource.Util.bytes() 来计算盐值. 
    3). 盐值需要唯一: 一般使用随机字符串或 user id
    4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值. 
    View Code

      加密的部分请参见开涛的博客:http://jinnianshilongnian.iteye.com/blog/2021439

      接下来我们再介绍多Realm验证

      先建立一个SHA1加密的Realm:

    package com.atguigu.shiro.realms;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.LockedAccountException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.crypto.hash.SimpleHash;
    import org.apache.shiro.realm.AuthenticatingRealm;
    import org.apache.shiro.util.ByteSource;
    
    public class SecondRealm extends AuthenticatingRealm {
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
            System.out.println("[SecondReaml] doGetAuthenticationInfo");
            
            //1. 把 AuthenticationToken 转换为 UsernamePasswordToken 
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            
            //2. 从 UsernamePasswordToken 中来获取 username
            String username = upToken.getUsername();
            
            //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
            System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
            
            //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
            if("unknown".equals(username)){
                throw new UnknownAccountException("用户不存在!");
            }
            
            //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. 
            if("monster".equals(username)){
                throw new LockedAccountException("用户被锁定");
            }
            
            //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
            //以下信息是从数据库中获取的.
            //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
            Object principal = username;
            //2). credentials: 密码. 
            Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
            if("admin".equals(username)){
                credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";
            }else if("user".equals(username)){
                credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
            }
            
            //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
            String realmName = getName();
            //4). 盐值. 
            ByteSource credentialsSalt = ByteSource.Util.bytes(username);
            
            SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
            info = new SimpleAuthenticationInfo("secondRealmName", credentials, credentialsSalt, realmName);
            return info;
        }
    
        public static void main(String[] args) {
            String hashAlgorithmName = "SHA1";
            Object credentials = "123456";
            Object salt = ByteSource.Util.bytes("admin");;
            int hashIterations = 1024;
            
            Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
            System.out.println(result);
        }
    }
    View Code

      在spring配置文件中配置第二个bean:

     <bean id="secondRealm" class="com.atguigu.shiro.realms.SecondRealm">
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <property name="hashAlgorithmName" value="SHA1"></property>
                    <property name="hashIterations" value="1024"></property>
                </bean>
            </property>
        </bean>
    View Code

      再把管家的多个Realm进行配置:

     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="cacheManager" ref="cacheManager"/>
            <property name="authenticator" ref="authenticator"></property>
            
            <property name="realms">
                <list>
                    <ref bean="jdbcRealm"/>
                    <ref bean="secondRealm"/>
                </list>
            </property>
            
            <property name="rememberMeManager.cookie.maxAge" value="10"></property>
        </bean>
    View Code

      测试暂不举例(认证策略暂不展开):

    AuthenticationStrategy
    • AuthenticationStrategy 接口的默认实现:
    • FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第
    一个 Realm 身份验证成功的认证信息,其他的忽略;
    • AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和
    FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信
    息;
    • AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有
    Realm身份验证成功的认证信息,如果有一个失败就失败了。
    • ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy
    策略

     退出: 不用我们去实现退出,只要去访问一个退出的url(该 url是可以不存在),由LogoutFilter拦截住,清除session。

      在applicationContext-shiro.xml配置LogoutFilter:

        <!-- 请求 logout.action地址,shiro去清除session-->
                    /logout.action = logout

    二、授权——Authorization

      授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作 等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限 (Permission)、角色(Role)。

      授权基础流程:

      

     测试授权的具体流程:

    1、在applicationContext-shiro.xml中配置filter规则
        <!--商品查询需要商品查询权限  -->
        /items/queryItems.action = perms[item:query]
    2、用户在认证通过后,请求/items/queryItems.action
    3、被PermissionsAuthorizationFilter拦截,发现需要“item:query”权限
    4、PermissionsAuthorizationFilter调用realm中的doGetAuthorizationInfo获取数据库中正确的权限
    5、PermissionsAuthorizationFilter对item:query 和从realm中获取权限进行对比,如果“item:query”在realm返回的权限列表中,授权通过。

      相关的对象解释:

    • 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权
    后才允许访问相应的资源。
    • 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些
    数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
    • 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户
    有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用
    户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控
    制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允
    许。
    • Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,
    即实例级别的)
    • 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有
    一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等
    都是角色,不同的角色拥有一组不同的权限。
      Shiro支持的授权方式:
     

     //当然编码与注解我们还是倾向于使用注解的形式

    规则即:“用户名=密码,角色1,角色2”,如果需要在应用中判断用户是否有相应角色,就需要在相应的Realm中返回角色信息,也就是说Shiro不负责维护用户-角色信息,需要应用提供,Shiro只是提供相应的接口方便验证

       Shiro相关的拦截器参见day01

      1.Shiro 的 Permissions  //quickstart的shiro.ini中也有相关的介绍。

      规则:资源标识符:操作:对象实例 ID 即对哪个资源的哪个实例可以进行什么操作.

      • 实例级访问控制 – 这种情况通常会使用三个部件:域、操作、被付诸实施的实例。如:user:edit:manager,对资源user的manager实例拥有edit操作

        – 也可以使用通配符来定义,如:user:edit:*、user:*:*、 user:*:manager

        – 部分省略通配符:缺少的部件意味着用户可以访问所 有与之匹配的值,比如:user:edit 等价于 user:edit :*、 user 等价于 user:*:*

        – 注意:通配符只能从字符串的结尾处省略部件,也就 是说 user:edit 并不等价于 user:*:edit

       2.授权流程简要分析如下:

    • 流程如下:
    • 1、首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给
    SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
    • 2、Authorizer是真正的授权者,如果调用如
    isPermitted(“user:view”),其首先会通过
    • PermissionResolver 把字符串转换成相应的 Permission 实例;
    • 3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角
    色/权限用于匹配传入的角色/权限;
    • 4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果
    有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,
    如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示
    授权失败。

       如何实现授权:

    //授权会被 shiro 回调的方法
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
            //1. 从 PrincipalCollection 中来获取登录用户的信息
            Object principal = principals.getPrimaryPrincipal();
            
            //2. 利用登录的用户的信息来用户当前用户的角色或权限(可能需要查询数据库)
            Set<String> roles = new HashSet<>();
            roles.add("user");
            if("admin".equals(principal)){
                roles.add("admin");
            }
            
            //3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
            
            //4. 返回 SimpleAuthorizationInfo 对象. 
            return info;
        }
    View Code

     1.编码实现举例:

      ini配置文件:(后期应该改造为自定义realm,在数据库存储)

    shiro-permission.ini里边的内容相当于在数据库。
    
    #用户
    [users]
    #用户zhang的密码是123,此用户具有role1和role2两个角色
    zhang=123,role1,role2
    wang=123,role2
    
    #权限
    [roles]
    #角色role1对资源user拥有create、update权限
    role1=user:create,user:update
    #角色role2对资源user拥有create、delete权限
    role2=user:create,user:delete
    #角色role3对资源user拥有create权限
    role3=user:create

     2.一般情况下,我们都需要通过自定义Realm来进行授权(也就是前面自定义Realm中另外一个未实现的方法):

        在之前的customerRealm中完成另外一个授权方法:

        // 用于授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
            
            //从 principals获取主身份信息
            //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),
            ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();
            
            //根据身份信息获取权限信息
            //从数据库获取到权限数据
            List<SysPermission> permissionList = null;
            try {
                permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //单独定一个集合对象 
            List<String> permissions = new ArrayList<String>();
            if(permissionList!=null){
                for(SysPermission sysPermission:permissionList){
                    //将数据库中的权限标签 符放入集合
                    permissions.add(sysPermission.getPercode());
                }
            }
            
            
        /*    List<String> permissions = new ArrayList<String>();
            permissions.add("user:create");//用户的创建
            permissions.add("item:query");//商品查询权限
            permissions.add("item:add");//商品添加权限
            permissions.add("item:edit");//商品修改权限
    */        //....
            
            //查到权限数据,返回授权信息(要包括 上边的permissions)
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            //将上边查询到授权信息填充到simpleAuthorizationInfo对象中
            simpleAuthorizationInfo.addStringPermissions(permissions);
    
            return simpleAuthorizationInfo;
        }
    View Code

         测试方法:

    // 自定义realm进行资源授权测试
        @Test
        public void testAuthorizationCustomRealm() {
    
            // 创建SecurityManager工厂
            Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                    "classpath:shiro-realm.ini");
    
            // 创建SecurityManager
            SecurityManager securityManager = factory.getInstance();
    
            // 将SecurityManager设置到系统运行环境,和spring后将SecurityManager配置spring容器中,一般单例管理
            SecurityUtils.setSecurityManager(securityManager);
    
            // 创建subject
            Subject subject = SecurityUtils.getSubject();
    
            // 创建token令牌
            UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
                    "111111");
    
            // 执行认证
            try {
                subject.login(token);
            } catch (AuthenticationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            System.out.println("认证状态:" + subject.isAuthenticated());
            // 认证通过后执行授权
    
            // 基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库查询正确权限数据
            // isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到权限数据之内
            boolean isPermitted = subject.isPermitted("user:create:1");
            System.out.println("单个权限判断" + isPermitted);
    
            boolean isPermittedAll = subject.isPermittedAll("user:create:1",
                    "user:create");
            System.out.println("多个权限判断" + isPermittedAll);
    
            // 使用check方法进行授权,如果授权不通过会抛出异常
            subject.checkPermission("items:add:1");
    
        }
    View Code

          方法流程(上文已有一个版本):

    1、对subject进行授权,调用方法isPermitted("permission串"2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
    3、ModularRealmAuthorizer执行realm(自定义的CustomRealm)从数据库查询权限数据
        调用realm的授权方法:doGetAuthorizationInfo
    
    4、realm从数据库查询权限数据,返回ModularRealmAuthorizer
    5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对
    6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。

      3.Shiro标签:

        标签的介绍参见http://jinnianshilongnian.iteye.com/blog/2026398

      4.shiro注解 

      对系统中类的方法给用户授权,建议在controller层进行方法授权。(下文有进一步原因解释)

       在springMVC中配置注解的AOP式支持:

       <!-- 开启aop,对类代理 -->
        <aop:config proxy-target-class="true"></aop:config>
        <!-- 开启shiro注解支持 -->
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager" />
        </bean>

        

      这样就可以在controller方法上(强烈建议controller方法上,而不是其它层)使用 @RequiresPermissions("order:query") ——执行此方法需要此权限,示例需要此角色类同式注解了:

    @RequiresRoles("admin")  
    @RequestMapping("/hello2")  
    public String hello2() {  
        return "success";  
    }  

      当调用controller的一个方法,由于该 方法加了@RequiresPermissions("item:query") ,shiro调用realm获取数据库中的权限信息,看"item:query"是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。

      当展示一个jsp页面(真身是servlet)时,页面中如果遇到<shiro:hasPermission name="item:update">,shiro调用realm获取数据库中的权限信息,看item:update是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。

    问题:只要遇到注解或jsp标签的授权,都会调用realm方法查询数据库,需要使用缓存解决此问题。

         jeesite中的配置(待补充):

    <!-- AOP式方法级权限检查  -->
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
            <property name="proxyTargetClass" value="true" />
        </bean>
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager"/>
        </bean>

      完整配置文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
            http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.1.xsd"
        default-lazy-init="true">
    
        <description>Shiro Configuration</description>
    
        <!-- 加载配置属性文件 -->
        <context:property-placeholder ignore-unresolvable="true" location="classpath:jeesite.properties" />
        
        <!-- Shiro权限过滤过滤器定义 -->
        <bean name="shiroFilterChainDefinitions" class="java.lang.String">
            <constructor-arg>
                <value>
                    /static/** = anon
                    /userfiles/** = anon
                    ${adminPath}/cas = cas
                    ${adminPath}/login = authc
                    ${adminPath}/logout = logout
                    ${adminPath}/** = user
                    /act/editor/** = user
                    /ReportServer/** = user
                </value>
            </constructor-arg>
        </bean>
        
        <!-- 安全认证过滤器 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" /><!-- 
            <property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->
            <property name="loginUrl" value="${adminPath}/login" />
            <property name="successUrl" value="${adminPath}?login" />
            <property name="filters">
                <map>
                    <entry key="cas" value-ref="casFilter"/>
                    <entry key="authc" value-ref="formAuthenticationFilter"/>
                </map>
            </property>
            <property name="filterChainDefinitions">
                <ref bean="shiroFilterChainDefinitions"/>
            </property>
        </bean>
        
        <!-- CAS认证过滤器 -->  
        <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">  
            <property name="failureUrl" value="${adminPath}/login"/>
        </bean>
        
        <!-- 定义Shiro安全管理配置 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="systemAuthorizingRealm" />
            <property name="sessionManager" ref="sessionManager" />
            <property name="cacheManager" ref="shiroCacheManager" />
        </bean>
        
        <!-- 自定义会话管理配置 -->
        <bean id="sessionManager" class="com.thinkgem.jeesite.common.security.shiro.session.SessionManager"> 
            <property name="sessionDAO" ref="sessionDAO"/>
            
            <!-- 会话超时时间,单位:毫秒  -->
            <property name="globalSessionTimeout" value="${session.sessionTimeout}"/>
            
            <!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话   -->
            <property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
    <!--          <property name="sessionValidationSchedulerEnabled" value="false"/> -->
             <property name="sessionValidationSchedulerEnabled" value="true"/>
             
            <property name="sessionIdCookie" ref="sessionIdCookie"/>
            <property name="sessionIdCookieEnabled" value="true"/>
        </bean>
        
        <!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
            当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! -->
        <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg name="name" value="jeesite.session.id"/>
        </bean>
    
        <!-- 自定义Session存储容器 -->
    <!--     <bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.JedisSessionDAO"> -->
    <!--         <property name="sessionIdGenerator" ref="idGen" /> -->
    <!--         <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" /> -->
    <!--     </bean> -->
        <bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.CacheSessionDAO">
            <property name="sessionIdGenerator" ref="idGen" />
            <property name="activeSessionsCacheName" value="activeSessionsCache" />
            <property name="cacheManager" ref="shiroCacheManager" />
        </bean>
        
        <!-- 自定义系统缓存管理器-->
    <!--     <bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.JedisCacheManager"> -->
    <!--         <property name="cacheKeyPrefix" value="${redis.keyPrefix}_cache_" /> -->
    <!--     </bean> -->
        <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManager" ref="cacheManager"/>
        </bean>
        
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
        
        <!-- AOP式方法级权限检查  -->
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
            <property name="proxyTargetClass" value="true" />
        </bean>
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager"/>
        </bean>
        
    </beans>
    View Code

      注解的实现请参见:http://www.cnblogs.com/lzlblogs/p/5973535.html

      【注意】:

      在Service方法上使用注解 @Transactional 即在方法开始的时候会有事务,这个时候这个Service已经是一个代理对象,

      这个是有把 权限注解加到 Service上是不好用的,会发生类型转换异常。需要加到Controller上,因为不能够让Service是代理的代理。

      并且,controller的入口只有一个,而service是可以被其它层公开调用了,就失去了授权的意义。

      4.之前我们配置的受保护的资源和对应关系是配置在applicationContext.xml中配置固定的:

    <property name="filterChainDefinitions">
                <value>
                    /login.jsp = anon
                    /shiro/login = anon
                    /shiro/logout = logout
                    
                    /user.jsp = roles[user]
                    /admin.jsp = roles[admin]
                    
                    # everything else requires authentication:
                    /** = authc
                </value>
            </property>

     我们可以改进为将数据存入数据库中,然后通过SQL方式进行读取,这样便于管理。

        我们把 filterChainDefinitions 配置给ShiroFilter:

     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <property name="loginUrl" value="/login.jsp"/>
            <property name="successUrl" value="/list.jsp"/>
            <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
            
            <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>

        创建相应的Map,存放资源权限数据:

    package com.atguigu.shiro.factory;
    
    import java.util.LinkedHashMap;
    
    public class FilterChainDefinitionMapBuilder {
    
        public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){
            LinkedHashMap<String, String> map = new LinkedHashMap<>();
            
            map.put("/login.jsp", "anon");
            map.put("/shiro/login", "anon");
            map.put("/shiro/logout", "logout");
            map.put("/user.jsp", "authc,roles[user]");
            map.put("/admin.jsp", "authc,roles[admin]");
            map.put("/list.jsp", "user");
            
            map.put("/**", "authc");
            
            return map;
        }
        
    }
    View Code

      //实际操作的时候,从数据库取出数据(一定是linkedHashMap,而且注意顺序)

      再补全配置文件:

      <!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->
        <bean id="filterChainDefinitionMap" 
            factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean>
        
        <bean id="filterChainDefinitionMapBuilder"
            class="com.atguigu.shiro.factory.FilterChainDefinitionMapBuilder"></bean>

     通过自定义拦截器可以扩展功能,例如:动态url-角色/权 限访问控制的实现、根据 Subject 身份信息获取用户信息 绑定到 Request(即设置通用数据)、验证码验证、在线 用户信息的保存等

    三、会话管理

     Shiro 提供了完整的企业级会话管理功能,不依赖于底层容 器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境 都可以使用,提供了会话管理、会话事件监听、会话存储/ 持久化、容器无关的集群、失效/过期支持、对Web 的透明 支持、SSO 单点登录的支持等特性。

      相关的API这里暂不展开。

      1.这里需要提的是Shiro的会话管理可以进入Service,这样就可以在开发时在service层访问session

    package com.atguigu.shiro.services;
    
    import java.util.Date;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.apache.shiro.session.Session;
    
    public class ShiroService {
        
        @RequiresRoles({"admin"})
        public void testMethod(){
            System.out.println("testMethod, time: " + new Date());
            
            Session session = SecurityUtils.getSubject().getSession();
            Object val = session.getAttribute("key");
            
            System.out.println("Service SessionVal: " + val);
        }
        
    }
     
    View Code

      2.SessionDao:

    AbstractSessionDAO 提供了 SessionDAO 的基础实现,
    如生成会话ID等
    • CachingSessionDAO 提供了对开发者透明的会话缓存的
    功能,需要设置相应的 CacheManager
    • MemorySessionDAO 直接在内存中进行会话维护
    • EnterpriseCacheSessionDAO 提供了缓存功能的会话维
    护,默认情况下使用 MapCache 实现,内部使用
    ConcurrentHashMap 保存缓存的会话。
    View Code

     示例配置:

        3.会话验证

      • Shiro 提供了会话验证调度器,用于定期的验证会话是否 已过期,如果过期将停止会话 • 出于性能考虑,一般情况下都是获取会话时来验证会话是 否过期并停止会话的;但是如在 web 环境中,如果用户不 主动退出是不知道会话是否过期的,

      因此需要定期的检测 会话是否过期,Shiro 提供了会话验证调度器 SessionValidationScheduler • Shiro 也提供了使用Quartz会话验证调度器: QuartzSessionValidationScheduler

    【更新】:shiro的缓存

      针对上边授权频繁查询数据库,需要使用shiro缓存。

      shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的。主要研究授权信息缓存,因为授权的数据量大。

      用户认证通过。

      该 用户第一次授权:调用realm查询数据库

      该 用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符:就是一堆字符串)。

      缓存的配置步骤:

        导包:

          

        配置缓存管理器(cacheManager):由于缓存管理器也是归安全管理器securityManager这个大管家管理的,所以在其属性处进行配置即可:

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
        <!--diskStore:缓存数据持久化的目录 地址  -->
        <diskStore path="F:developehcache" />
        <defaultCache 
            maxElementsInMemory="1000" 
            maxElementsOnDisk="10000000"
            eternal="false" 
            overflowToDisk="false" 
            diskPersistent="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120" 
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        </defaultCache>
    </ehcache>
    View Code

    【更新】:验证码的引入:

      思路

      shiro使用FormAuthenticationFilter进行表单认证,验证校验的功能应该加在FormAuthenticationFilter中,在认证之前进行验证码校验。

      需要写FormAuthenticationFilter的子类,继承FormAuthenticationFilter,改写它的认证方法,在认证之前进行验证码校验。

    public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
    
        //原FormAuthenticationFilter的认证方法
        @Override
        protected boolean onAccessDenied(ServletRequest request,
                ServletResponse response) throws Exception {
            //在这里进行验证码的校验
            
            //从session获取正确验证码
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpSession session =httpServletRequest.getSession();
            //取出session的验证码(正确的验证码)
            String validateCode = (String) session.getAttribute("validateCode");
            
            //取出页面的验证码
            //输入的验证和session中的验证进行对比 
            String randomcode = httpServletRequest.getParameter("randomcode");
            if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
                //如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
                httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
                //拒绝访问,不再校验账号和密码 
                return true; 
            }
            return super.onAccessDenied(request, response);
        }
    View Code
    <!-- 自定义filter配置 -->
            <property name="filters">
                <map>
                    <!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->
                    <entry key="authc" value-ref="formAuthenticationFilter" />
                </map>
            </property>
    <!-- 自定义form认证过虑器 -->
    <!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
        <bean id="formAuthenticationFilter" 
        class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
            <!-- 表单中账号的input名称 -->
            <property name="usernameParam" value="username" />
            <!-- 表单中密码的input名称 -->
            <property name="passwordParam" value="password" />
            <!-- 记住我input的名称 -->
            <property name="rememberMeParam" value="rememberMe"/>
     </bean>

    四、认证与记住我

       • Shiro 提供了记住我(RememberMe)的功能,比如访问如淘宝 等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问,基本流程如下:

      • 1、首先在登录页面选中 RememberMe 然后登录成功;如果是 浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并 保存下来;——用户信息实现序列化借口,方便进行保存与反序列化。

      • 2、关闭浏览器再重新打开;会发现浏览器还是记住你的;

      • 3、访问一般的网页服务器端还是知道你是谁,且能正常访问;

      • 4、但是比如我们访问淘宝时,如果要查看我的订单或进行支付 时,此时还是需要再进行身份认证的,以确保当前用户还是你。

       如何配置:

      前台页面:

                   <tr>
                                <TD></TD>
                                <td><input type="checkbox" name="rememberMe" />自动登陆</td>
                            </tr>

    //当然,name是可以配置的,详见配置文件applicationContext-shiro.xml:

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
            http://www.springframework.org/schema/mvc 
            http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-3.2.xsd 
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
            http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    
    <!-- web.xml中shiro的filter对应的bean -->
    <!-- Shiro 的Web过滤器 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
            <property name="loginUrl" value="/login.action" />
            <!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
            <property name="successUrl" value="/first.action"/>
            <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面-->
            <property name="unauthorizedUrl" value="/refuse.jsp" />
            <!-- 自定义filter配置 -->
            <property name="filters">
                <map>
                    <!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->
                    <entry key="authc" value-ref="formAuthenticationFilter" />
                </map>
            </property>
            
            <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
            <property name="filterChainDefinitions">
                <value>
                    <!-- 对静态资源设置匿名访问 -->
                    /images/** = anon
                    /js/** = anon
                    /styles/** = anon
                    <!-- 验证码,可匿名访问 -->
                    /validatecode.jsp = anon
                    
                    <!-- 请求 logout.action地址,shiro去清除session-->
                    /logout.action = logout
                    <!--商品查询需要商品查询权限 ,取消url拦截配置,使用注解授权方式 -->
                    <!-- /items/queryItems.action = perms[item:query]
                    /items/editItems.action = perms[item:edit] -->
                    <!-- 配置记住我或认证通过可以访问的地址 -->
                    /index.jsp  = user
                    /first.action = user
                    /welcome.jsp = user
                    <!-- /** = authc 所有url都必须认证通过才可以访问-->
                    /** = authc
                    <!-- /** = anon所有url都可以匿名访问 -->
                    
                </value>
            </property>
        </bean>
    
    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="customRealm" />
            <!-- 注入缓存管理器 -->
            <property name="cacheManager" ref="cacheManager"/>
            <!-- 注入session管理器 -->
            <property name="sessionManager" ref="sessionManager" />
            <!-- 记住我 -->
            <property name="rememberMeManager" ref="rememberMeManager"/>
            
        </bean>
    
    <!-- realm -->
    <bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    
    <!-- 凭证匹配器 -->
    <bean id="credentialsMatcher"
        class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="1" />
    </bean>
    
    <!-- 缓存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
        </bean>
    
    <!-- 会话管理器 -->
        <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
            <!-- session的失效时长,单位毫秒 -->
            <property name="globalSessionTimeout" value="600000"/>
            <!-- 删除失效的session -->
            <property name="deleteInvalidSessions" value="true"/>
        </bean>
    
    <!-- 自定义form认证过虑器 -->
    <!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
        <bean id="formAuthenticationFilter" 
        class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
            <!-- 表单中账号的input名称 -->
            <property name="usernameParam" value="username" />
            <!-- 表单中密码的input名称 -->
            <property name="passwordParam" value="password" />
            <!-- 记住我input的名称 -->
            <property name="rememberMeParam" value="rememberMe"/>
     </bean>
    
    <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 -->
        <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
            <property name="cookie" ref="rememberMeCookie" />
        </bean>
        <!-- 记住我cookie -->
        <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <!-- rememberMe是cookie的名字 -->
            <constructor-arg value="rememberMe" />
            <!-- 记住我cookie生效时间30天 -->
            <property name="maxAge" value="2592000" />
        </bean>
    
    
    </beans>
    View Code

      配置某些页面可以使用记住我访问:

       认证与记住我的区别:

      subject.isAuthenticated() 表示用户进行了身份验证登录的, 即使有 Subject.login 进行了登录;

      • subject.isRemembered():表示用户是通过记住我登录的, 此时可能并不是真正的你(如你的朋友使用你的电脑,或者 你的cookie 被窃取)在访问的

      • 两者二选一,即 subject.isAuthenticated()==true,则 subject.isRemembered()==false;反之一样。

            // rememberme
                token.setRememberMe(true);

      //一般为若单选框的进行了勾选则设置记住我,还可以进一步设置记住我的时间。

     记住我请参阅:http://jinnianshilongnian.iteye.com/blog/2031823

      

  • 相关阅读:
    程其襄实变函数与泛函分析课件
    谢惠民答案
    谢惠民 数学分析习题课讲义 答案
    谢惠民数学分析习题课讲义下册参考解答
    重磅! 谢惠民下册参考解答已经全部完成, 共 473 页!
    各大高校考研试题参考解答目录2020/06/21版
    Jenkins Pipeline审批
    Zabbix监控DHCP作用域(json格式数据)
    MDT通过UserExit.vbs调用PowerShell脚本获取变量
    MDT通过PowerShell脚本自定义变量(自定义计算机名)
  • 原文地址:https://www.cnblogs.com/jiangbei/p/7138197.html
Copyright © 2020-2023  润新知