tutorial mvc mkyong example spring spring-mvc spring-security spring-boot spring-oauth2

mvc - spring security oauth2 example mkyong



HttpSession null después de reemplazar AuthorizationRequest (2)

¿Has resuelto tu problema? He estado buscando una muestra completa de 2FA junto con spring-security-oauth2. Es genial que haya publicado sus conceptos completos y las fuentes completas.

Probé tu paquete y tu problema simplemente puede resolverse cambiando solo 1 línea de código en tu 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 }

Su configuración original pasó la cadena de autenticación de seguridad de primavera que le devolvió un objeto nulo de autenticación.

También le recomendaría que cambie la creación de Bean de CustomOAuth2RequestFactory a la siguiente que anule todas las OAuth2RequestFactory en la cadena

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

Para el código que ha agregado para manejar el CSRF, simplemente puede eliminarlos, por ej. el controlador 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"; } }

Por favor, hágamelo saber si desea un código fuente completo después de la limpieza.

El código completo y las instrucciones para reproducir rápidamente el problema se detallan a continuación.

EL PROBLEMA: La HttpSession pasa a ser null después de que una implementación personalizada de DefaultOAuth2RequestFactory reemplaza la AuthorizationRequest actual con una AuthorizationRequest guardada. Esto provoca un error en la solicitud posterior a /oauth/token porque CsrfFilter en la cadena de filtro de Spring Security que precede al punto final /oauth/token no puede encontrar un Csrf token session en la session null para comparar con el Csrf token la request . FLUJO DE CONTROL DURANTE EL ERROR:

El siguiente diagrama de flujo ilustra dónde el Paso 14 y el Paso 15 null alguna manera la HttpSession . (O posiblemente no coincida con un JSESSIONID .) Un SYSO al comienzo de CustomOAuth2RequestFactory.java en el Paso 14 muestra que, de hecho, hay una HttpSession que de hecho contiene el CsrfToken correcto. Sin embargo, de alguna manera, la HttpSession ha vuelto null cuando el Paso 15 desencadena una llamada del cliente en el localhost:8080/login url de localhost:8080/login regreso al localhost:9999/oauth/token punto final localhost:9999/oauth/token .

Se agregaron HttpSessionSecurityContextRepository de HttpSessionSecurityContextRepository a cada línea del HttpSessionSecurityContextRepository mencionado en los registros de depuración a continuación. (Se encuentra en la carpeta Maven Dependencies del proyecto authserver eclipse.) Estos puntos de interrupción confirmaron que HttpSession es null cuando la solicitud final a /oauth/token se realiza en el siguiente diagrama de flujo. (Abajo, a la izquierda del diagrama de flujo). La HttpSession null puede deberse a que el JSESSIONID que permanece en el navegador queda desactualizado después de que se DefaultOAuth2RequestFactory código DefaultOAuth2RequestFactory personalizado.

¿Cómo se puede solucionar este problema, para que la misma HttpSession permanezca durante la llamada final al punto final /oauth/token , después del final del Paso 15 en el diagrama de flujo?

CÓDIGO Y REGISTROS PERTINENTES:

El código completo de CustomOAuth2RequestFactory.java se puede ver en un sitio para compartir archivos haciendo clic en este enlace. Podemos suponer que la session null se debe a 1.) el JSESSIONID no está siendo actualizado en el navegador por el código en CustomOAuth2RequestFactory , o 2.) la HttpSession realmente está null .

Los registros de depuración de Spring Boot para la llamada a /oauth/token después del Paso 15 indican claramente que no hay HttpSession en ese punto, y se pueden leer de la siguiente manera:

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 RE-CREAR EL PROBLEMA EN SU COMPUTADORA:

Puede volver a crear el problema en cualquier computadora en solo unos minutos siguiendo estos simples pasos:

1.) Descargue la versión comprimida de la aplicación desde un sitio para compartir archivos haciendo clic en este enlace .

2.) Descomprima la aplicación escribiendo: tar -zxvf oauth2.tar(4).gz

3.) Inicie la aplicación oauth2/authserver navegando a oauth2/authserver y luego escribiendo mvn spring-boot:run .

4.) Inicie la aplicación de resource navegando a oauth2/resource y luego escribiendo mvn spring-boot:run

5.) Inicie la aplicación ui navegando a oauth2/ui y luego escribiendo mvn spring-boot:run

6.) Abra un navegador web y navegue a http : // localhost : 8080

7.) Haga clic en Login y luego ingrese Frodo como el usuario y MyRing como la contraseña, y haga clic para enviar.

8.) Ingrese 5309 como Pin Code y haga clic en enviar. Esto activará el error que se muestra arriba.

Los registros de depuración Spring Boot mostrarán MUCHO SYSO , que proporciona los valores de variables como XSRF-TOKEN HttpSession y HttpSession en cada paso que se muestra en el diagrama de flujo . SYSO ayuda a segmentar los registros de depuración para que sean más fáciles de interpretar. Y todo el SYSO se realiza en una clase llamada por las otras clases, por lo que puede manipular la clase SYSO para cambiar los informes en todas partes en el flujo de control. El nombre de la clase TestHTTP es TestHTTP , y su código fuente se puede encontrar en el mismo paquete de demo .

UTILIZA EL DEBUGGER:

1.) Seleccione la ventana del terminal que ejecuta la aplicación authserver y escriba Ctrl-C para detener la aplicación authserver .

2.) Importe las tres aplicaciones ( authserver , resource y ui ) en eclipse como proyectos maven existentes .

3.) En el eclipse Project Explorer de la aplicación authserver , haga clic para expandir la carpeta Maven Dependencies , luego desplácese hacia abajo para hacer clic para expandir Spring-Security-web... jar como se muestra en un círculo en naranja en la imagen siguiente. A continuación, desplácese para buscar y expandir el paquete org.springframework.security.web.context . Luego haga doble clic para abrir la clase HttpSessionSecurityContextRepository resaltada en azul en la captura de pantalla siguiente. Agregue puntos de interrupción a cada línea en esta clase. Es posible que desee hacer lo mismo con la clase SecurityContextPersistenceFilter en el mismo paquete. Estos puntos de interrupción le permitirán ver el valor de la HttpSession , que actualmente se vuelve null antes del final del flujo de control, pero necesita tener un valor válido que pueda correlacionarse con un XSRF-TOKEN para resolver este OP.

4.) En el paquete de demo la aplicación, agregue puntos de interrupción dentro de CustomOAuth2RequestFactory.java . Luego, Debug As... Spring Boot App para iniciar el depurador.

5.) Luego repita los pasos 6 a 8 anteriores. Es posible que desee borrar el caché del navegador antes de cada nuevo intento. Y es posible que desee que se abra la pestaña Red de las herramientas para desarrolladores del navegador.


La sesión no es nula en su aplicación authserver en el momento de la llamada final a localhost :9999/uaa/oauth/token . No solo hay una sesión, sino que el JSESSIONID y el token csrf de los valores válidos de coincidencia de sesión presentes en el flujo de control entre el punto donde el usuario envía el pin correcto y el punto donde se realiza la solicitud fallida a /oauth/token .

El problema es que hay dos valores JSESSIONID y se selecciona el error de los dos valores para ingresar la llamada a /oauth/token . Por lo tanto, la solución debe provenir de la modificación de los filtros para eliminar el JSESSIONID incorrecto, de modo que se pueda enviar el valor correcto.

Lo siguiente resumirá:

HttpSessionListener identificó el JSESSIONID válido

Para aislar el problema, creé una implementación de HttpSessionListener y luego lo llamé desde una implementación personalizada de HttpLListener , de la siguiente manera:

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) { } }

Luego llamé al HttpSessionListener anterior en una implementación personalizada de OncePerRequestFilter , que OncePerRequestFilter en la cadena de filtros de Spring Security de la aplicación OncePerRequestFilter para proporcionar información de diagnóstico, de la siguiente manera:

@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); } } Aislando el código del problema:

A continuación, se combinan y resumen los datos de diagnóstico de HttpSessionListener con las herramientas de desarrollador del navegador web para los pasos entre el usuario que hace clic en enviar en la vista de código PIN de envío y el navegador devuelve un rechazo del /oauth/token .

Como puede ver, hay dos valores de JSESSIONID flotando alrededor. Uno de los valores es correcto, mientras que el otro valor no es. El valor incorrecto se transfiere a la solicitud a /oauth/token y provoca el rechazo, aunque el csrf pasado sea correcto. Por lo tanto, la solución a este problema probablemente vendrá de la modificación de los pasos a continuación para dejar de colocar el JSESSIONID incorrecto en lugar del 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

Trataré de pasar un poco más de tiempo con esto para aislar aún más la solución, dada la cantidad de puntos que está ofreciendo. Pero lo anterior debería reducir sustancialmente el problema.

Estoy publicando esto antes de que esté completamente terminado porque su período de recompensa está a punto de caducar.