简体   繁体   English

如何解决 CORS 错误与 Oauth2 Spring 启动和反应

[英]How resolve CORS Error with Oauth2 Spring boot and react

I want to develop an application where the user need to use a blizzard account to connect them.我想开发一个应用程序,用户需要使用暴雪帐户来连接它们。

I used spring boot 2.4.2 for the backend and React 17.0.0 for the frontend.我使用 spring boot 2.4.2 作为后端,React 17.0.0 作为前端。

That why i use spring security and the oauth2 authentication.这就是我使用 spring 安全性和 oauth2 身份验证的原因。

When i click on the login button i call the endpoint /user/isAccountExist to check if the user are registred on the application.当我单击登录按钮时,我调用端点 /user/isAccountExist 以检查用户是否已在应用程序上注册。

But when i try to call the endpoint, with the frontend in http://localhost:3000 (with the npm run start) i have a error.但是当我尝试调用端点时,前端在 http://localhost:3000 (使用 npm 运行开始)我有一个错误。

Call htpp://localhost:3000 before调用 htpp://localhost:3000 之前

I have tried with the frontend in http://localhost:2142 (exposed by the backend) but i have the same error.我曾尝试使用 http://localhost:2142 中的前端(由后端公开),但我有同样的错误。

Call htpp://localhost:2142 before调用 htpp://localhost:2142 之前

After thath i want to try to call the endpoint direclty with the browser (http://localhost:2142/user/isAccountExist) and i see that work.之后,我想尝试使用浏览器(http://localhost:2142/user/isAccountExist)直接调用端点,我看到了这个工作。

Call directly直接打电话

And after when i retry to click on the button in the frontend (http://localhost:3000 and http://localhost:2142) that works.当我重试单击前端中的按钮(http://localhost:3000 和 http://localhost:2142)时,它起作用了。

Call htpp://localhost:3000 after之后调用 htpp://localhost:3000

Call htpp://localhost:2142 after之后调用 htpp://localhost:2142

So, i don't know what happend and why before the call that doesn't worked.所以,我不知道在电话不起作用之前发生了什么以及为什么。

Thank you.谢谢你。

I link the github repository: https://github.com/opendoha/myGuild/tree/login_page我链接了 github 存储库: https://github.com/opendoha/myGuild/tree/login_page

I link a part of code我链接了一部分代码

UserController.java用户控制器.java

package fr.opendoha.myguild.server.controller;

import fr.opendoha.myguild.server.dto.UserAccountDTO;
import fr.opendoha.myguild.server.exception.UserBattleTagAlreadyUsedException;
import fr.opendoha.myguild.server.exception.UserBlizzardIdAlreadyUsedException;
import fr.opendoha.myguild.server.exception.UserEmailAlreadyUsedException;
import fr.opendoha.myguild.server.exception.UserNickNameAlreadyUsedException;
import fr.opendoha.myguild.server.parameters.FetchingUserAccountParameter;
import fr.opendoha.myguild.server.parameters.UserRegistrationParameter;
import fr.opendoha.myguild.server.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Controller used to manage users
 */
@RestController
@RequestMapping("user")
public class UserController extends MotherController {

    protected final UserService userService;

    /**
     * Constructor
     */
    @Autowired
    public UserController(final UserService userService) {
        super();
        this.userService = userService;
    }

    /**
     * Endpoint used to check if the account exist
     */
    @GetMapping("/isAccountExist")
    public Boolean isAccountExist(final OAuth2AuthenticationToken authentication) {

        final Integer blizzardId = this.getBlizzardId(authentication);

        return this.userService.checkUserAccountAlreadyExist(blizzardId);
    }

}

SecurityConfiguration.java安全配置.java

package fr.opendoha.myguild.server.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**-
 * Class configuration used to configure spring security
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * Configuration of security
     */
    @Override
    public void configure(final HttpSecurity http) throws Exception {

        http
            .cors()
            .and().csrf().disable()
            .antMatcher("/**").authorizeRequests()
            .antMatchers("/", "index.html", "/favicon.ico", "/*manifest.json", "workbox-*/*.js", "/*.js", "/*.png", "/static/**", "/*.svg", "/*.jpg").permitAll()
            .anyRequest().authenticated()
            .and().logout().logoutSuccessUrl("/")
            .and().oauth2Login();
    }

}

Application.yml应用程序.yml

application:
  server:
    port: 2142
    url: http://localhost
  db-param:
    host: localhost
    port: 3306
    db-name: ********
    user: ********
    password: ********
  blizzard:
    wow:
      delay-ms: 10
      profile:
        base-uri: https://eu.api.blizzard.com/profile
        namespace: profile-eu
      game-data:
        base-uri: https://eu.api.blizzard.com/data/wow
        namespace: static-eu

logging:
  level:
    fr.opendoha.myguild.server:
      tools:
        HttpHelper: INFO
        handler.GlobalExceptionHandler: INFO
      MyGuildApplication: INFO
      service:
        BlizzardService: INFO
        oauth2:
          BlizzardOAuth2FlowHandler: INFO
      aop.interceptor: 
        GeneralInterceptor: INFO
        ServiceInterceptor: INFO
    org:
      apache: INFO
      springframework:
        web.client.RestTemplate: INFO
        security: INFO
        data: INFO

server:
  port : ${application.server.port}

spring:
  jpa:
    show-sql: false
    database-platform : org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: create-drop
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
  security:
    oauth2:
      client:
        registration:
          oauth-blizzard:
            client-id: ********
            client-secret: ********
            client-name: ********
            provider: blizzard
            scope: wow.profile
            redirect-uri: "${application.server.url}:${application.server.port}/login/oauth2/code/oauth-blizzard"
            client-authentication-method: basic
            authorization-grant-type: authorization_code 
        provider:
          blizzard:
            issuer-uri: https://eu.battle.net/oauth
            authorization-uri: https://eu.battle.net/oauth/authorize
            token-uri: https://eu.battle.net/oauth/token
            user-info-uri: https://eu.battle.net/oauth/userinfo
            user-name-attribute: battletag
  
  datasource:
    url: jdbc:mysql://${application.db-param.host}:${application.db-param.port}/${application.db-param.db-name}
    username: ${application.db-param.user}
    password: ${application.db-param.password}
    driverClassName: com.mysql.cj.jdbc.Driver

Login componant (LoginPaper.tsx)登录组件 (LoginPaper.tsx)

import { Box, Button, makeStyles, Paper, Typography } from '@material-ui/core'
import React from 'react'
import Logo from './../../img/BlizzardLogo.svg'
import { UserHttpClient } from '../../api/httpclient/UserHttpClient'

export default function LoginPaper() {
  const classes = useStyles()

  const toto = async () => {
    
    const userHttpClient = new UserHttpClient()
    
    userHttpClient.isAccountExist()
    .then((value) => {
      console.log(value)
    })
    .catch((reason) => {
      console.log(reason)
    })
    

  }

  return (
    <Paper className={classes.paper} variant="outlined">
      <Box
        className={classes.box}
        display="flex"
        flexDirection="column"
        justifyContent="space-around"
        alignItems="center"
      >
        <Typography variant="h1" className={classes.title}>
          My Guild
        </Typography>
        <Button
          variant="contained"
          color="primary"
          className={classes.loginButton}
          onClick={toto}
        >
          <img src={Logo} className={classes.iconButton} />
          <div className={classes.buttonLabel}>Login with BLIZZARD</div>
        </Button>
      </Box>
    </Paper>
  )
}

UserHttpClient.ts用户HttpClient.ts

import { AddUserParameter } from "./../Entities"
import HttpClient from "./HttpClient"

export class UserHttpClient extends HttpClient {

    constructor(){
        super(false)
    }

    public async isAccountExist(): Promise<boolean> {
        const response = await this.get("/user/isAccountExist")
        return response.data as boolean
    }

}

HttpClient.ts HttpClient.ts

import Axios, { AxiosError, AxiosResponse } from 'axios'
import ApiError from './ApiError'

export default class HttpClient {
  public static readonly FOUND_STATUS = 403
  public static readonly UNAUTHORIZED_ERROR_STATUS = 401
  public static readonly FORBIDDEN_ERROR_STATUS = 403
  public static readonly INTERNAL_SERVER_ERROR_STATUS = 501
  public static readonly INTERNAL_SERVER_ERROR_MESSAGE =
    'An internal server error occured'

  private redirectIfUnauthorized: boolean

  constructor(redirectIfUnauthorized: boolean = true) {
    this.redirectIfUnauthorized = redirectIfUnauthorized
  }

  protected async get(
    apiUrl: string,
    queryParams?: { [key: string]: any },
    headers?: { [key: string]: any },
  ) {
    return this.manageErrors(() =>
      Axios.get(apiUrl, {
        params: queryParams,
        headers: this.buildHeader(headers),
      }),
    )
  }

  protected async post(
    apiUrl: string,
    data: any,
    headers?: { [key: string]: any },
  ) {
    return this.manageErrors(() =>
      Axios.post(apiUrl, data, {
        headers: this.buildHeader(headers),
      }),
    )
  }

  private buildHeader(headers?: {
    [key: string]: any
  }): { [key: string]: any } {
    return {
      'Content-Type': 'application/json',
      ...headers,
    }
  }

  private async manageErrors(
    apiCall: () => Promise<AxiosResponse>,
  ): Promise<AxiosResponse> {
    try {
      const response: AxiosResponse = await apiCall()
      return response
    } catch (err) {
      const error: AxiosError = err as AxiosError

      if (error.response) {
        if (
          this.redirectIfUnauthorized === true &&
          error.response.status === HttpClient.UNAUTHORIZED_ERROR_STATUS
        ) {
          window.location.href = '/'
        }
        throw new ApiError(
          error.response.status,
          error.response.statusText,
          error.response.data,
        )
      } else if (error.request) {
        throw new ApiError(
          HttpClient.INTERNAL_SERVER_ERROR_STATUS,
          HttpClient.INTERNAL_SERVER_ERROR_MESSAGE,
          error.request,
        )
      } else {
        throw new ApiError(
          HttpClient.UNAUTHORIZED_ERROR_STATUS,
          error.message,
          null,
        )
      }
    }
  }
}

package.json (part) package.json(部分)

"dependencies": {
    "axios": "^0.21.1",
}

"proxy": "http://localhost:2142"

If it helps, I added this to my Spring WebFlux Security Configuration class and it has enabled CORs from a different port when running apps on my localhost:如果有帮助,我将其添加到我的 Spring WebFlux 安全配置 class 中,并且在我的本地主机上运行应用程序时,它已从不同的端口启用 CORs:

@EnableWebFluxSecurity
class WebSecurity {
    @Bean
    fun configure(http: ServerHttpSecurity): SecurityWebFilterChain {
        http.authorizeExchange { customizer -> customizer
            .pathMatchers(
                "/my-swagger-api.yml",
                "/webjars/**",
                "/api-docs/**",
                "/oauth2/authorization/**",
                "/login/**"
            ).permitAll()
            .anyExchange().authenticated()
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(MyUnauthorizedRequestRejectingEntryPoint())
            .and()
            .oauth2Login { oauth -> oauth
                .authenticationManager(MyCustomAzureADAuthManager())
                .authenticationSuccessHandler(MyUiRedirectingSuccessHandler())
            }
         }.build()
    }

    @Bean
    @Profile("local")
    fun coorsConfig(): CorsConfigurationSource {
        val configuration = CorsConfiguration()
        configuration.allowCredentials = true
        configuration.allowedOrigins = listOf("http://localhost:8081")
        configuration.allowedMethods = listOf("GET")
        configuration.allowedHeaders = listOf("*")
        val source = UrlBasedCorsConfigurationSource()
        source.registerCorsConfiguration("/**", configuration)
        return source
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM