簡體   English   中英

從 Raspberry Pi 上的 Spring Boot Rest 服務獲取 403 響應

[英]Getting 403 Response from Spring Boot Rest Service on Raspberry Pi

我的設置:

我有一個調用 spring boot rest api 的 vue 前端。 在本地,我在單獨的 docker 容器中運行它,在我的本地機器上一切正常。

出於測試目的,我將樹莓派連接到我的路由器。 為此,我還在不同的 docker 容器中運行前端和 spring boot。

這些設置之間的唯一區別是,我不能在我的樹莓派上使用FROM openjdk:11映像,因為它不支持 arm32v7。 在那里我使用了eclipse-temurin:11容器。

這是我的 docker-compose.yml:

version: "3.4"

services:
  db:
    image: jsurf/rpi-mariadb
    environment:
      MYSQL_ROOT_PASSWORD: 'admin'
      MYSQL_DATABASE: 'ghp_board'
      MYSQL_USER: 'ghp_board_admin'
      MYSQL_PASSWORD: 'ghp_board'
    volumes:
      - ./data/mariadb:/docker-entrypoint-initdb.d
    ports:
      - "33006:3306"

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "8081:8080"
    container_name: ghp_board_frontend
    volumes:
      - ./frontend:/usr/src/app/ghp_board_frontend
    depends_on:
      - db

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    depends_on:
      - db
    ports:
      - "8886:8080"

這是我用於后端容器的Dockerfile

FROM eclipse-temurin:11

COPY ./system/build/libs/system-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

我想要什么

Spring Boot 應用程序現在有兩個控制器(News 和 Demand),它們應該通過 REST API 向前端提供數據。 兩個控制器都有幾乎相同的端點:

import org.springframework.web.multipart.MultipartFile;
import org.webjars.NotFoundException;

import javax.lang.model.element.NestingKind;
import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Optional;

@RestController
@AllArgsConstructor
@CrossOrigin
public class NewsController {

    @Autowired
    private final NewsService newsService;
    private JwtAuthenticationService jwtAuthenticationService;

    @GetMapping(path = "/api/v1/news", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getAllNews(HttpServletRequest request) {
        return new ResponseEntity<>(newsService.getAllNewsEntries(), HttpStatus.OK);
    }

    @GetMapping(path = "/api/v1/news/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getNews(@PathVariable("id") Long id, HttpServletRequest request) {

        return new ResponseEntity<>(newsService.getNewsEntry(id), HttpStatus.OK);
    }

    // Post -> receive a new NewsEntry
    @PostMapping(
            path = "/api/v1/news",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> createNews(@RequestParam("file") Optional<MultipartFile> file, @RequestParam("newsData") String newsEntryString, HttpServletRequest request) throws IOException {

        ObjectMapper mapper = new ObjectMapper()
                .findAndRegisterModules()
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        NewsEntryDto newsEntryDto = mapper.readValue(newsEntryString, NewsEntryDto.class);

        return new ResponseEntity<>(newsService.insertNewNewsEntry(newsEntryDto, file), HttpStatus.OK);
    }

    //Put -> update an existing NewsEntry
    @PutMapping(
            path = "/api/v1/news",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public ResponseEntity<?> updateNews(
            @RequestParam("file") Optional<MultipartFile> file,
            @RequestParam("newsUpdateId") Long newsUpdateId,
            @RequestParam("newsData") String newsUpdateString,
            HttpServletRequest request) throws IOException
    {
        ObjectMapper mapper = new ObjectMapper()
                .findAndRegisterModules()
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        NewsEntryDto newsUpdateDto = mapper.readValue(newsUpdateString, NewsEntryDto.class);

        return new ResponseEntity<>(newsService.updateNewsEntry(newsUpdateId, newsUpdateDto, file), HttpStatus.OK);
    }

    //Delete -> delete an existing NewsEntry
    @DeleteMapping(
            path = "/api/v1/news/{id}"
    )
    public ResponseEntity<?> deleteNews(@PathVariable("id") Long newsDeleteId) {
        newsService.deleteNewsEntry(newsDeleteId);
        return new ResponseEntity<>("", HttpStatus.OK);
    }
}
package com.example.system.demand;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.webjars.NotFoundException;

import java.util.List;
import java.util.Optional;

@RestController
@AllArgsConstructor
@CrossOrigin
public class DemandController {

    private DemandService demandService;

    @GetMapping(path = "/api/v1/demand", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getAllDemands() {
        return new ResponseEntity<>(demandService.getAllDemandEntries(), HttpStatus.OK);
    }

    @GetMapping(path = "/api/v1/demand/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getDemandById(@PathVariable("id") Long id) {
        return new ResponseEntity<>(demandService.getDemandById(id), HttpStatus.OK);
    }

    @PostMapping(
            path = "/api/v1/demand",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public ResponseEntity<?> insertNewDemand(
            @RequestParam Optional<List<MultipartFile>> files,
            @RequestParam String demandData
    ) throws JsonProcessingException {

        ObjectMapper mapper = new ObjectMapper()
                .findAndRegisterModules()
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        DemandEntryDto demandEntryDto = mapper.readValue(demandData, DemandEntryDto.class);

        return new ResponseEntity<>(demandService.insertNewDemandEntry(demandEntryDto, files), HttpStatus.OK);
    }

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<?> handlerNotFoundException(NotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

我的問題:

當我從前端調用 GET-Endpoint /api/v1/news時,我從數據庫中獲取所有條目,所以這是有效的。 GET-Endpoint /api/v1/news/{id}和 POST-Endpoint 也可以正常工作。 但是 NewsController 的其他 Endpoints PUT 和 DELETE 以及 DemandController 的所有 Endpoints 正在接收 Responsecode 403 - Forbidden 我真的不明白為什么會這樣。

我的WebSecurityConfig不應該有任何解釋這種行為的規范:

package com.example.system.security;

import com.example.system.security.jwt.JwtAuthenticationEntrypoint;
import com.example.system.security.jwt.JwtAuthenticationProvider;
import com.example.system.security.jwt.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntrypoint unauthorizedHandler;  // Gibt 403 zurück, wenn nicht authorisiert

    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
    }

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
        return new JwtAuthenticationTokenFilter();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .cors().and() // Aktivieren von Cross-Site
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // Gibt 403 zurück, wenn nicht authorisiert
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                    .antMatchers("/")
                    .permitAll()
                    .antMatchers("/home")
                    .permitAll()
                    .antMatchers("/api/v1/login")
                    .permitAll()
                .anyRequest().authenticated();

        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.headers().cacheControl();
    }
}

並且 axois 調用的標頭也是相同的:

getNews() {
        const token = localStorage.getItem('token');

        const header = {
            'Content-Type' : 'application/x-www-form-urlencoded',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.get(process.env.VUE_APP_BACKEND_URL + "/api/v1/news", {
            headers: header
        });
    },
    getNewsDetail(id) {
        const token = localStorage.getItem('token');

        const header = {
            'Content-Type' : 'application/x-www-form-urlencoded',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.get(process.env.VUE_APP_BACKEND_URL + "/api/v1/news/" + id, {
            headers: header
        });
    },
    insertNewsEntry(data) {
        const token = localStorage.getItem('token');
        const header = {
            //'Content-Type' : 'application/json',
            'Content-Type' : 'multipart/form-data',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.post(process.env.VUE_APP_BACKEND_URL + "/api/v1/news", data, {
            headers: header
        });

    },
    updateNewsEntry(data) {
        const token = localStorage.getItem('token');
        const header = {
            //'Content-Type' : 'application/json',
            'Content-Type' : 'multipart/form-data',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.put(process.env.VUE_APP_BACKEND_URL + "/api/v1/news", data, {
            headers: header
        });
    },
    deleteNewsEntry(id) {
        const token = localStorage.getItem('token');
        const header = {
            //'Content-Type' : 'application/json',
            'Content-Type' : 'multipart/form-data',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.delete(process.env.VUE_APP_BACKEND_URL + "/api/v1/news/" + id, {
            headers: header
        });
    }

有誰知道我在這里錯過了什么?

對於任何未來的讀者,如果評論被刪除,我建議使用

http.cors().and().csrf().disable()...而不是.csrf().disable().cors().and()..configure方法中作為cors & 的使用順序csrf在原始帖子中不正確,問題得到解決后記。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM