Spring 4 Security + CORS + AngularJS - странный редирект - PullRequest
0 голосов
/ 13 февраля 2019

У меня проблемы с моим бэкэндом Spring и внешним интерфейсом AngularJS.Как информация, я довольно новичок в Spring Security и также учусь с этим проектом.

Я не использую SpringBoot.Оба работают раздельно и должны работать на отдельных машинах.ATM Мой внешний интерфейс работает локально через сервер gulp на https://localhost:3000, внутренний сервер работает в Tomcat на https://localhost:8443/context.Я настроил CORSFilter в Java.

Пока все хорошо.Если я запускаю веб-интерфейс, в сервер поступают вызовы для получения ресурсов, и я захожу на страницу входа.Если я выберу логин, звонок будет https://localhost:8443/context/login, как и положено. Но : после обработки имени входа в серверной части сервер выполняет перенаправление на https://localhost:8443/context вместо https://localhost:3000, что, конечно, создает404 и приводит к неудачной регистрации (по внешнему интерфейсу).Я просто не могу найти, где происходит это странное перенаправление.

SpringSecurityConfig:

private static final String C440_LOGIN = "/login";
private static final String c440_START_PAGE = "/index.html";
private static final String FAVICON_ICO = "/favicon.ico";

@Override
protected void configure(HttpSecurity http) throws Exception {
    // HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), SessionManagementFilter.class); does not work!
    HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
    workHttp.addFilterBefore(new CookieFilter(), ChannelProcessingFilter.class);
    workHttp.addFilterBefore(getUsernamePasswordPortalAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    // set authorizations
    workHttp = authorizeRequests(http);
    // login handling
    workHttp = formLogin(workHttp);
    // exception handling
    workHttp = exceptionHandling(workHttp);
    // logout handling
    workHttp = logout(workHttp);
    // cookie handling
    workHttp = rememberMe(workHttp);

    // disable caching because if IE11 webfonds bug
    // /6159599/font-face-eot-ne-zagruzhaetsya-po-https
    http.headers().cacheControl().disable();

    csrf(workHttp);
}

/**
 * Configures request authorization.
 *
 * @param http The security configuration.
 * @return The configured security configuration.
 * @throws Exception is throws if the configuration fails.
 */
protected HttpSecurity authorizeRequests(HttpSecurity http) throws Exception {
    return http
        .authorizeRequests()

        // secured pages
        .antMatchers("/", getCustomerdWebRessourceSecuredPath()).authenticated()

        // common resources
        .antMatchers("/app/**").permitAll()
        .antMatchers("/profiles/**").permitAll()
        .antMatchers("/captcha/**").permitAll()

        .antMatchers("/", getCustomerRessourcePath()).permitAll()
        .antMatchers("/", getCustomerWebRessourcePath()).permitAll()

        .antMatchers("/", c440_START_PAGE).permitAll()
        .antMatchers("/", FAVICON_ICO).permitAll()
        .antMatchers(C440_LOGIN).permitAll()

        // frontend services
        .antMatchers("/services/userService/**").permitAll()
        .antMatchers("/services/applicationService/**").permitAll()
        .antMatchers("/services/textContentService/**").permitAll()
        .antMatchers("/services/textContentBlockService/**").permitAll()
        .antMatchers("/services/menuItemService/**").permitAll()
        .antMatchers("/services/calculatorService/**").permitAll()

        .anyRequest().authenticated()
        .and();
}

private String getCustomerRessourcePath() {
    return "/resources/app-" + portalFrontendBase + "/**";
}

private String getCustomerWebRessourcePath() {
    return "/app-" + portalFrontendBase + "/**";
}

private String getCustomerdWebRessourceSecuredPath() {
    return "/app-" + portalFrontendBase + "/secure/**";
}

/**
 * Configures form login.
 *
 * @param http The security configuration.
 * @return The configured security configuration.
 * @throws Exception is throws if the configuration fails.
 */
protected HttpSecurity exceptionHandling(HttpSecurity http) throws Exception {
    return http
        .exceptionHandling()
        .authenticationEntryPoint((request, response, authException) -> {
            if (authException != null) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                /**
                 * IMPORTANT: do not redirect the requests. The front-end will be responsible to do this.
                 * Otherwise the unauthorized status cannot be caught in the front-end correctly.
                 */
                return;
            }
        })
        .and();
}

/**
 * Configures form login.
 *
 * @param http The security configuration.
 * @return The configured security configuration.
 * @throws Exception is throws if the configuration fails.
 */
protected HttpSecurity formLogin(HttpSecurity http) throws Exception {
    return http
        .formLogin()
            .loginPage(c440_START_PAGE)
            .successHandler(getAuthenticationSuccessHandler())
            .failureHandler(getAuthenticationFailureHandler())
            .loginProcessingUrl(C440_LOGIN)
            .permitAll()
            .and();
}

/**
 * Configures logout.
 *
 * @param http The security configuration.
 * @return The configured security configuration.
 * @throws Exception is throws if the configuration fails.
 */
protected HttpSecurity logout(HttpSecurity http) throws Exception {
    return http
        .logout()
            .logoutUrl(portalLogoutURL)
            .addLogoutHandler(getLogoutHandler())
            .logoutSuccessHandler(getLogoutSuccessHandler())
            .invalidateHttpSession(true)
            .and();
}

@Bean
public UsernamePasswordPortalAuthenticationFilter getUsernamePasswordPortalAuthenticationFilter() throws Exception {
    UsernamePasswordPortalAuthenticationFilter customFilter = new UsernamePasswordPortalAuthenticationFilter();
    customFilter.setAuthenticationManager(authenticationManagerBean());
    return customFilter;
}

UsernamePasswordPortalAuthenticationFilter.java:

    @PropertySource(value = {"classpath:application.properties"})
    public class UsernamePasswordPortalAuthenticationFilter extends 
    UsernamePasswordAuthenticationFilter {
    private Logger log = Logger.getLogger(this.getClass());

    @Value("${captchaActive}")
    private boolean captchaActive;

    @Override
    public AuthenticationManager getAuthenticationManager() {
        return super.getAuthenticationManager();
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
        return getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * Reads the UsernamePasswordPortalAuthenticationToken from the data of the request.
     *
     * @param request The request to read the data from.
     * @return The authentication token.
     * @throws AuthenticationException is thrown if the data cannot be read.
     */
    public UsernamePasswordPortalAuthenticationToken getAuthenticationTokenFromRequest(final HttpServletRequest request) throws AuthenticationException {
        StringBuffer buf = new StringBuffer();
        String line = null;

        try {
            BufferedReader reader = request.getReader();
            while ((line = reader.readLine()) != null) {
                buf.append(line);
            }

            UsernamePasswordPortalAuthenticationToken loginDataWithCaptcha =
                new ObjectMapper().readValue(buf.toString(), UsernamePasswordPortalAuthenticationToken.class);

            if (this.captchaActive) {
                String answer = (String) request.getSession().getAttribute("COLLPHIRCAPTCHA");

                List<CaptchaCookieDto> captchaCookieDtos;
                captchaCookieDtos = (List<CaptchaCookieDto>) request.getAttribute("captchaCookies");

                CaptchaCookieDto captchaCookieDto = captchaCookieDtos.stream().filter(captchaCookie -> captchaCookie.getUsername().equals(
                    loginDataWithCaptcha.getUsername())).findAny().orElse(null);

                if (captchaCookieDto != null && captchaCookieDto.getCounter() >= 2) {
                    if (answer.equals(loginDataWithCaptcha.getConfirmCaptcha())) {
                        return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
                            UsernamePasswordPortalAuthenticationToken.class);
                    } else {
                        throw new BadCredentialsException("invalid data");
                    }
                } else {
                    return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
                        UsernamePasswordPortalAuthenticationToken.class);
                }
            } else {
                return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(), UsernamePasswordPortalAuthenticationToken.class);
            }


        } catch (Exception e) {
            throw new BadCredentialsException("invalid data");
        }

    }

}

Я попытался изменить порядок двух своих пользовательских фильтров (CORSFilter и CookieFilter) или поставить CORSFilter что-то еще (addFilterBefore SessionManagementFilter не работает, если я это сделаюто, что login-call не будет работать из-за отсутствия CORS-заголовка) и почти всего остального ...

Я также попытался использовать идею из authsuccesshandler из https://www.baeldung.com/spring_redirect_after_login, гдеЯ просто получаю заголовок origin запросов (который должен быть URL-адресом https://localhost:3000) для перенаправления на него:

@Component
public class MyTestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private Logger LOG = Logger.getLogger(this.getClass());
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public MyTestAuthenticationSuccessHandler() {
        super();
        setUseReferer(true);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
        LOG.info("onAuthenticationSuccess");
        SecurityContextHolder.getContext().setAuthentication(auth);

        handle(request, response, auth);
    }

    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {

        String targetUrl = determineTargetUrl(request);

        if (response.isCommitted()) {
            LOG.info("Response has already been committed. Unable to redirect to " + targetUrl);
            return;
        }

        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    protected String determineTargetUrl(HttpServletRequest request) {
        return request.getHeader("Origin");
    }
}

, но все равно он не работает.

Кроме того, если я попытаюсь отладить бэкэнд и установить точки останова внутри authsuccesshandler и authfailurehandler, это все равно не останавливается на достигнутом.Разве это не должно останавливаться на достигнутом?

.formLogin()
        .loginPage(c440_START_PAGE)
        .successHandler(getAuthenticationSuccessHandler())
        .failureHandler(getAuthenticationFailureHandler())
        .loginProcessingUrl(C440_LOGIN)
        .permitAll()
        .and();

Я действительно не понимаю, где происходит это перенаправление и почему он не будет использовать мой новый authsuccesshandler.

ОБНОВЛЕНИЕ07.03.19: Похоже, что successhandler вообще не вызывается , даже если я разверну и интерфейс, и серверную часть по одному и тому же URL-адресу, что и связанный файл WAR, который заставляет вход в систему работатьснова.Странно то, что даже если я уберу материал .formLogin() из метода configure внутри SecurityConfig, логин все еще работает.Так что я думаю, что похоже, что вся магия происходит в AuthenticationProvider, который вызывается в нашем обычае UsernamePasswordAuthenticationFilter:

UsernamePasswordAuthenticationFilter

[...]
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
    return getAuthenticationManager().authenticate(authRequest);
}
[...]

AuthenticationProvider:

[...]
@Override
public CollphirAuthentication authenticate(Authentication authentication) throws AuthenticationException {

    if (authentication == null) {
        throw new IllegalArgumentException("authentication");
    }

    if (UsernamePasswordPortalAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
        UsernamePasswordPortalAuthenticationToken clientAuthentication = (UsernamePasswordPortalAuthenticationToken) authentication;

        CollphirUser user = getUserService().loginUser(
                clientAuthentication.getName(), clientAuthentication.getCredentials().toString(), clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
        CollphirAuthentication auth = null;

        if (user == null || user.getBenutzerkennung() == null || user.getCOLRolle() == null) {
            LOG.info("authentication failed");
            Notification[] notifications = user.getNotifications();
            String msg = null;

            if (notifications != null && notifications[0] != null && notifications[0].getText() != null) {
                msg = notifications[0].getText();
            }

            throw new BadCredentialsException(msg);
        }
        Referenz arbeitgeberReference = getArbeitgeberReference(user, clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());

        auth = new CollphirAuthentication(user, arbeitgeberReference);
        auth.setArbeitgeber(getArbeitgeber( arbeitgeberReference));

        LOG.debug("is authenticated: " + auth.isAuthenticated());

        return auth;
    }

    throw new BadCredentialsException("type");
}
[...]

Итак, я думаю: где-то внутри UsernamePasswordPortalAuthenticationFilter илиAuthenticationProvider перенаправление выполняется.Если я подумаю об этом, перенаправление вообще не имеет смысла во внешнем интерфейсе AngularJS, где вызов сервера выполняется через REST, верно?Разве сервер не должен просто отправлять обратно код состояния или что-то, что контроллер AngularJS может оценить, чтобы изменить состояние или отобразить сообщение об ошибке?

Похоже, весь процесс входа в систему в этом приложении действительно странный.Я не могу себе представить, что обычно не используют .formLogin() и .successHandler()?Дело в том, что у меня нет лучшего примера для интерфейса AngularJS и Spring Security для сравнения ...

1 Ответ

0 голосов
/ 13 февраля 2019

Используете ли вы по умолчанию Springs AuthenticationSuccessHandler?Я думаю, что этот обработчик просто перенаправляет на базовый путь приложения, и он не работает, если ваш интерфейс находится в другом контексте или URL.Таким образом, вы должны выполнить перенаправление после успешного входа обратно на ваш веб-интерфейс.

Взгляните на эту страницу: https://www.baeldung.com/spring-security-redirect-login

Здесь вы найдете несколько возможностей справиться с этим делом.

...