Spring Zuul API Gateway с аутентификацией Spring Session/Redis и маршрутизацией в одном запросе

Последние несколько дней я действительно искал повсюду, как это сделать, и, наконец, решил признать поражение и попросить о помощи, пожалуйста !!!

Я следил за учебным пособием доктора Дэйва Сайера по Angular и Spring Security, в частности, прокси-серверу Zuul в качестве шлюза API и использованию Spring Session с Redis (https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/double#_sso_with_oauth2_angular_js_and_spring_security_part_v )

Проблема, с которой я сталкиваюсь, заключается в том, что я вызываю службы отдыха ресурсов через шлюз из внешнего приложения со следующим заголовком:

String plainCreds = "user:password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);

пройти аутентификацию, а затем маршрутизироваться zuul, а затем ресурс, чтобы иметь доступ к аутентифицированному сеансу через Redis.

Проблема в том, что сеанс, по-видимому, фиксирует Redis в шлюзе только после ответа на запрос. Итак, что происходит, когда я вызываю службу ресурсов с заголовком, я вижу успешную аутентификацию, происходящую в шлюзе и создаваемом сеансе, однако я получаю 403 в ресурсе из-за того, что сеанс не находится в Redis после его маршрутизировался через zuul.

Однако, если я получу ошибку, возьмите идентификатор сеанса, добавьте его в заголовок и повторите попытку, он работает, потому что теперь мой аутентифицированный сеанс доступен для проекта ресурсов после его маршрутизации.

Пожалуйста, может ли кто-нибудь указать мне, как я могу получать свои звонки через шлюз для аутентификации и маршрутизации в том же запросе, пожалуйста?

Спасибо, Джастин.


person Justin Taylor    schedule 12.01.2016    source источник
comment
ты решил это? Я с той же проблемой.   -  person Rafael Zeffa    schedule 10.02.2016


Ответы (3)


Я следил за сообщениями Джастина Тейлора на разных страницах, так что это его решение. Мне имеет смысл иметь решение с исходным кодом здесь:

  1. Заставьте Spring Session фиксироваться с нетерпением — начиная с spring-session v1.0 существует свойство аннотации @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE), которое немедленно сохраняет данные сеанса в Redis. Документация здесь.
  2. Простой фильтр Zuul для добавления сеанса в заголовок текущего запроса:
@Component
public class SessionSavingZuulPreFilter extends ZuulFilter {

    @Autowired
    private SessionRepository repository;

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();

        HttpSession httpSession = context.getRequest().getSession();
        Session session = repository.getSession(httpSession.getId());

        context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());

        log.info("ZuulPreFilter session proxy: {}", session.getId());

        return null;
    }

}

Еще раз - это не мое решение - полномочия передаются Джастину Тейлору.

person shobull    schedule 10.08.2016
comment
Круто топил это было полезно! Я надеюсь, что в будущем будет улучшена интеграция с zuul и spring session, которая будет учитывать это, поскольку это довольно стандартный вариант использования пограничной архитектуры/шлюза с API без сохранения состояния. - person Justin Taylor; 11.08.2016
comment
Для тех, кто натыкается на это. Этот фильтр работает в основном из коробки. Для «других методов» установите тип на pre и порядок на ноль. Не уверен, почему они опущены, поскольку они, по сути, представляют собой одну строку, которая заставляет этот код копировать и вставлять. - person Tim Schimandle; 19.10.2016
comment
@Ceekay спасибо за ваш комментарий. Я исправил код. Этот код должен был быть просто примером. Например. он не обрабатывает несколько значений Cookie (он переопределяет все существующие значения с помощью SESSION=‹sessionID›). - person shobull; 20.10.2016
comment
Как будто он не будет обрабатывать одновременных пользователей? - person Tim Schimandle; 21.10.2016

Я очень сожалею о задержке ответа здесь, одна из замечательных вещей в Южной Африке - это наши замечательные телекоммуникации, хе-хе, у меня дома некоторое время не было интернета, и мой исходный код для этого находится на моем домашнем компьютере.

Да, Стив на правильном пути. Здесь нужно решить две проблемы:

  1. Сеанс Spring только фиксирует аутентифицированный сеанс для повторного использования в ответ на первоначальный входящий запрос. Итак, первый шаг — перейти по ссылке, предоставленной Стивом, чтобы убедиться, что весенний сеанс фиксируется в Redis при каждом изменении сеанса.

  2. Zuul не распространяет этот новый аутентифицированный сеанс при начальной маршрутизации. Итак, что вам нужно сделать, это использовать предварительный фильтр zuul (множество примеров вокруг), который получает идентификатор аутентифицированного сеанса, а затем добавляет его в запрос zuul к ресурсу за шлюзом. Вы увидите метод установки в запросе zuul для установки идентификатора сеанса.

Если вы этого не сделаете, вам нужно будет сделать два вызова: один для аутентификации и получения действительного идентификатора сеанса, который будет в redis из весеннего сеанса, а затем последующий вызов с идентификатором вашего аутентифицированного сеанса.

Я боролся с этим какое-то время, но когда у меня это заработало, все было в порядке. Я расширил это решение, чтобы оно работало не только для базового http, но и было добавлено в реализацию токена jwt.

Надеюсь, это поможет, как только я буду подключен дома, я могу опубликовать источник.

Удачи! Джастин

person Justin Taylor    schedule 23.02.2016

Мой APIGateway (Zuul) проксируется Apache Httpd и защищен модулем Mellon (SAML 2.0). После успешной аутентификации у поставщика удостоверений модуль mellon правильно вводит некоторые прочитанные заголовки в ответ SAML, но первый запрос завершается с ошибкой с кодом состояния 403.

Я также использую SpringSecurity, чтобы решить проблему, я использую простой фильтр, добавленный в цепочку фильтров безопасности, которые обеспечивают правильное создание SecurityContext:

@Component
public class MellonFilter extends OncePerRequestFilter {

    private final Logger log = LoggerFactory.getLogger(MellonFilter.class);


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

       String mellonId=req.getHeader("mellon-nameid");

        if(mellonId==null||mellonId.isEmpty())
            ;//do filterchain
        else {

            UserWithRoles userWithRoles = new UserWithRoles();
            userWithRoles.setUsername(mellonId);
            SilUserDetails details = new SilUserDetails(userWithRoles);

            SilAuthenticationPrincipal silPrincipal = null;
            Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();

            authorities.add(new SimpleGrantedAuthority("Some roles");

            silPrincipal = new SilAuthenticationPrincipal(details, true, authorities);
            SecurityContextHolder.clearContext();
            SecurityContextHolder.getContext().setAuthentication(silPrincipal);
        }
        filterChain.doFilter(req,httpServletResponse);
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        if(SecurityContextHolder.getContext().getAuthentication()!=null&&SecurityContextHolder.getContext().getAuthentication() instanceof SilAuthenticationPrincipal)
            return true;
        return false;

    }
}

Затем мне нужен ZuulFilter для сохранения сеанса (в Redis) и распространения фактического идентификатора сеанса:

public class ZuulSessionCookieFilter extends ZuulFilter {

    private final Logger log = LoggerFactory.getLogger(ZuulSessionCookieFilter.class);

    @Autowired
    private SessionRepository repository;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {

        return true;
    }

    @Override
    public Object run() throws ZuulException {

        RequestContext context = RequestContext.getCurrentContext();

        HttpSession httpSession = context.getRequest().getSession();
        httpSession.setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
                SecurityContextHolder.getContext()
        );
        Session session = repository.findById(httpSession.getId());
        context.addZuulRequestHeader("cookie", "SESSION=" + base64Encode(httpSession.getId()));
        log.debug("ZuulPreFilter session proxy: {} and {}", session.getId(),httpSession.getId());

        return null;
    }

    private static String base64Encode(String value) {
        byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
        return new String(encodedCookieBytes);
    }
}

Я надеюсь, что это решение будет полезно для всех.

person paspaola    schedule 28.06.2019