Вопрос или проблема
Мой код работает, когда я запускаю его обычно в IntelliJ IDE с сервером tomcat, предоставленным spring на порту 8080. Но когда я изменяю файл pom.xml, чтобы создать WAR-файл, и загружаю его на свой локальный сервер tomcat, который я настроил на порт 8081, он просто показывает код 403 для самого простого API, который даже не имеет безопасности. WAR-файл успешно создаётся и также загружается через приложение tomcat manager.
Я изменил имя своего WAR-файла на “logindemo”.
Когда я разворачиваю свой WAR-файл в webapps Tomcat, я запускаю этот URL в Postman с GET:-
http://localhost:8081/logindemo/auth/welcome
Ниже показан мой файл pom.xml, который я использую для создания WAR-файла.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.8</version>
<relativePath/>
</parent>
<groupId>com.gfg</groupId>
<artifactId>springboot3-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot3-security</name>
<description>Демо-проект для Spring Boot 3 Security</description>
<properties>
<java.version>17</java.version>
<jjwt.version>0.11.5</jjwt.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Ниже приведён мой SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private JwtAuthFilter authFilter;
@Bean
public UserDetailsService userDetailsService() {
return new UserInfoService(); // Убедитесь, что UserInfoService реализует UserDetailsService
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Отключить CSRF для безсостояних API
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/welcome", "/auth/addNewUser", "/auth/generateToken").permitAll()
.requestMatchers("/auth/user/**").hasAuthority("ROLE_USER")
.requestMatchers("/auth/admin/**").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated() // Защитить все другие конечные точки
)
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Без сессий
)
.authenticationProvider(authenticationProvider()) // Пользовательский поставщик аутентификации
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class); // Добавить JWT фильтр
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // Кодирование паролей
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
Ниже приведён мой Controller
@RestController
@RequestMapping("/auth")
public class UserController {
@Autowired
private UserInfoService service;
@Autowired
private JwtService jwtService;
@Autowired
private AuthenticationManager authenticationManager;
@GetMapping("/welcome")
public String welcome() {
return "Добро пожаловать, эта конечная точка не защищена";
}
@PostMapping("/addNewUser")
public String addNewUser(@RequestBody UserInfo userInfo) {
return service.addUser(userInfo);
}
@GetMapping("/user/userProfile")
@PreAuthorize("hasAuthority('ROLE_USER')")
public String userProfile() {
return "Добро пожаловать в профиль пользователя";
}
@GetMapping("/admin/adminProfile")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String adminProfile() {
return "Добро пожаловать в профиль администратора";
}
@PostMapping("/generateToken")
public String authenticateAndGetToken(@RequestBody AuthRequest authRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
if (authentication.isAuthenticated()) {
return jwtService.generateToken(authRequest.getUsername());
} else {
throw new UsernameNotFoundException("Недействительный пользовательский запрос!");
}
}
}
Ниже приведён мой LoginApplication.java
@SpringBootApplication
public class LoginApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(LoginApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(LoginApplication.class);
}
}
Ответ или решение
Когда ваше Spring-приложение работает в IntelliJ IDE, но не запускается в каталоге webapps вашего сервера Tomcat, необходимо обратить внимание на несколько важных аспектов. Давайте проанализируем вашу ситуацию, исходя из предоставленных данных.
1. Возвращаемый код 403
Ошибка 403 обычно указывает на то, что доступ к запрашиваемому ресурсу запрещен. Это может быть связано с настройками безопасности вашего приложения. В вашем SecurityConfig.java
описаны правила доступа, которые могут блокировать определенные URL. Вы явно разрешаете доступ к /auth/welcome
, однако, стоит проверить настройки, которые могут применяться ко всем запросам.
2. Параметры конфигурации
Обратите внимание на следующее:
- CSRF: Вы отключили защиту от CSRF, что может быть уместно для статeless API, но также помните, что если у вас есть другие защищенные ресурсы, их доступ к ним также может быть заблокирован.
.csrf(csrf -> csrf.disable()) // Отключение CSRF для статeless API
- Настройки авторизации: Убедитесь, что URL, к которому вы обращаетесь, действительно разрешён. Например, если поменяете структуру вашего приложения или добавите путь, не забудьте обновить правила в
securityFilterChain
.
3. Контекстный путь
При развертывании WAR-файла в Tomcat, ваше приложение может потребовать особого контекстного пути. Проверьте, что вы действительно используете корректный базовый URL. Убедитесь, что вы вызываете правильный URL:
http://localhost:8081/logindemo/auth/welcome
Это важный момент, потому что неправильный контекстный путь может вызвать проблемы с доступом.
4. Проверка структуры WAR-файла
Не забудьте убедиться, что WAR-файл, который вы загружаете, действительно содержит все нужные файлы и зависимости. Вы можете проверить содержимое WAR-файла, распаковав его, и убедиться, что WEB-INF
и другие нужные директории присутствуют.
5. Логи Tomcat
Просмотрите логи Tomcat, чтобы узнать, почему доступ к этому ресурсу может быть запрещён. Логи могут содержать полезную информацию о том, какие именно ошибки происходят во время выполнения.
6. Dependencies в POM.xml
У вас в pom.xml
есть зависимости, такие как spring-boot-starter-security
и другие, которые могут быть отключены или работать неправильно в среде Tomcat, если они неправильно настроены или если существуют конфликты версий.
7. Spring Boot и развертывание в Tomcat
Убедитесь, что вы правильно наследуете SpringBootServletInitializer
в вашем основном классе LoginApplication
. У вас это сделано корректно, и это позволяет Tomcat знать, как обрабатывать Spring-приложение.
@SpringBootApplication
public class LoginApplication extends SpringBootServletInitializer {
// ваш код
}
8. Доступ к файловой системе
Убедитесь, что пользователь, под которым запущен сервер Tomcat, имеет соответствующие права доступа к файловой системе, чтобы запускать и читать ваш WAR-файл.
Резюме
Проанализировав перечисленные аспекты, вы сможете более точно определить причину ошибки 403. Убедитесь, что все элементы вашего проекта соответствующим образом настроены как для разработки, так и для продакшн-среды. Если все вышеперечисленные шаги не помогут, рассмотрите возможность создания минимального тестового приложения для изоляции проблемы.