Spring Вход для системы безопасности с Java и Angular2

У меня возникли проблемы с интеграцией spring безопасности (а именно с частью входа) в моем веб-приложении. My BackEnd работает на localhost: 8080, а FrontEnd (Ionic 2 с AngularJS 2) работает на localhost: 8100. Мне удалось войти (по крайней мере, я так думаю), но остальные запросы становятся недоступными, и я получаю следующую ошибку в консоли разработчика Chrome:

XMLHttpRequest cannot load http://localhost:8080/company/getAll. Response for preflight is invalid (redirect)

При тестировании с помощью Postman он работает, я вхожу в систему, а затем я могу выполнять POST-запросы на http://localhost:8080/company/getAll и все работает по назначению.

Предположительно, я пропускаю что-то (например, токен), но не могу понять, что. Я новичок в Ionic и spring Security, поэтому, пожалуйста, простите меня, если это что-то тривиальное. Я пробовал различные обучающие программы, но не смог ничего найти (большинство из них использовало JSP).

Как я могу заставить это работать? Как должны выглядеть мои запросы из интерфейса?

Вот мой метод входа с контроллера BackEnd:

@RequestMapping(value = "/login", method = RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<GeneralResponse> login(Model model, String error, String logout) {
        System.out.println("LOGIN"+model.toString());

        if (error != null) {
            System.out.println("1.IF");
            model.addAttribute("error", "Your username and password is invalid.");
        }

        if (logout != null) {
            System.out.println("2.IF");
            model.addAttribute("message", "You have been logged out successfully.");
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new GeneralResponse(true, "FAILURE: Error"));
    }

Это мой WebSecurityConfig:

@Configuration
@EnableWebSecurity
@ComponentScan("com.SAB.service")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/resources/**", "/registration").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/user/login")
            .permitAll()
            .and()
            .logout()
            .permitAll();
        http
            .csrf().disable();
        http.formLogin().defaultSuccessUrl("http://localhost:8100/", true);
        //.and().exceptionHandling().accessDeniedPage("/403")
        //.and().sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

И, наконец, мой код FrontEnd, ответственный за вход в систему:

  login(){
    var body = 'username='+this.loginForm.controls['username'].value+'&password='+this.loginForm.controls['password'].value;
    var headers = new Headers();
    headers.append('Content-Type', 'application/x-www-form-urlencoded');
    //headers.append("Access-Control-Allow-Origin", "*");

    this.http
      .post('http://localhost:8080/user/login',
        body, {
          headers: headers
        })
      .subscribe(data => {
        console.log('ok');
        console.log(data)
      }, error => {
        console.log(JSON.stringify(error.json()));
      });
  }

Если вам нужны какие-либо дополнительные данные, пожалуйста, сообщите мне, и я предоставлю их.

Спасибо заранее!

Я не получил ошибку Access-COntroll-Origin, но я попытался изменить ее в соответствии с комментариями в любом случае, и теперь я получаю "Недопустимый запрос CORS"

Заголовок запроса и ответа для входа от почтальона: введите описание изображения здесь

EDIT: Вот журналы безопасности spring. Однако spring регистрирует только запрос OPTIONS и POST, даже если FrontEnd вызывает POST.

************************************************************

Request received for OPTIONS '/login':

[email protected]

servletPath:/login
pathInfo:null
headers: 
host: localhost:8080
connection: keep-alive
access-control-request-method: POST
origin: http://localhost:8100
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
access-control-request-headers: access-control-allow-origin
accept: */*
referer: http://localhost:8100/
accept-encoding: gzip, deflate, sdch, br
accept-language: en-US,en;q=0.8


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CorsFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2017-05-12 19:18:28.529 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-05-12 19:18:28.529 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 4 of 12 in additional filter chain; firing Filter: 'CorsFilter'
2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.se[email protected]5baaaa2b
2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

UPDATE: Поэтому после добавления CORSFilter, предложенного в принятом ответе, мне также пришлось изменить мои запросы в интерфейсе, чтобы файл cookie с JSESSIONID отправлялся по каждому запросу. Баспически я должен был добавить следующее ко всем моим реквизитам: let options = new RequestOptions({ headers: headers, withCredentials: true });

Ответы

Ответ 1

Это, похоже, проблема, связанная с конфигурацией CORS. Браузер делает запрос предварительной проверки OPTION перед вашим фактическим вызовом GET. У меня мало знаний в этой области, но вы можете попробовать добавить фильтр ниже в конец spring.

public class CORSFilter extends GenericFilterBean {

    public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse response = (HttpServletResponse) res;
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.addHeader("Access-Control-Max-Age", "3600");
        response.addHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token");
        response.addHeader("Access-Control-Allow-Credentials", "true");

        if(req.getMethod().equalsIgnoreCase("options")){
             return;
        }
        chain.doFilter(request, response);
    }
}

И добавьте строку ниже в метод настройки вашего WebSecurityConfig.

.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);

Ответ 2

Решение очень простое. Имя пользователя и пароль в POST должны быть представлены как FormData, а не часть тела. У меня была такая же проблема с моим приложением, и после долгой борьбы я создал HTML-форму и сделал POST, тогда она работала как шарм.

См. ниже живой пример. Вы должны иметь возможность регистрироваться с любым идентификатором и пытаться войти в систему и дебютировать в своем браузере. Он использует загрузку Spring для серверной части и Angular 4 в пользовательском интерфейсе.

Live Demo: http://shop-venkatvp.rhcloud.com/#/

Вход в систему HTML: https://github.com/reflexdemon/shop/blob/master/src/app/login/login.component.html#L7-L31

Spring Конфигурация безопасности: https://github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/WebSecurityConfig.java#L20-L34

Контроллер входа: https://github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/page/controller/LoginController.java

Надеюсь, что это будет полезно.

Ответ 3

Вы пытались явно разрешить запросы OPTIONS? Вот так:

protected void configure(HttpSecurity http) throws Exception {
   ...
   http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
   ...
}

Ответ 4

Я нахожусь в середине написания статьи об интеграции Angular 4 и spring. У меня нет готовой статьи, но исходный код сделан. Я не уверен, почему вы пытаетесь обслуживать свое приложение с двух разных хостов, это излишне усложняет ваше приложение, введя требование cors и необходимость управлять двумя контекстами безопасности. Если это не абсолютное требование, я буду обслуживать все с одного и того же сервера.

Взгляните на этот проект github для полной интеграции Angular и spring boot: https://github.com/tschiman/tutorials/tree/master/spring-cloud/spring-cloud-bootstrap/gateway. Вы можете игнорировать материал зуула там, если вам это не нужно. Это не влияет на поддержку Angular ui.

Если вы хотите включить csrf, и вы должны посмотреть на это сообщение: be4b206478c136d24ea1bf8b6f93da0f3d86a6c3

Если чтение, которое не имеет смысла, это шаги, которые вам нужно выполнить:

  • Переместите исходный код Angular в ваше загрузочное приложение spring
  • Измените файл angular -cli.json, чтобы иметь исходный каталог в ваших ресурсах static или resource public folder: "outDir":"../../resources/static/home" Это устанавливает выходной каталог, когда вы запускаете ng build из командной строки с помощью Angular кли.
  • Измените URL-адрес успеха для входа в систему для перенаправления на /home/index.html .formLogin().defaultSuccessUrl("/home/index.html", true), это заставит успешный логин всегда перенаправляться в ваше приложение Angular.
  • Если вы хотите автоматизировать создание своего приложения Angular в maven, то добавьте этот плагин в свой файл POM:

        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <phase>generate-resources</phase>
                    <configuration>
                        <tasks>
                            <exec executable="cmd" osfamily="windows" dir="${project.basedir}/src/main/angular/ui">
                                <arg value="/c"/>
                                <arg value="ng"/>
                                <arg value="build"/>
                            </exec>
                            <exec executable="/bin/sh" osfamily="mac" dir="${project.basedir}/src/main/angular/ui">
                                <arg value="-c"/>
                                <arg value="ng build"/>
                            </exec>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    

Если у вас есть дополнительные вопросы, я буду рад ответить дальше, если этого недостаточно, чтобы вы его работали.

Ответ 5

Прежде всего - это, безусловно, проблема с настройкой CORS, поскольку вы используете бэкэнд и интерфейс на разных адресах.

Вы можете использовать backend для поддержки запросов CORS, но это грязное решение, espesialy, так как вы, вероятно, не хотите, чтобы CORS в вашей версии. Гораздо более понятный подход состоял в том, чтобы настроить либо внешний интерфейс, либо сервер на сервере как прокси-сервер другому.

Я предпочитаю настроить интерфейс для прокси-запросов на бэкэнд. Таким образом, это не повлияет на производственную версию приложения.

В следующей части предполагается, что вы используете angular -cli (как и в случае с angular2).

У них есть раздел в своих документах специально для настройки прокси: https://www.npmjs.com/package/angular-cli#proxy-to-backend.

У меня есть все мои службы REST внутри/api-контекста, поэтому мой прокси .conf.json выглядит так:

{
  "/api": {
    "target": "http://localhost:8083",
    "secure": false
  },
  "/login": {
    "target": "http://localhost:8083",
    "secure": false
  },
  "/logout": {
    "target": "http://localhost:8083",
    "secure": false
  }
}

Где http://localhost:8083 - это мой бэкэнд. Затем вы можете запустить приложение:

ng serve --proxy-config proxy.conf.json

и используйте только свой внешний адрес. Конфигурация CORS не требуется.

P.S. Если вы не используете angular -cli, я уверен, что вы можете использовать этот подход с помощью любого инструмента, который вы используете для обслуживания frontent. До перехода на angular -cli я использовал это с lite-server.