替换AuthorizationRequest

时间:2016-05-30 23:46:58

标签: spring spring-mvc spring-security spring-boot spring-oauth2

Complete code以及快速重现问题的说明如下


问题:
自定义DefaultOAuth2RequestFactory实施后,HttpSession变为null,用保存的AuthorizationRequest替换当前AuthorizationRequest这会导致后续请求失败/oauth/token,因为/oauth/token端点之前的Spring Security过滤器链中的CsrfFilter无法找到session {{在Csrf token null中1}}与session的{​​{1}}进行比较。


在错误期间控制流量:

以下流程图说明了步骤14 步骤15 某处request - {if} Csrf token。 (或者可能与null不匹配。)步骤14 HttpSession开头的JSESSIONID表示确实存在SYSO实际上包含正确的CustomOAuth2RequestFactory.java。然而,不知何故,当{strong>第15步触发来自HttpSession网址的客户的电话回到{{1}时,CsrfToken已变为HttpSession终点。

断点已添加到下面调试日志中提到的HttpSessionSecurityContextRepository的每一行。 (它位于null eclipse项目的localhost:8080/login文件夹中。)这些断点确认当localhost:9999/oauth/token的最终请求时,{strong> Maven Dependenciesauthserver在下面的流程图中。 (流程图的左下角。) HttpSession null可能是由于自定义/oauth/token后浏览器中保留的null变得过时代码运行。

如何修复此问题,以便在流程图中步骤15结束后,在HttpSession端点的最终调用期间保留相同的JSESSIONID


相关代码和日志:

完整代码DefaultOAuth2RequestFactory can be viewed at a file sharing site by clicking on this link. 我们可以猜测HttpSession /oauth/token是由于1.)CustomOAuth2RequestFactory.java不是通过null中的代码在浏览器中更新,或2. session实际为JSESSIONID - ified。

步骤15 之后调用CustomOAuth2RequestFactory的Spring Boot调试日志清楚地表明此时没有HttpSession,可以按如下方式阅读:< / p>

null


在您的计算机上重新创建问题:

您可以通过以下简单步骤在几分钟内在任何计算机上重新创建问题:

1。)下载zipped version of the app from a file sharing site by clicking on this link

2。)键入以下内容解压缩应用:/oauth/token

3.。)导航到HttpSession,然后输入2016-05-30 15:33:42.630 DEBUG 13897 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/token at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' 2016-05-30 15:33:42.631 DEBUG 13897 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/token at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' 2016-05-30 15:33:42.631 DEBUG 13897 --- [io-9999-exec-10] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists 2016-05-30 15:33:42.631 DEBUG 13897 --- [io-9999-exec-10] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created. 2016-05-30 15:33:42.631 DEBUG 13897 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/token at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter' 2016-05-30 15:33:42.631 DEBUG 13897 --- [io-9999-exec-10] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@2fe29f4b 2016-05-30 15:33:42.631 DEBUG 13897 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/token at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter' 2016-05-30 15:33:42.644 DEBUG 13897 --- [io-9999-exec-10] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:9999/uaa/oauth/token 2016-05-30 15:33:42.644 DEBUG 13897 --- [io-9999-exec-10] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession. 2016-05-30 15:33:42.644 DEBUG 13897 --- [io-9999-exec-10] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed ,启动tar -zxvf oauth2.tar(4).gz应用。

4.。)导航至authserver,然后输入oauth2/authserver

,启动mvn spring-boot:run应用

5.。)导航至resource,然后输入oauth2/resource

,启动mvn spring-boot:run应用

6。)打开Web浏览器并导航到ui

7。)点击oauth2/ui,然后输入mvn spring-boot:run作为用户,输入http : // localhost : 8080作为密码,然后点击提交。

8。)输入Login作为Frodo,然后点击提交。 这将触发上面显示的错误。

Spring Boot调试日志将显示很多MyRing,它会在流程图中显示的每个步骤中提供5309Pin Code等变量的值SYSO有助于对调试日志进行分段,以便更容易解释。并且所有XSRF-TOKEN都由其他类调用的一个类完成,因此您可以操纵HttpSession - 生成类来更改控制流中的报告。 SYSO生成类的名称为SYSO,其源代码可以在同一个SYSO包中找到。


使用调试人员:

1。)选择运行SYSO应用的终端窗口,然后输入TestHTTP以停止demo应用。

2.。)将三个应用(authserverCtrl-Cauthserver)导入eclipse,作为现有的maven项目

3。)在authserver应用程序的eclipse Project Explorer中,单击以展开 resource文件夹,然后在其中向下滚动以单击以展开 { {1}} jar 如下图所示,以橙色圆圈显示。然后滚动查找并展开ui包。然后双击打开下面屏幕截图中以蓝色突出显示的authserver类。将断点添加到此类中的每一行。您可能希望对同一包中的Maven Dependencies类执行相同操作。 这些断点可让您查看Spring-Security-web... 的值,该值在控制流结束之前当前变为org.springframework.security.web.context,但需要具有可以有效的值映射到HttpSessionSecurityContextRepository以解决此OP。

4.。)在应用的SecurityContextPersistenceFilter包中,在HttpSession内添加断点。然后null启动调试器。

5.)然后重复上面的步骤6到8。您可能希望在每次新尝试之前清除浏览器的缓存。您可能希望打开浏览器开发人员工具的“网络”选项卡。

2 个答案:

答案 0 :(得分:4)

最后一次致电authserver时,localhost :9999/uaa/oauth/token应用中的

会话不为空不仅有会话,而且有效会话的JSESSIONIDcsrf令牌与控制流中存在的值在用户提交正确引脚的点之间以及向/oauth/token发出失败请求的点。

问题在于有两个JSESSIONID值,并且选择了两个值中的错误来输入对/oauth/token的调用。因此,解决方案应该来自修改过滤器以删除错误JSESSIONID,以便可以发送正确的值。

以下将总结:


HttpSessionListener确定有效JSESSIONID

为了隔离问题,我创建了HttpSessionListener的实现,然后从HttpLListener的自定义实现中调用它,如下所示:

public class HttpSessionCollector implements HttpSessionListener, ServletContextListener {

    private static final Set<HttpSession> sessions = ConcurrentHashMap.newKeySet();

    public void sessionCreated(HttpSessionEvent event) {
        sessions.add(event.getSession());
    }

    public void sessionDestroyed(HttpSessionEvent event) {
        sessions.remove(event.getSession());
    }

    public static Set<HttpSession> getSessions() {
        return sessions;
    }

    public void contextCreated(ServletContextEvent event) {
        event.getServletContext().setAttribute("HttpSessionCollector.instance", this);
    }

    public static HttpSessionCollector getCurrentInstance(ServletContext context) {
        return (HttpSessionCollector) context.getAttribute("HttpSessionCollector.instance");
    }

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0) {
    }

}

然后我在HttpSessionListener的自定义实现中调用了上面的OncePerRequestFilter,我将其插入到authserver应用的Spring安全过滤器链中以提供诊断信息,如下所示:

@Component
public class DiagnoseSessionFilter extends OncePerRequestFilter implements ServletContextAware {

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain fc) throws ServletException, IOException {

    System.out.println("...........///////////// START OF DiagnoseSessionFilter.doFilterInternal() ///////////...........");
    //start of request stuff
    System.out.println("\\\\\\\\\\ REQUEST ATTRIBUTES ARE: ");
    if(req.getAttribute("_csrf")!=null){
        System.out.println("_csrf is: " + req.getAttribute("_csrf").toString());
    }
    if(req.getAttribute("org.springframework.security.web.csrf.CsrfToken")!=null){
        CsrfToken ucsrf = (CsrfToken) req.getAttribute("org.springframework.security.web.csrf.CsrfToken");
        System.out.println("ucsrf.getToken() is: " + ucsrf.getToken());
    }
    String reqXSRF = req.getHeader("XSRF-TOKEN");
    System.out.println("request XSRF-TOKEN header is: " + reqXSRF);
    String reqCookie = req.getHeader("Cookie");
    System.out.println("request Cookie header is: " + reqCookie);
    String reqSetCookie = req.getHeader("Set-Cookie");
    System.out.println("request Set-Cookie header is: " + reqSetCookie);
    String reqReferrer = req.getHeader("referrer");
    System.out.println("request referrer header is: " + reqReferrer);
    HttpSession rsess = req.getSession(false);
    System.out.println("request.getSession(false) is: " + rsess);
    if(rsess!=null){
        String sessid = rsess.getId();
        System.out.println("session.getId() is: "+sessid);
    }
    System.out.println("/////////// END OF REQUEST ATTRIBUTES ");

    //end of request stuff
    ServletContext servletContext = req.getServletContext();
    System.out.println("\\\\\\\\\\ START OF SESSION COLLECTOR STUFF ");

    HttpSessionCollector collector = HttpSessionCollector.getCurrentInstance(servletContext);
    Set<HttpSession> sessions = collector.getSessions();

    System.out.println("sessions.size() is: " + sessions.size());
    for(HttpSession sess : sessions){
        System.out.println("sess is: " + sess);
        System.out.println("sess.getId() is: " + sess.getId());
        CsrfToken sessCsrf = (CsrfToken) sess.getAttribute("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN");
        System.out.println("csrf is: " + sessCsrf);
        if(sessCsrf!=null){
            if(sessCsrf.getToken()!=null){
                System.out.println("sessCsrf.getToken() is: " + sessCsrf.getToken());
            } else { System.out.println("sessCsrf.getToken() is: null "); }
        } else { System.out.println("sessCsrf is: null "); }

        System.out.println("sess.getAttribute(SPRING_SECURITY_SAVED_REQUEST) is: " + sess.getAttribute("SPRING_SECURITY_SAVED_REQUEST") );
        if(sess.getAttribute("SPRING_SECURITY_SAVED_REQUEST") instanceof DefaultSavedRequest){
            System.out.println("_____ START PRINTING SAVED REQUEST");
            DefaultSavedRequest savedReq = (DefaultSavedRequest) sess.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
            List<Cookie> savedCookies = savedReq.getCookies();
            for(Cookie cook : savedCookies){
                String name = cook.getName();String value = cook.getValue();
                System.out.println("cookie name, value are: " + name + " , " + value);
            }
            Collection<String> savedHeaderNames = savedReq.getHeaderNames();
            for(String headerName : savedHeaderNames){
                System.out.println("headerName is: " + headerName);
            }
            List<Locale> savedLocales = savedReq.getLocales();
            for(Locale loc : savedLocales){
                System.out.println("loc.getLanguage() is: " + loc.getLanguage());
            }
            String savedMethod = savedReq.getMethod();
            System.out.println("savedMethod is: " + savedMethod);
            Map<String, String[]> savedParamMap = savedReq.getParameterMap();
            Iterator<Entry<String, String[]>> it = savedParamMap.entrySet().iterator();
            while (it.hasNext()) {
                Entry<String, String[]> pair = it.next();
                System.out.println("savedParamMap: " + pair.getKey() + " = " + pair.getValue());
                it.remove(); // avoids a ConcurrentModificationException
            }
            Collection<String> savedParamNames = savedReq.getParameterNames();
            for(String savedParamName : savedParamNames){
                System.out.println("savedParamName: " + savedParamNames);
            }
            System.out.println("_____ DONE PRINTING SAVED REQUEST");

        }

//      System.out.println("sess.getAttribute(SPRING_SECURITY_CONTEXT) is: " + sess.getAttribute("SPRING_SECURITY_CONTEXT") );
        if(sess.getAttribute("SPRING_SECURITY_CONTEXT") instanceof SecurityContextImpl){
            SecurityContext ctxt = (SecurityContext) sess.getAttribute("SPRING_SECURITY_CONTEXT");
            Authentication auth = ctxt.getAuthentication();

            if(auth.getDetails() instanceof WebAuthenticationDetails){
                WebAuthenticationDetails dets = (WebAuthenticationDetails) auth.getDetails();
                System.out.println( "dets.getSessionId() is: " + dets.getSessionId() );
            }
            System.out.println("auth.getAuthorities() is: " + auth.getAuthorities() );
            System.out.println("auth.isAuthenticated() is: " + auth.isAuthenticated() );
        }
    }

    SecurityContext context = SecurityContextHolder.getContext();
    System.out.println("...........///////////// END OF DiagnoseSessionFilter.doFilterInternal() ///////////...........");
    fc.doFilter(req, res);

    }
}


隔离问题代码:

以下结合并总结了来自HttpSessionListener的诊断数据与网络浏览器的开发者工具,用于用户点击提交密码视图提交和浏览器返回/oauth/token拒绝之间的步骤端点。

正如您所看到的,有两个JSESSIONID值浮动。其中一个值是正确的,而另一个值则不正确。不正确的值会传递到/oauth/token的请求中,并导致拒绝,即使传递的csrf是正确的。因此,解决这个问题的方法可能来自于改变下面的步骤,以阻止错误的JSESSIONID代替好的1.) POST http://localhost:9999/uaa/secure/two_factor_authentication request headers: Referer: 9999/uaa/secure/two_factor_authentication Cookie: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 filter chain: DiagnoseSessionFilter: request stuff: Cookie header: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 request.getSession(false).getId(): ....95CB77 session collector stuff: JSESSIONID: ....95CB77 csrf: ....862a73 SPRING_SECURITY_SAVED_REQUEST is null user details (from Authentication object with user/request JSESSIONID: ....ED927C Authenticated = true, with roles Complete the filter chain DiagnoseSessionFilter (again) request stuff: csrf attribute: ....862a73 Cookie header: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 request.getSession(false).getId(): 95CB77 session collector stuff: JSESSIONID: ....95CB77 csrf is: 862a73 SPRING_SECURITY_SAVED_REQUEST is null user details (Authentication for user/session/request) JSESSIONID: ....ED927C Authenticated = true, with authorities POST/secure/two_factor_authenticationControllerMethod do some stuff response: Location: 9999/uaa/oauth/authorize?.... XSRF-TOKEN: ....862a73 2.) GET http://localhost:9999/uaa/oauth/authorize?... request headers: Host: localhost:9999 Referer: 9999/uaa/secure/two_factor_authentication Cookie: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 FilterChain DiagnoseSessionFilter request stuff: Cookie header is: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 request.getSession(false).getId(): 95CB77 session collector stuff: JSESSIONID: ....95CB77 csrf is: ....862a73 SPRING_SECURITY_SAVED_REQUEST is: null user details (Authentication object with user/session/req) JSESSIONID: ....ED927C Authenticated = true with ALL roles. rest of filter chain TwoFactorAuthenticationFilter request stuff: csrf request attribute is: ....862a73 cookie header: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 request.getSession(false).getId() is: ....95CB77 updateCsrf is: ....862a73 response stuff: XSRF-TOKEN header (after manual update): ....862a73 DiagnoseSessionFilter: request stuff: _csrf request attribute: ....862a73 Cookie header: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 request.getSession(false).getId() is: ....95CB77 session collector stuff: JSESSIONID: ....95CB77 csrf is: ....862a73 SPRING_SECURITY_SAVED_REQUEST is: null user details (Authentication for user/session/request) JSESSIONID: ....ED927C Authenticated is true, with ALL roles. CustomOAuth2RequestFactory request stuff: _csrf request parameter is: ....862a73 Cookie header: JSESSIONID: ....95CB77 ....918636 XSRF-TOKEN: ....862a73 request.getSession(false).getId() is: ....95CB77 updateCsrf: ....862a73 response stuff: XSRF-TOKEN header: ....862a73 session attribute printout csrf: ....862a73 SPRING_SECURITY_CONTEXT (not printed, so don't know values) response: Location: 8080/login?code=myNwd7&state=f6b3Km XSRF-TOKEN: ....862a73 3.) GET http://localhost:8080/login?code=myNwd7&state=f6b3Km request headers: Host: localhost:8080 Referer: 9999/uaa/secure/two_factor_authentication Cookie: JSESSIONID: ....918636 XSRF-TOKEN: ....862a73 UiAppFilterChain: HttpSessionSecurityContextRepository creates new SPRING_SECURITY_CONTEXT to replace null one OAuth2ClientAuthenticationProcessingFilter (position 8 of 14) AuthorizationCodeAccessTokenProvider Retrieving token from 9999/uaa/oauth/token AuthServerFilterChain: DiagnoseSessionFilter request stuff: XSRF-TOKEN header is: null Cookie header is: null Set-Cookie header is: null referrer header is: null request.getSession(false) is: null session collector stuff: JSESSIONID: ....95CB77 sessCsrf.getToken() is: 862a73 SPRING_SECURITY_SAVED_REQUEST is: null Authenticated is true but with ONLY these roles: ROLE_HOBBIT, ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED SecurityContextPersistenceFilter reports no HttpSession and no SPRING_SECURITY_CONTEXT CsrfFilter rejects request to /oauth/token due to no session % csrf response headers: Set-Cookie: XSRF-TOKEN: ....527fbe X-Frame-Options: DENY

"npm start"

考虑到您提供的点数,我将尝试花费更多时间来进一步隔离解决方案。但上述情况应该大大缩小问题范围。

我在完成之前发布了这个,因为你的赏金期即将到期。

答案 1 :(得分:1)

你解决了问题吗?我一直在四处寻找2FA的完整样本以及spring-security-oauth2。很高兴您发布了完整的概念和完整的资源。

我尝试了您的软件包,只需更改AuthserverApplication.java中的一行代码即可解决您的问题

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .formLogin().loginPage("/login").permitAll()
        .and()
                .requestMatchers().antMatchers("/login", "/oauth/authorize", "/secure/two_factor_authentication", "/pincode")
        .and()
                .authorizeRequests().anyRequest().authenticated();
        // @formatter:on
    }

您的原始配置通过了spring security的认证链,该认证链返回了一个空的身份验证对象。

我还建议您将CustomOAuth2RequestFactory的Bean创建更改为覆盖链中所有OAuth2RequestFactory的以下内容

@Bean
    public OAuth2RequestFactory customOAuth2RequestFactory(){
        return new CustomOAuth2RequestFactory(clientDetailsService);
    }

对于您为处理CSRF而添加的代码,您可以只删除它们,例如。 2FA控制器:

@Controller
@RequestMapping(TwoFactorAuthenticationController.PATH)
public class TwoFactorAuthenticationController {
    private static final Logger LOG = LoggerFactory.getLogger(TwoFactorAuthenticationController.class);
    public static final String PATH = "/secure/two_factor_authentication";
    public static final String AUTHORIZE_PATH = "/oauth/authorize";
    public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @RequestMapping(method = RequestMethod.GET)
    public String auth(HttpServletRequest request, HttpSession session, HttpServletResponse resp/*, ....*/) {
        System.out.println("-------- inside GET /secure/two_factor_authentication --------------");
        if (AuthenticationUtil.isAuthenticatedWithAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
            LOG.info("User {} already has {} authority - no need to enter code again", ROLE_TWO_FACTOR_AUTHENTICATED);
//            throw ....;
        }
        else if (session.getAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME) == null) {
//            LOG.warn("Error while entering 2FA code - attribute {} not found in session.", CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
//          throw ....;
        }
        return "pinCode";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String auth(FormData formData, HttpServletRequest req, HttpServletResponse resp,
                                            SessionStatus sessionStatus, Principal principal, Model model)
        throws IOException{

        if (formData.getPinVal()!=null) {
            if(formData.getPinVal().equals("5309")){
                AuthenticationUtil.addAuthority(ROLE_TWO_FACTOR_AUTHENTICATED);
                return "redirect:"+AUTHORIZE_PATH;
            };
        };

        return "pinCode";
    }
}

如果您想在清理后获得完整的源代码,请告诉我。