ShiroFilter设计原理与实现

2023-11-02

Shiro提供了与Web集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,ShiroFilter类似于如Strut2/SpringMVC这种web框架的前端控制器,其是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL是否需要登录/权限等工作。
而要在spring中使用Shiro的话,可在web.xml中配置一个DelegatingFilterProxy,DelegatingFilterProxy作用是自动到Spring容器查找名字为shiroFilter(filter-name)的bean并把所有Filter的操作委托给它。

首先是在web.xml中配置DelegatingFilterProxy:

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <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>

配置好DelegatingFilterProxy后,下面只要再把ShiroFilter配置到Spring容器(此处为Spring的配置文件)即可:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
</bean>

可以看到我们使用了ShiroFilterFactoryBean来创建shiroFilter,这里用到了Spring中一种特殊的Bean——FactoryBean。当需要得到名为”shiroFilter“的bean时,会调用其getObject()来获取实例。下面我们通过分析ShiroFilterFactoryBean创建实例的过程来探究Shiro是如何实现安全拦截的:

 public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }

其中调用了createInstance()来创建实例:

protected AbstractShiroFilter createInstance() throws Exception {

        // 这里是通过FactoryBean注入的SecurityManager(必须)
        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

可以看到创建SpringShiroFilter时用到了两个组件:SecurityManager和ChainResolver。

  • SecurityManager:我们知道其在Shiro中的地位,类似于一个“安全大管家”,相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher,是Shiro的心脏,所有具体的交互都通过SecurityManager进行控制,它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
  • ChainResolver:Filter链解析器,用来解析出该次请求需要执行的Filter链。
  • PathMatchingFilterChainResolver:ChainResolver的实现类,其中还包含了两个重要组件FilterChainManager、PatternMatcher。
  • FilterChainManager:管理Filter和Filter链,配合PathMatchingFilterChainResolver解析出Filter链。
  • PatternMatcher:用来进行请求路径匹配,默认为Ant风格的路径匹配。

先有一个大体的了解,那么对于源码分析会有不少帮助。下面会对以上两个重要的组件进行分析,包括PathMatchingFilterChainResolver和FilterChainManager。首先贴一段ShiroFilter的在配置文件中的定义:


<!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="unauthorizedUrl" value="/special/unauthorized" />
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter" />
                <entry key="logout" value-ref="logoutFilter" />
                <entry key="ssl" value-ref="sslFilter"></entry>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /resources/** = anon
                /plugin/** = anon
                /download/** = anon
                /special/unauthorized = anon
                /register = anon
                /login = ssl,authc
                /logout = logout
                /admin/** = roles[admin]

                /** = user
            </value>
        </property>
    </bean>

再来看看PathMatchingFilterChainResolver和FilterChainManager的创建过程:

 protected FilterChainManager createFilterChainManager() {

        // 默认使用的FilterChainManager是DefaultFilterChainManager
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        // DefaultFilterChainManager默认会注册的filters(后面会列出)
        Map<String, Filter> defaultFilters = manager.getFilters();

        // 将ShiroFilterFactoryBean配置的一些公共属性(上面配置的loginUrl,successUrl,unauthorizeUrl)应用到默认注册的filter上去
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        // 处理自定义的filter(上面配置的filters属性),步骤类似上面
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                // 将Filter添加到manager中去,可以看到对于Filter的管理是依赖于FilterChainManager的
                manager.addFilter(name, filter, false);
            }
        }

        // 根据FilterChainDefinition的配置来构建Filter链(上面配置的filterChainDefinitions属性)
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                // 后面会分析该步的源码,功能上就是创建Filter链
                manager.createChain(url, chainDefinition);
            }
        }

        return manager;
    }

下面有必要来看看DefaultFilterChainManager的源码,分析一下上面调用到的方法。先来看看他的几个重要的属性:

    private FilterConfig filterConfig;

    private Map<String, Filter> filters; //pool of filters available for creating chains

    private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain

其中filterConfig仅在初始化Filter时有效,而我们自定义的Filter都不是init的,所以该属性可以暂时忽略()。
而后面两张map就重要了:filters中缓存了所有添加的filter,filterChains则缓存了所有的filterChain。其中前者的key是filter name,value是Filter。而后者的key是chain name,value是NamedFilterList。
有的童鞋可能会问NamedFilterList是怎么样的结构呢,你可以把它当成List,这样就好理解了吧。下面再分析刚才createFilterChainManager()中调用过的manager的几个方法:

  • addFilter(缓存filter让manager来管理)
public void addFilter(String name, Filter filter, boolean init) {
        addFilter(name, filter, init, true);
    }

    protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
        Filter existing = getFilter(name);
        if (existing == null || overwrite) {
            if (filter instanceof Nameable) {
                ((Nameable) filter).setName(name);
            }
            if (init) {
                initFilter(filter);
            }
            this.filters.put(name, filter);
        }
    }

将filter缓存到filters这张map里,不管是默认注册的还是自定义的都需要FilterChainManager来统一管理。
* createChain:创建filterChain并将定义的filter都加进去

 // chainName就是拦截路径"/resources/**",chainDefinition就是多个过滤器名的字符串
    public void createChain(String chainName, String chainDefinition) {
        if (!StringUtils.hasText(chainName)) {
            throw new NullPointerException("chainName cannot be null or empty.");
        }
        if (!StringUtils.hasText(chainDefinition)) {
            throw new NullPointerException("chainDefinition cannot be null or empty.");
        }

        if (log.isDebugEnabled()) {
            log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
        }

        // 先分离出配置的各个filter,比如 
        // "authc, roles[admin,user], perms[file:edit]" 分离后的结果是:
        // { "authc", "roles[admin,user]", "perms[file:edit]" }
        String[] filterTokens = splitChainDefinition(chainDefinition);

        // 进一步分离出"[]"内的内容,其中nameConfigPair是一个长度为2的数组
        // 比如 roles[admin,user] 经过解析后的nameConfigPair 为{"roles", "admin,user"}
        for (String token : filterTokens) {
            String[] nameConfigPair = toNameConfigPair(token);

            // 得到了 拦截路径、filter以及可能的"[]"中的值,那么执行addToChain
            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
        }
    }
  • addToChain
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
        if (!StringUtils.hasText(chainName)) {
            throw new IllegalArgumentException("chainName cannot be null or empty.");
        }
        Filter filter = getFilter(filterName);
        if (filter == null) {
            throw new IllegalArgumentException("There is no filter with name '" + filterName + "' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " + "filter with that name/path has first been registered with the addFilter method(s).");
        }

        // 将匹配关系注册到filter中,后续会提到
        applyChainConfig(chainName, filter, chainSpecificFilterConfig);

        // 确保chain已经被加到filterChains这张map中了
        NamedFilterList chain = ensureChain(chainName);
        // 将该filter加入当前chain
        chain.add(filter);
    }

至此,FilterChainManager就创建完了,它无非就是缓存了两张map,没有什么逻辑上的操作。下面将FilterChainManager设置到PathMatchingFilterChainResolver中。PathMatchingFilterChainResolver实现了FilterChainResolver接口,该接口中只定义了一个方法:

FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);

通过解析请求来得到一个新的FilterChain。而PathMatchingFilterChainResolver实现了该接口,依靠了FilterChainManager中保存的chainFilters和filters这两张map来根据请求路径解析出相应的filterChain,并且和originalChain组合起来使用。下面具体看看PathMatchingFilterChainResolver中的实现:

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        // 得到 FilterChainManager 
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);

        // chainNames就是刚定义的filterChains的keySet,也就是所有的路径集合(比如:["/resources/**","/login"])
        for (String pathPattern : filterChainManager.getChainNames()) {

            // 请求路径是否匹配某个 定义好的路径:
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " + "Utilizing corresponding filter chain...");
                }
                // 找到第一个匹配的Filter链,那么就返回一个ProxiedFilterChain
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }

这里返回只有两种情况,要么是null,要么就是一个ProxiedFilterChain。返回null并不表示中断FilterChain,而是只用originChain。而关于ProxiedFilterChain,它实现了FilterChain,内部维护了两份FilterChain(其实一个是FilterChain,另一个是List)
FilterChain也就是web.xml中注册的Filter形成的FilterChain,我们称之为originChain。而另一个List则是我们在Shiro中注册的Filter链了,下面看看ProxiedFilterChain中关于doFilter(…)的实现:

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

可以看到,它会先执行Shiro中执行的filter,然后再执行web.xml中的Filter。不过要注意的是,需要等到originChain执行到ShiroFilter之后才会执行Shiro中的Filter链。
至此,两个组件的创建过程差不多都介绍完了,那么当这两个组件创建完毕后,是如何工作的呢?
先从ShiroFilter入手,因为它是总的拦截器,看看其中的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);

            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    // 其实需要关心的就在这里
                    // touch一下session
                    updateSessionLastAccessTime(request, response);
                    // 执行Filter链
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

跟进executeChain(…)方法:

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }

如何得到FilterChain的呢?如果你认真的看到这里,那么你应该不难想到其中肯定利用了刚才注册的ChainResolver:

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

猜对了~并且也验证了当resolver.getChain(…)返回null时,直接使用originChain了。然后执行返回的FilterChain的doFilter(…)方法。这个过程我们再脱离代码来分析一下:当我们从浏览器发出一个请求,究竟发生了什么?
这里只站在Filter的层面来分析。服务器启动后,读取web.xml中的filter、filter-mapping节点后组成FilterChain,对请求进行拦截。拦截的顺序按照filter节点的定义顺序,Shiro利用ShiroFilter来充当一个总的拦截器来分发所有需要被Shiro拦截的请求,所以我们看到在Shiro中我们还可以自定义拦截器。ShiroFilter根据它在拦截器中的位置,只要执行到了那么就会暂时中断原FilterChain的执行,先执行Shiro中定义的Filter,最后再执行原FilterChian。可以打个比方,比如说本来有一条铁链,一直蚂蚁从铁链的开端往末端爬,其中某一环叫ShiroFilter,那么当蚂蚁爬到ShiroFilter这一环时,将铁链打断,并且接上另一端铁链(Shiro中自定义的Filter),这样就构成了一条新的铁链。然后蚂蚁继续爬行(后续的执行过程)。

到这里,我们已经根据请求路径找到了一条Filter链(originChain + shiroChain),之后就是对链上的Filter做doFilter,其中关于如何
就是filter后配置的[]部分是如何生效的,我们可以看PathMatchingFilter中的Prehandle(…)方法:

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
            if (log.isTraceEnabled()) {
                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
            }
            return true;
        }

        // appliedPaths中保存了该filter中能拦截的路径和该路径配置的key-value对,比如{key="/admin/**", value="[admin]"}
        for (String path : this.appliedPaths.keySet()) {
            // 首先是匹配路径
            if (pathsMatch(path, request)) {
                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
                // 然后开始验证“[]”中的字符串
                Object config = this.appliedPaths.get(path);
                return isFilterChainContinued(request, response, path, config);
            }
        }

        //no path matched, allow the request to go through:
        return true;
    }

下面跟踪isFilterChainContinued(…):

private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
                                           String path, Object pathConfig) throws Exception {

        if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
            if (log.isTraceEnabled()) {
                 // log
            }
            return onPreHandle(request, response, pathConfig);
        }

        if (log.isTraceEnabled()) {
            // log
        }
        return true;
    }

基本也就是交给onPreHandle(…)来处理,所以一般需要验证”[]“中字符串的filter都会扩展这个方法,比如AccessControlFilter:

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }

而RolesAuthorizationFilter中:

public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;

        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        Set<String> roles = CollectionUtils.asSet(rolesArray);
        return subject.hasAllRoles(roles);
    }

最后附上默认注册的filters:

public enum DefaultFilter {

    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ShiroFilter设计原理与实现 的相关文章

  • SpringBoot 整合shiro框架

    网上有很多整合shiro的博客分享 但是貌似没找到一个完整 并且能够实现的 不是包的问题 就是代码的问题 也可能是自己的问题 或者版本的问题 所以 整理了一版自己已应用的 maven依赖
  • shiro标签页点击报错: No SecurityManager accessible to the calling code...

    shiro按钮配置标签报错问题 问题 最近的项目需要将按钮也动态配置进去 我按照网上的步骤加上shiro的taglib标签 然后在该页面的某个按钮上加上
  • Shiro权限框架-Realm缓存机制(6)

    1 Realm缓存机制意义 在上面我们自定了自己的realm 但是我们发现 在认证和授权的时候 程序需要频繁的访问数据库 这样对于数据库的压力可想而知 那我们怎么处理呢 2 Realm缓存机制实现思路 1 缓存机制图解 2 原理分析 此时我
  • Shiro实战学习笔记(2)-自定义Realm

    1 自定义realm package org tzb realm import org apache shiro authc AuthenticationException import org apache shiro authc Aut
  • Shiro权限框架-限制密码重试次数(8)

    1 实现原理 保证原子性 单系统 AtomicLong计数 集群系统 RedissionClient提供的RAtomicLong计数 1 获取系统中是否已有登录次数缓存 缓存对象结构预期为 用户名 登录次数 2 如果之前没有登录缓存 则创建
  • shiro多项目跳转用户身份失效问题排查

    shiro多项目跳转用户身份失效问题排查 1 身份失效问题 最近在项目中遇到过一个问题 统一登录系统中有各个子系统的模块 可点击子系统模块进行跳转 如下图所示 如上图 当用户点击子系统B新窗口打开时 实现跳转成功 当再回到原统一登录系统页面
  • 用户登录的详细流程(二)JWT生成token登录

    JWT生成token登录 1 jwt的构成 1 header 2 payload 3 signature 2 token的登陆原理 3 在实际中如何应用token 1 设置token的生成代码 2 如何从token中获取有用的信息 3 验证
  • 源码分析shiro认证授权流程

    1 shiro介绍 Apache Shiro是一个强大易用的Java安全框架 提供了认证 授权 加密和会话管理等功能 认证 用户身份识别 常被称为用户 登录 授权 访问控制 密码加密 保护或隐藏数据防止被偷窥 会话管理 每用户相关的时间敏感
  • shiro使用自定义realm实现数据认证

    自定义realm实现数据认证 在开发中 有时会与一些nosql或者其他地方保存的数据进行认证 这时候 shiro定义的那些realm类可能不能满足实际的功能需求 这时候我们可以通过自定义一个realm来沟通这些数据 实现认证和权限控制 首先
  • shiro实现基于redis的sessionDao

    shiro实现基于redis的sessionDao 将session持久化到数据库的一个关键步骤是对session对象的序列化和反序列化操作 另外在使用redis保存session时一定要设置过期时间 或在编码中检查过期并及时删除缓存 否则
  • shiro通过注解方式自定义控制接口无需认证访问的解决过程

    1 需求背景 用过Shiro的小伙伴都知道 shiro提供两种权限控制方式 通过过滤器或注解 我们项目是springboot vue前后分离项目 后台对于权限控制一直使用的是过滤器的方式 并且还有自定义的过滤器 大概如下 Bean shir
  • Shiro错误之No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util

    提示 No SecurityManager accessible to the calling code either bound to the org apache shiro util ThreadContext or as a vm
  • Shiro总结和常见面试题

    Shiro总结和常见面试题 一 什么是shiro Shiro是一个强大易用的java安全框架 提供了认证 授权 加密 会话管理 与web集成 缓存等功能 对于任何一个应用程序 都可以提供全面的安全服务 相比其他安全框架 shiro要简单的多
  • shiro-session-ehcache配置

    首先准备好jar包 shiro的主要四个 ehcache shiro xml 配置 首先securityManager配置 sessionManager配置
  • 【shiro】shiro反序列化漏洞综合利用工具v2.2(下载、安装、使用)

    目录 1 工具下载 2 依赖环境安装 3 使用 1 工具下载 shiro反序列化漏洞综合利用工具v2 2下载 链接 https pan baidu com s 1kvQEMrMP PZ4K1eGwAP0 Q pwd zbgp 提取码 zbg
  • springboot整合shiro相关依赖和配置整理

    springboot整合shiro 一 shiro快速开始 去github下载shiro 找到samples quickstart文件夹 用idea打开它 点进shiro ini文件 这个时候idea会提示下载插件 下载它 二 新建一个sp
  • Shiro JndiLdapRealm 针对 LDAP 的授权

    The Shiro 类 JndiLdapRealm 的 JavaDoc明确表示默认情况下禁用授权 并且用户应通过子类化和覆盖 LDAP 服务器来实现授权JndiLdapRealm doGetAuthorizationInfo方法 是否有示例
  • 带有 jdbc 和哈希密码的 shiro

    这是我的 shiro 配置 main authc loginUrl site index jsp authc usernameParam user authc passwordParam pass authc rememberMeParam
  • 使用 Shiro 登录后重定向到最后访问的页面

    使用 apache shiro 登录并重定向到最后访问的页面的更好方法是什么 我只有这个 SecurityUtils getSubject login new UsernamePasswordToken username password
  • 何时从容器管理的安全性转向 Apache Shiro、Spring Security 等替代方案?

    我正在尝试保护使用 JSF2 0 构建的应用程序的安全 我很困惑人们什么时候会选择使用 Shiro Spring Security 或 owasp 的 esapi 等安全替代方案 而放弃容器管理的安全性 看过一些相关问题 https sta

随机推荐

  • 面向 Android* 设备的英特尔® USB 驱动程序

    作者 Tao Wang Intel 利用英特尔 Android USB 驱动程序包 您能够将您基于 Windows 的机器连接至安装了英特尔 凌动 处理器的 Android 设备 注 英特尔 USB 驱动程序包 版本 1 1 5 增加了对
  • el-input 只能输入数字并限制长度

    在上一个博客中 有关于限制长度的使用 本文介绍限制只能输入数字的方法 el input 代码如下
  • 为什么要用数据库视图?

    视图的定义 视图 View 是一种虚拟的表 其结构和数据来自于一个或多个基本表 可以被当作普通表一样进行查询操作 但实际上不存储任何数据 在数据库中 视图可以被看作是一种数据访问的方式 它可以隐藏底层表的复杂性 提供简洁易懂的数据访问接口
  • chmod 666后文件不能看了!!!

    蛋疼 makefile里不能 mkdir 试着在终端改变下权限 sudo chmod 666 你的目录 R 然后 你的目录 就不能普通用户查看了 没事 再改回权限 755 或者 765 然后就正常了
  • 对角遍历矩阵算法c语言,C Tricks(十七)—— 对角线元素的屏蔽、二维数组(矩阵)的遍历...

    1 对角线元素的屏蔽 使用 if continue 实现对对角线元素的屏蔽 for u in range n for v in range n if u v continue 2 矩阵 二维数组 的遍历方法 遍历方法取决于最内层的操作 比如
  • 湖北 医学院

    http job hust edu cn show article htm id 25736 华中科技大学2014届医科毕业生专场招聘会 邀 请 函 尊敬的用人单位负责人 您好 感谢贵单位多年来对我校就业工作的大力支持 目前 2014届毕业
  • 【技术碎片】基于指数扩散二分搜索的重名文件重命名后缀

    目录 前言 linearSearch exponentialDiffusionBinarySearch 实现 ExponentialDiffusionBinarySearch java 运行 前言 一般我们在重命名文件时可以发现是这种结构
  • cppcheck linux安装和使用

    环境 centos7 下载cppcheck地址 官网 ccpcheck版本 cppcheck 2 6 上传到响应的目录执行编译 unzip cppcheck 2 6 zip cd cppcheck 2 6 make 代码检查命令 cppch
  • jenkins 插件安装缓慢

    两条命令解决 cd 你的Jenkins工作目录 updates sed i s http updates jenkins ci org download https mirrors tuna tsinghua edu cn jenkins
  • maskrcnn掩膜拟合效果不好是什么原因引起的,分类倒是很准确

    可能是因为训练数据中目标物体的掩膜标记不够精确或者数量不足 导致模型在预测掩膜时出现误差 或者是因为模型没有足够的参数 在处理复杂的图像时表现不佳
  • cacheput注解 用法_spring cache常用注解使用

    1 CacheConfig 主要用于配置该类中会用到的一些共用的缓存配置 示例 CacheConfig cacheNames users public interface UserService 配置了该数据访问对象中返回的内容将存储于名为
  • SpringBoot + MyBatis 结合 MVC框架设计 第1关:项目整合 - SpringBoot + MyBatis

    目录 任务描述 相关知识 使用MyBatis Spring Boot Starter进行整合SpringBoot MyBatis 使用SpringBoot MyBatis编写一个查询用户信息的接口 编程要求 测试说明 参考代码 任务描述 本
  • PCL 4PCS算法实现点云配准

    4PCS算法 一 算法原理 1 算法流程 2 参考文献 二 代码实现 1 主要参数 2 完整代码 三 结果展示 四 相关链接 一 算法原理 1 算法流程 4PCS算法是计算机图形学中一种流行的配准工具 给定两个点集 P Q P Q
  • Android系统运动传感器

    转自 https blog csdn net liang123l article details 53992197 Android平台提供了多种感应器 让你监控设备的运动 传感器的架构因传感器类型而异 重力 线性加速度 旋转矢量 重要运动
  • Windows 10 安装安卓子系统 WSA(Magisk/KernelSU)使用 WSA 工具箱安装 APK

    from https blog zhjh top archives XokySA7Rc1pkVvnxAEP5E 前提是系统为 Windows 10 22H2 10 0 19045 2311 或更高版本 尽量新 步骤 使用 WSAPatch
  • android真机和模拟器(emulator)的判断

    最近收到领导需求要判断真机和模拟器 先前项目里是有的 可能当时能用 但现在都不能用了 然后 baidu上能够找到的其实都不能用了 包括说使用cache来区分cpu架构是哈佛结构还是冯诺伊曼结构来判断的 这个其实是最不靠谱的 因为硬件结构是会
  • C语言函数大全-- p 开头的函数

    p 开头的函数 1 perror 1 1 函数说明 1 2 演示示例 1 3 运行结果 2 pieslice 2 1 函数说明 2 2 演示示例 2 3 运行结果 3 pow powf powl 3 1 函数说明 3 2 演示示例 3 3
  • 数据结构-冒泡排序,选择排序,插入排序,快速排序,希尔排序,堆排序

    冒泡排序 冒泡排序的思想 从头开始数据两两比较 将大的放到后面小的放到前面 经过一轮比较后就找到了该序列的最大数且将它放到了最后 再循环上述步骤找出第二大的数 第三大的数 int maoapo int a int len a为数组的首地址
  • 期货开户顺大市而逆小市

    期货的行情 有人愿意以更高的价来买入 就会涨 有人买意以更低的价格卖出 就会跌 现货市场上 一个馒头5角钱的时候 在期货市场上 如果有很多人争着买 这个馒头可能会涨到5块 或者50块 也是可能的 在这个馒头5块钱一个的时候 你感觉这个馒头太
  • ShiroFilter设计原理与实现

    Shiro提供了与Web集成的支持 其通过一个ShiroFilter入口来拦截需要安全控制的URL 然后进行相应的控制 ShiroFilter类似于如Strut2 SpringMVC这种web框架的前端控制器 其是安全控制的入口点 其负责读