[英]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.