zoukankan      html  css  js  c++  java
  • SpringMVC整合Apache Shiro

    关于什么是Shiro,可以查看这篇文章http://www.cnblogs.com/Laymen/articles/6117751.html

    一、添加maven依赖

    <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-web</artifactId>
         <version>1.2.3</version>
    </dependency>
    
    <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.2.3</version>
    </dependency>

    如果嫌麻烦可以直接添加shiro-all的依赖

    二、web.xml配置Shiro的过滤器

    要让shiro拦截web的所有请求那么需要我们在web.xml中配置Shrio和web项目整合提供的filter,配置如下:

    <!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->
        <!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> -->
        <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->
        <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->
        <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
            <init-param>
                <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
                <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    三、Application-shiro.xml配置

    <?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:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd   http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
        <util:properties id="securityCode" location="classpath:config/security-management.properties"/>
    
        <bean id="jdbcRealm" class="com.layman.study.core.shiro.realm.LaymanJdbcRealm">
            <property name="permissionsLookupEnabled" value="true"/>
            <property name="name" value="jdbcRealm"/>
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <property name="storedCredentialsHexEncoded" value="true"/>
                    <property name="hashAlgorithmName" value="MD5"/>
                </bean>
            </property>
            <property name="authorizationCachingEnabled" value="true"/>
            <property name="authorizationCacheName" value="shiro_authorization_cache"/>
        </bean>
    
        <bean id="customAuthorizationFilter" class="com.layman.study.core.shiro.filter.CustomAuthorizationFilter">
            <property name="ignoreList">
                <list>
                    <value>/</value>
                    <value>/login</value>
                    <value>/logout</value>
                    <value>/index</value>
                    <value>/user/register</value>
                </list>
            </property>
        </bean>
    
        <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
        <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
        <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="jdbcRealm"/>
            <property name="cacheManager">
                <bean class="com.layman.study.core.shiro.cache.CustomCacheManager"/>
            </property>
            <property name="sessionManager">
                <bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
                    <property name="deleteInvalidSessions" value="true"/>
                    <property name="sessionDAO">
                        <bean class="com.layman.study.core.shiro.session.CustomSessionDao"/>
                    </property>
                </bean>
            </property>
        </bean>
    
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <property name="loginUrl" value="/login"/>
            <property name="successUrl" value="/index"/>
            <property name="unauthorizedUrl" value="/static/page/404.html"/>
    
            <property name="filters">
                <util:map>
                    <entry key="customAuthorizationFilter" value-ref="customAuthorizationFilter"/>
                </util:map>
            </property>
    
            <property name="filterChainDefinitionMap">
                <map>
                    <entry key="/index" value="authc"/>
                    <entry key="/**" value="customAuthorizationFilter"/>
                </map>
            </property>
        </bean>
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    </beans>

    bean的id为shiroFilter的就是web.xml中代理filter需要的spring bean,这个bean中可以看到:

    1.loginUrl:指定了当拦截到请求时如果没有登录那么就会跳转到这个属性指定的地址让用户进行登录操作。

    2.successUrl:指定了用户登录成功后跳转的地址

    3.unauthorizedUrl:用户的请求被判断为没有权限时会跳转到这个属性指定的页面

    4.filters:指定过滤器链,可以是默认提供的过滤器也可以指定自定义的过滤器,在这里我指定的是自定义的过滤器com.layman.study.core.shiro.filter.CustomAuthorizationFilter

    5.filterChainDefinitionMap:指定过滤器拦截的urlpatten,<entry key="/index" value="authc"/>这句就是使用了Shiro默认给我们提供的一个过滤器org.apache.shiro.web.filter.authc.FormAuthenticationFilter(用户访问/index这个路劲时是需要登录的)<entry key="/**" value="customAuthorizationFilter"/>定义了我们自定义过滤器拦截所有的请求。

    Shrio为我们提供的默认过滤器:

    /**
    
    * Shiro-1.2.2内置的FilterChain
    
    * @see =========================================================================================================
    
    * @see 1)Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时)
    
    * @see   故filterChainDefinitions的配置顺序为自上而下,以最上面的为准
    
    * @see 2)当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用
    
    * @see   自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称
    
    * @see   anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter
    
    * @see   authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    
    * @see   authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    
    * @see   logout-------------org.apache.shiro.web.filter.authc.LogoutFilter
    
    * @see   noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter
    
    * @see   perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter
    
    * @see   port---------------org.apache.shiro.web.filter.authz.PortFilter
    
    * @see   rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
    
    * @see   roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    
    * @see   ssl----------------org.apache.shiro.web.filter.authz.SslFilter
    
     *@see   user---------------org.apache.shiro.web.filter.authz.UserFilter
    
    * @see =========================================================================================================
    
    * @see 3)通常可将这些过滤器分为两组
    
    * @see   anon,authc,authcBasic,user是第一组认证过滤器
    
    * @see   perms,port,rest,roles,ssl是第二组授权过滤器
    
    * @see   注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的
    
    * @see   user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe
    
    * @see   说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc
    
    * @see ==========================================================================================================
    
    * @see 4)举几个例子
    
    * @see   /admin=authc,roles[admin]      表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求
    
    * @see   /edit=authc,perms[admin:edit]  表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求
    
    * @see   /home=user                     表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起'/home'请求
    
    * @see ==========================================================================================================
    
    * @see 5)各默认过滤器常用如下(注意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配)
    
    * @see   /admins/**=anon             无参,表示可匿名使用,可以理解为匿名用户或游客
    
    * @see   /admins/user/**=authc       无参,表示需认证才能使用
    
    * @see   /admins/user/**=authcBasic  无参,表示httpBasic认证
    
    * @see   /admins/user/**=user        无参,表示必须存在用户,当登入操作时不做检查
    
    * @see   /admins/user/**=ssl         无参,表示安全的URL请求,协议为https
    
    * @see   /admins/user/**=perms[user:add:*]
    
    * @see    参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms["user:add:*,user:modify:*"]
    
    * @see    当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法
    
    * @see   /admins/user/**=port[8081]
    
    * @see     当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString
    
    * @see     其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数
    
    * @see   /admins/user/**=rest[user]
    
    * @see       根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等
    
    * @see   /admins/user/**=roles[admin]
    
    * @see     参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如/admins/user/**=roles["admin,guest"]
    
    * @see     当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法
    View Code

    com.layman.study.core.shiro.filter.CustomAuthorizationFilter自定义过滤器源码:

    public class CustomAuthorizationFilter extends AuthorizationFilter {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthorizationFilter.class);
    
        private static final String AJAX_REQUEST = "XMLHttpRequest";
    
        private List<String> ignoreList;
    
        public void setIgnoreList(List<String> ignoreList) {
            this.ignoreList = ignoreList;
        }
    
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            String path = getPathWithinApplication(request);
            //去除.json的后缀
            if (path.endsWith(".json")) {
                path = path.substring(0, path.length() - 5);
            }
            //忽略(通过)特定后缀的访问
            String ext = getExt(path);
            if (ext != null && !ext.equals(".json")) {
                return true;
            }
    
            if (!CollectionUtils.isEmpty(ignoreList) && ignoreList.contains(path)) {
                return true;
            }
    
            //url改写 如/site/add  改为/site:add,就是把后面的操作(方法)区分出来
            int i = path.lastIndexOf('/');
            if (i > 0) {
                path = path.substring(0, i) + ":" + path.substring(i + 1, path.length());
            } else if (i == 0) {
                path = "/:" + path.substring(1, path.length());
            }
            //进行权限验证
            Subject subject = getSubject(request, response);
            boolean isPermitted = false;
            try {
                isPermitted = subject.isPermitted(path);
            } catch (Exception e) {
                LOGGER.error("判断权限出错:{}", e);
            }
            LOGGER.info(path + ":" + isPermitted);
            return isPermitted;
        }
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
            Subject subject = getSubject(request, response);
            if (StringUtils.isEmpty(subject.getPrincipal())) {
                String header = WebUtils.toHttp(request).getHeader("x-requested-with");
                //当shiro的session超时时 如果用户发起了ajax请求这个时候页面并没有跳转到我们的配置的登录页面,
                // 所以在后端判断了下如果登录超时并是ajax请求就发送一个错误码,在页面使用全局ajax配置判断返回码进行跳转操作
                if (AJAX_REQUEST.equals(header)) {
                    WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
                } else {
                    saveRequestAndRedirectToLogin(request, response);
                }
            } else {
                String unauthorizedUrl = getUnauthorizedUrl();
                String path = getPathWithinApplication(request);
                //如果以.json的形式访问 则返回.json形式的提醒
                if (path.endsWith(".json")) {
                    unauthorizedUrl += ".json";
                }
                if (org.apache.shiro.util.StringUtils.hasText(unauthorizedUrl)) {
                    WebUtils.issueRedirect(request, response, unauthorizedUrl);
                } else {
                    WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
                }
            }
            return false;
        }
    
        /**
         * 获取后缀  .js .css等
         *
         * @param path
         * @return
         */
        private String getExt(String path) {
            if (path != null) {
                int index = path.lastIndexOf(".");
                if (index >= 0) {
                    return path.substring(index, path.length());
                }
            }
            return null;
        }
    }
    View Code

    过滤器拦截到请求后会委托给SecurityManager进行权限验证,SecurityManager配置:

    <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
        <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
        <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="jdbcRealm"/>
            <property name="cacheManager">
                <bean class="com.layman.study.core.shiro.cache.CustomCacheManager"/>
            </property>
            <property name="sessionManager">
                <bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
                    <property name="deleteInvalidSessions" value="true"/>
                    <property name="sessionDAO">
                        <bean class="com.layman.study.core.shiro.session.CustomSessionDao"/>
                    </property>
                </bean>
            </property>
        </bean>

    自定义realm

    在进行权限验证的时候会通过realm去查询身份和权限信息,这里我使用了一个自定义的realm(com.layman.study.core.shiro.realm.LaymanJdbcRealm)去mysql中查询用户身份信息和权限信息,relam配置如下:

    <bean id="jdbcRealm" class="com.layman.study.core.shiro.realm.LaymanJdbcRealm">
            <property name="permissionsLookupEnabled" value="true"/>
            <property name="name" value="jdbcRealm"/>
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <property name="storedCredentialsHexEncoded" value="true"/>
                    <property name="hashAlgorithmName" value="MD5"/>
                </bean>
            </property>
            <property name="authorizationCachingEnabled" value="true"/>
            <property name="authorizationCacheName" value="shiro_authorization_cache"/>
        </bean>

    1.credentialsMatcher:mysql中用户信息的密码我进行了一次MD5散列算法,不是保存的明文,所以在realm中我们可以通过这个属性指定我们需要的散列算法,同时在进行身份验证的时候我们还可以指定参与散列算法的salt,这个值是用户注册时一起保存在数据库中的。shiro在身份验证的时候对salt的操作是使用ByteSource类将salt转会为byte[],所以在进行散列算法的时候需要使用shiro提供的算法类在提供了salt时进行相同的处理,用户密码散列算法如下:

    //使用shiro提供的散列算法类进行散列计算
            String pwd = new Md5Hash(user.getPassword(), user.getSalt(), 1).toString();
    
    /**
         * 随机产生的salt
         *
         * @return
         */
        private String getRandomSalt() {
            //shiro提供的一个随机数生存类
            RandomNumberGenerator gen = new SecureRandomNumberGenerator();
            ByteSource salt = gen.nextBytes();//返回的是一个SimpleByteSource实例
            return salt.toString();//SimpleByteSource类覆写了toString方法,其实质就是调用了它的toBase64()方法
        }

    或者使用SimpleHashRequest类,指定需要的散列算法

    2.authorizationCacheName:指定权限缓存名称,shiro针对用户每个请求都会去判断是否有权限,如果使用了缓存,CacheManager会调用getCache(String name)(方法这里的name值就是authorizationCacheName的值)获得一个Cache实例进行缓存操作。

    LaymanJdbcRealm源码

    @Component
    public class LaymanJdbcRealm extends JdbcRealm {
    
        @Autowired
        private SysUserService userService;
    
        @Autowired
        private SysPermissionService permissionService;
    
        @Autowired
        private SysRoleService roleService;
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String userName = (String) token.getPrincipal();
            SysUser user = userService.getUserByName(userName);
    
            if (null == user || null == user.getPassword()) {
                return null;
            }
    
            return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), user.getNickName());
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            Set<String> roles = new HashSet<String>();
            Set<String> permissions = new HashSet<String>();
    
            String userName = (String) principals.getPrimaryPrincipal();
            SysUser user = userService.getUserByName(userName);
    
            List<SysRole> roleList = roleService.getRoleList(user.getId());
            if (null != user) {
                for (SysRole sysRole : roleList) {
                    roles.add(sysRole.getId().toString());
                    List<SysPermission> permissionList = permissionService.getPermissionListByRoleId(sysRole.getId());
                    for (SysPermission permission : permissionList) {
                        permissions.add(permission.getPermissionCode());
                    }
                }
            }
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            authorizationInfo.setStringPermissions(permissions);
    
            return authorizationInfo;
        }
    }
    View Code

    自定义缓存(Cache)策略(使用的redis作为缓存)

    com.layman.study.core.shiro.cache.CustomCacheManager源码:
    public class CustomCacheManager implements CacheManager {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomCacheManager.class);
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Value("#{securityCode['security.management.code']}")
        private String securityManagementCode;
    
        private static ConcurrentMap<String, CustomRedisCache> cacheMap = new ConcurrentHashMap<String, CustomRedisCache>();
    
        public <K, V> Cache<K, V> getCache(String name) throws CacheException {
            CustomRedisCache cache;
            cache = cacheMap.get(name);
            if (null != cache) {
                LOGGER.info("从cacheMap中根据名字:{},获取到了cache", name);
            }
            cache = new CustomRedisCache(redisTemplate, securityManagementCode);
            cacheMap.put(name, cache);
            return cache;
        }
    }
    View Code
    
    
    CustomCacheManager在spring实例化时就会根据realm中配置的信息调用getCache(String name)方法查询Cache实例,如果没有获取到就创建一个Cache返回并保存到自己的ConcurrentMap中,Cache实例才是我们对权限信息进行缓存操作的具体实现,这里我使用的是redis作为缓存容器
    那么就需要自定一个Cache对redis进行操作,CustomRedisCache源码如下:
    public class CustomRedisCache<K, V> implements Cache<K, V> {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomRedisCache.class);
    
        private static final String SHIRO_CACHE_PREFIX = "shiro-cache:";
    
        private String SECURITY_MANAGEMENT_CODE = "default";
    
        private RedisTemplate redisTemplate;
    
        public CustomRedisCache() {
        }
    
        public CustomRedisCache(RedisTemplate redisTemplate, String securityManagementCode) {
            this.redisTemplate = redisTemplate;
            this.SECURITY_MANAGEMENT_CODE = securityManagementCode;
        }
    
        @Override
        public V get(K key) throws CacheException {
            if (null == key) {
                return null;
            }
            try {
                V result = (V) redisTemplate.opsForValue().get((K) (SHIRO_CACHE_PREFIX + key));
                LOGGER.info("根据key[{}]获得缓存{}", key, result);
                return result;
            } catch (Exception e) {
                LOGGER.error("获取shiro缓存错误:", e);
                throw new CacheException(e);
            }
        }
    
        @Override
        public V put(K key, V value) throws CacheException {
            try {
                redisTemplate.opsForValue().set((K) (SHIRO_CACHE_PREFIX + key), value, 10, TimeUnit.MINUTES);
                LOGGER.info("存储shiro的缓存信息,key:{},value:{}", key, value);
                return value;
            } catch (Exception e) {
                LOGGER.error("存储shiro缓存发生错误key={},value={},error=", key, value, e);
                throw new CacheException(e);
            }
        }
    
        @Override
        public V remove(K key) throws CacheException {
            try {
                V deleteObj = get(key);
                redisTemplate.delete((K) (SHIRO_CACHE_PREFIX + key));
                LOGGER.info("删除shrio缓存key={},value={}", key, deleteObj);
                return deleteObj;
            } catch (Exception e) {
                LOGGER.error("删除shiro缓存发生错误:{}", e);
                throw new CacheException(e);
            }
        }
    
        @Override
        public void clear() throws CacheException {
            try {
                redisTemplate.delete(SHIRO_CACHE_PREFIX + "*");
                LOGGER.info("成功清楚shiro所有缓存");
            } catch (Exception e) {
                LOGGER.error("删除所有shiro缓存出错:{}", e);
                throw new CacheException(e);
            }
        }
    
        @Override
        public int size() {
            try {
                Long size = redisTemplate.opsForValue().size(SHIRO_CACHE_PREFIX + "*");
                LOGGER.info("shiro缓存大小:{}", size);
                return size.intValue();
            } catch (Exception e) {
                LOGGER.error("获取食肉缓存大小错误:{}", e);
                throw new CacheException(e);
            }
        }
    
        @Override
        public Set<K> keys() {
            try {
                Set<K> keys = redisTemplate.keys((K) (SHIRO_CACHE_PREFIX + "*"));
                if (CollectionUtils.isEmpty(keys)) {
                    return Collections.emptySet();
                } else {
                    Set<K> newKeys = new HashSet<K>();
                    for (K key : keys) {
                        newKeys.add(key);
                    }
                    LOGGER.info("获取shiro缓存的所有key:{}", newKeys);
                    return newKeys;
                }
            } catch (Exception e) {
                LOGGER.error("获取shiro缓存的keys错误:{}", e);
                throw new CacheException(e);
            }
        }
    
        @Override
        public Collection<V> values() {
            try {
                Set<K> keys = redisTemplate.keys(SHIRO_CACHE_PREFIX + "*");
                if (!CollectionUtils.isEmpty(keys)) {
                    List<V> values = new ArrayList<V>(keys.size());
                    for (K key : keys) {
                        V value = get(key);
                        if (value != null) {
                            values.add(value);
                        }
                    }
                    LOGGER.info("获取shiro缓存的所有value:{}", values);
                    return Collections.unmodifiableList(values);
                } else {
                    return Collections.emptyList();
                }
            } catch (Throwable t) {
                LOGGER.error("获取shiro缓存的values错误:{}", t);
                throw new CacheException(t);
            }
        }
    }
    View Code
    
    

    自定义SessionDao

    Shiro提供安全框架界独一无二的东西:一个完整的企业级Session 解决方案,可以为任意的应用提供session支持,包括web和非web应用,并且无需部署你的应用程序到Web 容器或使用EJB容器。

    关于SessionManager,
    SessionManager是用来管理Session的组件,包括:创建,删除,inactivity(失效)及验证,等等。SessionManager 也是一个由SecurityManager 维护的顶级组件。shiro提供了默认的SessionManager实现,一般没有必要自定义这个。

    但是可以通过设置他的属性来控制Session管理策略:

       (1)设置Sessioin的过期时间;Shiro 的SessionManager 实现默认是30 分钟会话超时。你可以设置SessionManager 默认实现的globalSessionTimeout 属性来为所有的会话定义默认的超时时间。

     (2)Sessioin的事件监听;你可以实现SessionListener 接口(或扩展易用的SessionListenerAdapter)并与相应的会话操作作出反应。

      。。。。。。。等

     关于SessionDAO,每当一个会话被创建或更新时,它的数据需要持久化到一个存储位置以便它能够被稍后的应用程序访问,实现这个功能的组件就是SessionDAO。你能够实现该接口来与你想要的任何数据存储进行通信。这意味着你的会话数据可以驻留在内存中,文件系统,关系数据库或NoSQL 的数据存储,或其他任何你需要的位置。

    com.layman.study.core.shiro.session.CustomSessionDao源码:
    public class CustomSessionDao extends AbstractSessionDAO {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomSessionDao.class);
    
        private static final String SHIRO_SESSION_PREFIX = "shiro-session:";
    
        @Value("#{securityCode['security.management.code']}")
        private String securityManagementCode;
    
        @Autowired
        private RedisTemplate<String, byte[]> redisTemplate;
    
        @Override
    
        protected Serializable doCreate(Session session) {
            Serializable sessionId = this.getSessionIdGenerator().generateId(session);
            this.assignSessionId(session, sessionId);
            String key = this.buildRedisKey(sessionId);
            redisTemplate.opsForValue().set(key, SerializationUtils.serialize(session), session.getTimeout(), TimeUnit.MILLISECONDS);
            return sessionId;
        }
    
        @Override
        protected Session doReadSession(Serializable serializable) {
            if (null == serializable) {
                return null;
            }
            String key = this.buildRedisKey(serializable);
            byte[] value = redisTemplate.opsForValue().get(key);
            return (Session) SerializationUtils.deserialize(value);
        }
    
        @Override
        public void update(Session session) throws UnknownSessionException {
            redisTemplate.opsForValue().set(this.buildRedisKey(session.getId()), SerializationUtils.serialize(session), session.getTimeout(), TimeUnit.MILLISECONDS);
        }
    
        @Override
        public void delete(Session session) {
            redisTemplate.delete(this.buildRedisKey(session.getId()));
        }
    
        @Override
        public Collection<Session> getActiveSessions() {
            Set<Session> sessions = new HashSet<Session>();
            Set<String> keys = redisTemplate.keys(this.buildRedisKey("*"));
    
            if (!CollectionUtils.isEmpty(keys)) {
                for (String key : keys) {
                    Session s = (Session) SerializationUtils.deserialize(redisTemplate.opsForValue().get(key));
                    sessions.add(s);
                }
            }
            return sessions;
        }
    
        private String buildRedisKey(Serializable sessionId) {
            return SHIRO_SESSION_PREFIX + securityManagementCode + ":" + sessionId;
        }
    }
    View Code

     开启注解

    <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>

    @RequiresPermissions() @RequiresAuthentication() @RequiresRoles() @RequiresUser() @RequiresGuest()的使用

    引入Tag

      <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

    The guest tag

      guest 标签将显示它包含的内容,仅当当前的Subject 被认为是‘guest’时。‘guest’是指没有身份ID 的任何Subject。也就是说,我们并不知道用户是谁,因为他们没有登录并且他们没有在上一次的访问中被记住(RememberMe 服务), guest 标签与user 标签逻辑相反。例子:

    <shiro:guest>
    
      Hi there! Please <a href="login.jsp">Login</a> or <a   href="signup.jsp">Signup</a>today!
    
    </shiro:guest>
    The user tag

      user 标签将显示它包含的内容,仅当当前的Subject 被认为是‘user’时。‘user’在上下文中被定义为一个已知身份ID的Subject,或是成功通过身份验证及通过‘RememberMe’服务的。请注意这个标签在语义上与authenticated 标签是不同的,authenticated 标签更为严格。usre 标签与guest 标签逻辑相反。

    The authenticated tag

      仅仅只当当前用户在当前会话中成功地通过了身份验证authenticated 标签才会显示包含的内容。它比‘user’标签更为严格。它在逻辑上与‘notAuthenticated’标签相反。

    The notAuthenticated tag

      notAuthenticated 标签将会显示它所包含的内容,如果当前Subject 还没有在其当前会话中成功地通过验证。

    The principal tag

      principal 标签将会输出Subject 的主体(标识属性)或主要的属性。

    The hasRole tag

      hasRole 标签将会显示它所包含的内容,仅当当前Subject 被分配了具体的角色。 hasRole 标签与lacksRole 标签逻辑相反。 例如:

    <shiro:hasRole name="administrator">
        <a href="admin.jsp">Administer the system</a>
    </shiro:hasRole>
    The lacksRole tag

      lacksRole 标签将会显示它所包含的内容,仅当当前Subject 未被分配具体的角色

    The hasAnyRoles tag

    hasAnyRole 标签将会显示它所包含的内容,如果当前的Subject 被分配了任意一个来自于逗号分隔的角色名列表中的具体角色。例如:

    <shiro:hasAnyRoles name="developer, project manager, administrator">
        You are either a developer, project manager, or administrater.
    </shiro:hasAnyRoles>
    The hasPermission tag

      hasPermission 标签将会显示它所包含的内容,仅当当前Subject“拥有”(蕴含)特定的权限。也就是说,用户具有特定的能力。hasPermission 标签与lacksPermission 标签逻辑相反。例如:

    <shiro:hasPermission name="user:create">
        <a href="createUser.jsp">Create a new User</a>
    </shiro:hasPermission>
    The lacksPermission tag

      lacksPermission 标签将会显示它所包含的内容,仅当当前Subject 没有拥有(蕴含)特定的权限。也就是说,用户没有特定的能力。

  • 相关阅读:
    libyuv编译(各平台)【转】
    /dev/mem可没那么简单【转】
    嵌入式Linux上通过boa服务器实现cgi/html的web上网【转】
    linux select函数详解【转】
    dpkg: error processing package bluez (--configure) 解决方法【转】
    Linux内核中的中断栈与内核栈的补充说明【转】
    Linux内核中断引入用户空间(异步通知机制)【转】
    用户空间与内核空间,进程上下文与中断上下文[总结]【转】
    linux 路由表设置 之 route 指令详解【转】
    自己动手做聊天机器人 一-涉及知识【转】
  • 原文地址:https://www.cnblogs.com/Laymen/p/6118066.html
Copyright © 2011-2022 走看看