HttpSession null после замены AuthorizationRequest
Предлагается 500 баллов.. Полный код и инструкции по быстрому воспроизведению проблемы приведены ниже.
ПРОБЛЕМА:
HttpSession
становится null
после пользовательской реализации DefaultOAuth2RequestFactory
заменяет текущую AuthorizationRequest
с сохраненным AuthorizationRequest
.. Это приводит к
сбою последующего запроса до
/oauth/token
, потому что
CsrfFilter в цепочке фильтров безопасности Spring, предшествующей конечной точке
/oauth/token
, не может найти
session
Csrf token
в
null
session
для сравнения с
request
Csrf token
.
КОНТРОЛЬНЫЙ ПОТОК ВО ВРЕМЯ ОШИБКИ:
Следующая блок-схема иллюстрирует, где Шаг 14 и Шаг 15 как-то null
-ify HttpSession
. (Или, возможно, несоответствие a JSESSIONID
.) A SYSO
в начале CustomOAuth2RequestFactory.java
в Шаг 14 показывает, что действительно есть HttpSession
, который действительно содержит правильный CsrfToken
, Тем не менее, как-то, HttpSession
стал null
к моменту Шаг 15 запускает вызов от клиента по адресу localhost:8080/login
обратно к конечной точке localhost:9999/oauth/token
.
Точки останова были добавлены в каждую строку HttpSessionSecurityContextRepository
, указанную в журналах отладки ниже. (Он находится в папке Maven Dependencies
проекта authserver
eclipse.) Эти точки останова подтвердили, что HttpSession
null
, когда окончательный запрос /oauth/token
сделан в блок-схеме ниже. (Внизу слева от блок-схемы.) null
HttpSession
может быть из-за JSESSIONID
, который остается в браузере, устаревшем после выполнения пользовательского кода DefaultOAuth2RequestFactory
.
Как эта проблема может быть исправлена, так что тот же HttpSession
остается во время окончательного вызова конечной точки /oauth/token
после завершения шага 15 в блок-схеме?
![2_FA_OAuth_Flow.gif]()
СООТВЕТСТВУЮЩИЙ КОД И ЛОГИСТИКИ:
Полный код CustomOAuth2RequestFactory.java
можно просмотреть на сайте совместного доступа к файлам, нажав на эту ссылку. Мы можем догадаться что null
session
обусловлено либо 1.) JSESSIONID
не обновляется в браузере кодом в CustomOAuth2RequestFactory
, либо 2.) HttpSession
на самом деле null
.
Spring Загрузочные журналы отладки для вызова /oauth/token
после Шаг 15 четко указывают, что нет 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.se[email protected]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-СОЗДАНИЕ ПРОБЛЕМЫ НА ВАШЕМ КОМПЬЮТЕРЕ:
Вы можете воссоздать проблему на любом компьютере всего за несколько минут, выполнив следующие простые шаги:
1.) Загрузите сжатую версию приложения с сайта совместного доступа к файлам, нажав на эту ссылку.
2.) Разархивируйте приложение, набрав: tar -zxvf oauth2.tar(4).gz
3.) Запустите приложение authserver
, перейдя на oauth2/authserver
, а затем введите mvn spring-boot:run
.
4.) Запустите приложение resource
, перейдя на oauth2/resource
, а затем введите mvn spring-boot:run
5.) Запустите приложение ui
, перейдя на oauth2/ui
, а затем введите mvn spring-boot:run
6.) Откройте веб-браузер и перейдите к http : // localhost : 8080
7.) Нажмите Login
, а затем введите Frodo
в качестве пользователя и MyRing
в качестве пароля и нажмите, чтобы отправить.
8.) Введите 5309
в качестве Pin Code
и нажмите "Отправить". Это вызовет ошибку, показанную выше.
Журналы отладки Spring будут отображать LOT SYSO
, который дает значения переменных, таких как XSRF-TOKEN
и HttpSession
на каждом шаге, показанном на блок-схеме. SYSO
помогает сегментировать журналы отладки, чтобы их было легче интерпретировать. И все SYSO
выполняется одним классом, называемым другими классами, поэтому вы можете манипулировать классом SYSO
-generating, чтобы изменить отчетность везде в потоке управления. Имя SYSO
-генерирующего класса TestHTTP
, а его исходный код может быть найден в том же пакете demo
.
ИСПОЛЬЗУЙТЕ ДЕВУШЕР:
1.) Выберите окно терминала, в котором запущено приложение authserver
, и введите Ctrl-C
, чтобы остановить приложение authserver
.
2.) Импортируйте три приложения (authserver
, resource
и ui
) в eclipse как существующие проекты maven.
3.) В authserver
приложении eclipse Project Explorer щелкните, чтобы развернуть папку Maven Dependencies
, затем прокрутите ее вниз, чтобы открыть значок Spring-Security-web...
jarкак показано на рисунке ниже, оранжевым. Затем прокрутите, чтобы найти и развернуть пакет org.springframework.security.web.context
. Затем дважды щелкните, чтобы открыть класс HttpSessionSecurityContextRepository
, выделенный синим цветом на снимок экрана ниже. Добавьте точки останова в каждую строку этого класса. Возможно, вы захотите сделать то же самое с классом SecurityContextPersistenceFilter
в том же пакете. Эти точки останова позволят вам увидеть значение HttpSession
, которое в настоящее время становится null
до конца потока управления, но должно иметь допустимое значение, которое может быть сопоставлено с XSRF-TOKEN
, чтобы разрешить этот OP.
![debug_session.jpg]()
4.) В пакете app demo
добавьте точки останова внутри CustomOAuth2RequestFactory.java
. Затем Debug As... Spring Boot App
, чтобы запустить отладчик.
5.) Затем повторите шаги с 6 по 8 выше. Вы можете очистить кеш браузера перед каждой новой попыткой. И вы можете открыть вкладку "Сеть" в инструментах разработчика браузера.
Ответы
Ответ 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, которая вернула вам нулевой объект аутентификации.
Я также рекомендую вам изменить Bean создание CustomOAuth2RequestFactory следующим образом, которое переопределяет все 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";
}
}
Пожалуйста, сообщите мне, если вы хотите получить полный исходный код после очистки.
Ответ 2
Сессия не является нулевой в вашем приложении authserver
во время последнего вызова localhost :9999/uaa/oauth/token
. Существует не только сеанс, но токен JSESSIONID
и csrf
допустимых значений соответствия сеанса, присутствующих в потоке управления, между точкой, в которой пользователь отправляет правильный вывод и точку, где выполнен неудачный запрос /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
app 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
Я попытаюсь потратить немного больше времени на это, чтобы еще больше изолировать решение, учитывая количество пунктов, которые вы предлагаете. Но вышеизложенное должно существенно сократить проблему.
Я отправляю это до того, как он будет полностью завершен, потому что истечет срок вашей награды.