atjiu / pybbs

更实用的Java开发的社区(论坛),Better use of Java development community (forum)
GNU Affero General Public License v3.0
1.85k stars 707 forks source link

开发中碰到的问题求助大家 #34

Closed atjiu closed 7 years ago

atjiu commented 7 years ago

在用springboot开发的时候碰到了不少问题,能解决的,我自己就解决了,还没解决的,来求助一下大家

问题一

springboot 使用springcloud 做第三方登录的解决方案,网上也找了很多例子,springboot官方也有很多例子,但都没有跟本地用户结合的登录例子(也可能是我没找到),所以在这求助大大们,有做过的springboot里用springsecurity管理权限并且实现了github/qq/weibo等联合登录的例子,求分享一下!

问题二

在pybbs里用的是springsecurity管理的权限,默认是开启了csrf的,网页上get,post提交都没有问题,因为可以在模板里拿到spring生成的csrf数据,但pybbs也提供了接口,接口里get请求没有csrf的问题,但post就有csrf的问题了,如果使用http.csrf().disabled() 是可以解决的,可我不想这么做,求碰到并解决了这样问题的大大给分享一下!

问题三

springboot里集成的solr/es等给我的感觉是用做了nosql数据库了(也就是跟用mysql,oracle一样),我没找到可以做检索的方法,也可能是我没弄明白,求大大们分享一下自己的经验

暂时碰到了这些问题,后面再有,再加,先谢谢了!!

TakWolf commented 7 years ago

关于问题2,我也遇到了这样的问题,说一下我自己的理解,不一定正确。

CSRF全称是跨站请求伪造,能够产生攻击的原理在于,网站是基于cookie-session模式进行权限认证的,而在访问页面的时候 ,当前网站的cookie是会被自动携带到请求中的。因此,虽然由于浏览器同源策略的限制,黑客无法获取跨域请求的内容,但是这并不妨碍他伪造请求动作。

你访问了网站A,授权了cookie没有退出,访问了网站B,网站B伪造了一个请求,虽然有跨域限制,但是动作还是执行了。

一种非常常见的手法,黑客通过js伪造了一个表单请求,并将他包装成了一个img标签的src属性,这时,如果用户浏览了这张图片,同时用户浏览器存储了目标网站的授权cookie,那么攻击就完成了(这也是为什么有的浏览器禁止src属性中包含可执行js)。

更好的参考资料:http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html

上面的总结为两点: 1.基于cookie认证的存在csrf风险,不基于cookie认证的不存在csrf问题。API通常是第二种模式,因为他的认证通常是authToken,而不是cookie 2.上面一条存在csrf风险的情况下, 幂等性的动词没有csrf风险,例如GET,HEADER,OPTIONS等,只有非幂等性动作才有csrf风险,例如POST,DELETE。因为只有非幂等动词才会改变服务端资源状态(前提条件你的实现符合http语义)。

第二条也解释了为什么你的get好使,而post不行,因为csrf校验只校验post请求

(吐槽:浏览器端的Web模型有着这样那样的问题,而且搞的这么复杂,根本的锅就是早期的内批程序员草率的设计)

回到 spring security,他的原理是什么呢?简单来说就是,对非幂等动作,校验csrfToken,这个csrfToken随表单随机生成。 参考下面的代码: https://github.com/spring-projects/spring-security/blob/a82cab7afdb1fc58830b1c415f1874d36b2c6c92/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java#L164 我摘抄出来:

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
        private final HashSet<String> allowedMethods = new HashSet<String>(
                Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

        /*
         * (non-Javadoc)
         *
         * @see
         * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
         * servlet.http.HttpServletRequest)
         */
        @Override
        public boolean matches(HttpServletRequest request) {
            return !this.allowedMethods.contains(request.getMethod());
        }
    }

security提供了一个RequestMatcher的bean,来判断请求是否被csrf拦截,因此默认情况,除了"GET", "HEAD", "TRACE", "OPTIONS"这几个兄弟,其他请求都会被拦截,拦截后代码会走到这里: https://github.com/spring-projects/spring-security/blob/a82cab7afdb1fc58830b1c415f1874d36b2c6c92/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java#L90 我还是摘抄出来:

@Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);

        CsrfToken csrfToken = this.tokenRepository.loadToken(request); // 这里获取csrfToken
        final boolean missingToken = csrfToken == null;
        if (missingToken) {
            csrfToken = this.tokenRepository.generateToken(request);
            this.tokenRepository.saveToken(csrfToken, request, response);
        }
        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);

        if (!this.requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        String actualToken = request.getHeader(csrfToken.getHeaderName());
        if (actualToken == null) {
            actualToken = request.getParameter(csrfToken.getParameterName());
        }
        if (!csrfToken.getToken().equals(actualToken)) {   // 这里校验了csrfToken
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Invalid CSRF token found for "
                        + UrlUtils.buildFullRequestUrl(request));
            }
            if (missingToken) {
                this.accessDeniedHandler.handle(request, response,
                        new MissingCsrfTokenException(actualToken));
            }
            else {
                this.accessDeniedHandler.handle(request, response,
                        new InvalidCsrfTokenException(csrfToken, actualToken));
            }
            return;
        }

        filterChain.doFilter(request, response);
    }

csrfToken如果在表单参数中,是“_csrf”,在header中是“X-CSRF-TOKEN”,两者都可以,有一个存在就行,这个定义在: https://github.com/spring-projects/spring-security/blob/a82cab7afdb1fc58830b1c415f1874d36b2c6c92/web/src/main/java/org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.java#L33 网站端的部分,csrfToken会自动生成到隐藏表单中,提交会自动携带,处理器代码在这里: https://github.com/spring-projects/spring-security/blob/a82cab7afdb1fc58830b1c415f1874d36b2c6c92/web/src/main/java/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.java 但是API的部分,如果你不手动提交这两个参数,当然无法通过csrf校验。

解决方案: 1.按照pybbs现有的方案,一种方式是提供自定义的 CsrfFilter 的bean替换掉默认的bean,然后在校验判断中,排除 api 的路径。 2.我自己想到的一种hack的方法,不知道合不合理,security定义一个比如叫做:ROLE_API 的用户权限,映射到 api路径然后关闭csrf

题外话: 我自己并不倾向于这个解决方案。因为,将web部分与api部分泡在同一个域下面是一种不太合理的设计。 逻辑上web跟api属于两个域,但是实际却 共享资源,造成很多混淆。 事实上,好多网站都是这么设计的,包括cnode,我这里暂时不在详细展开,总之我认为这样的设计非常不好。 我更倾向的一种设计是,web跟api运行在不同域上,这样web这段,你可以完全按照security默认的机制,也不需要做什么定制,Api这块,上面的理由也阐述了,不存在csrf的问题,也不存在xss的问题,甚至都可以开放跨域调用,都不会存在什么问题。

atjiu commented 7 years ago

@TakWolf

1.按照pybbs现有的方案,一种方式是提供自定义的 CsrfFilter 的bean替换掉默认的bean,然后在校验判断中,排除 api 的路径。

我倾向这个解决方案

不过,后面你也说了,web跟api本来就应该是两个项目,只是共用了一个数据库而已,这个我同意,我再想想吧,感谢 : )

atjiu commented 7 years ago

关于问题二解决办法真心简单, 结果还拖到现在, 惭愧

http.csrf().ignoringAntMatchers("/api/**");

后面关于/api/**的所有地址都不用csrf验证了

atjiu commented 7 years ago

总结一下三个问题

第一个问题可以使用https://github.com/ChinaSilence/any-spring-security 这个项目里的方式解决,它是用增加拦截器继承AbstractAuthenticationProcessingFilter实现的,不过我不喜欢那种解决方式,pybbs里就不加了

第二个问题上面已经给出解决方式了

第三个问题现在master代码里用的了hibernate-search来实现的,支持了中文分词检索的功能