Spring Boot - Google oAuth2 from external client on VueJS

Проблема: не получается настроить просто отправку get запроса для авторизации так, чтобы не нужно было делать такой костыль как у меня с редиректом. И так же не получается выполнить грамотный запрос на logout и перехватить его ответ на клиенте. Описание текущей реализации смотрите ниже.

upd. Авторизацию сделал. Redirect в google уходит теперь правильно. Нужно на сервере понять как сделать грамотный redirect не через кастомный loginSuccess на мой localhost:8000/

Небольшое описание содержания кода: Конфиг oAuth2 (2), конфиг vue-cli (4), конфиг axios (5). Использую spring-boot:2.3.0.

Описание текущей реализации: авторизация с клиента (localhost:8000), который написан на VueJS, проходит только через redirect на серверный url (1). После чего я ловлю redirect (2) с google сервера и делаю redirect на клиентский url в rest контроллере (3). Пользователя после авторизации я могу создать и получить (1). После авторизации, если я захочу сделать logout, то отправляю post на сервер, а после получаю ошибку, но при этом выход произошел и пользователя без авторизации я больше получить не могу. Но после logout я получаю ошибку (0), которой хотел бы избежать.


0) Ошибка редиректа с google сервера:

Ошибка редиректа с google сервера

1) Работа с авторизацией на клиенте:

import axios from './axiosApi'

export default {
  getUser() {
    return axios.get('/api/auth/create-or-get-user');
  },
  logout() {
    const CSRF_TOKEN_OBJECT = document.cookie.match(
      new RegExp(`XSRF-TOKEN=([^;]+)`));

    const headers = {
      'X-XSRF-TOKEN': CSRF_TOKEN_OBJECT
        ? CSRF_TOKEN_OBJECT[1] : '',
      'Access-Control-Allow-Origin': '*'
    }

    return axios.postWithCustomHeaders('/api/logout', headers);
  }
}

2) Конфигурация Java сервера:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().and()
            .authorizeRequests(a -> a
                .antMatchers("/**", "/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .exceptionHandling(e -> e
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
            )
            .csrf(c -> c
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )
            .logout(l -> l
                .deleteCookies("remove")
                .invalidateHttpSession(true)
                .permitAll()
            )
            .oauth2Login(o -> o
                .authorizationEndpoint()
                .baseUri("/login").and()
                .defaultSuccessUrl("/auth/loginSuccess")
            );
        }
    }
}

3) RestContoller для обработки авторизации пользователя, получения или его создания:

@RestController
@RequestMapping("auth")
public class AuthController {

    private final UserDetailsRepo userDetailsRepo;

    public AuthController(UserDetailsRepo userDetailsRepo) {
            this.userDetailsRepo = userDetailsRepo;
    }

    @GetMapping("loginSuccess")
    public RedirectView redirectIntoClient() {
            String CLIENT_URL = "http://localhost:8000/";
            return configureRedirectView(CLIENT_URL);
    }

    @GetMapping("create-or-get-user")
    @JsonView(Views.IdName.class)
    public User user(@AuthenticationPrincipal OAuth2User principal) {
            if (Objects.isNull(principal)) {
                    return null;
            }

            String id = principal.getAttribute("sub");

            if (StringUtil.isBlank(id)) {
                    return null;
            }

            User user = userDetailsRepo.findById(id).orElseGet(() -> {
                    User newUser = new User();

                    newUser.setId(id);
                    newUser.setName(principal.getAttribute("name"));
                    newUser.setEmail(principal.getAttribute("email"));
                    newUser.setGender(principal.getAttribute("gender"));
                    newUser.setLocale(principal.getAttribute("locale"));
                    newUser.setUserpic(principal.getAttribute("picture"));

                    return newUser;
            });

            user.setLastVisit(LocalDateTime.now());

            return userDetailsRepo.save(user);
    }

    private RedirectView configureRedirectView(String redirectUrl) {
            RedirectView redirectView = new RedirectView();
            redirectView.setStatusCode(HttpStatus.FOUND);
            redirectView.setUrl(redirectUrl);

            return redirectView;
    }
}

4) vue.config.js:

module.exports = {
  "outputDir": "target/dist",
  "assetsDir": "static",
  "publicPath": "./",
  "devServer": {
    "contentBase": "./dist",
    "compress": true,
    "port": 8000,
    "proxy": {
      "/api" : {
        target: 'http://localhost:9000',
        ws: true,
        changeOrigin: true,
        pathRewrite: {
          '^/api': '',
        },
      }
    },
    "stats": "errors-only",
    "clientLogLevel": "error"
  },
  "transpileDependencies": [
    "vuetify"
  ],
}

5) axiosApi.js:

import axios from 'axios'
import store from './../store/index'

function request(method, url, params, data, headers) {
  let axiosConfig = {
    method: method,
    url: url,
  }

  if (params) {
    axiosConfig.params = params;
  }

  if (data) {
    axiosConfig.data = data;
  }

  if (headers) {
    axiosConfig.headers = headers;
  }

  store.commit('settings/setLoading', true);

  axios.interceptors.response.use(function (response) {
    store.commit('settings/setLoading', false);
    return response;
  }, function (error) {
    store.commit('settings/setLoading', false);
    return Promise.reject(error);
  });

  return axios(axiosConfig);
}

function get(url) {
  return request('get', url, null, null, null)
}

function post(url, data) {
  data = data || {}
  return request('post', url, null, data, null)
}

function postWithCustomHeaders(url, headers) {
  return request('post', url, null, null, headers)
}

function put(url, data) {
  data = data || {}
  return request('put', url, null, data, null)
}

function remove(url) {
  return request('delete', url, null, null, null)
}

export default {get, post, postWithCustomHeaders, put, remove, request}


Ответы (0 шт):