mirror of
https://github.com/ulitsaRaskolnikova/is-coursework.git
synced 2026-09-12 20:09:58 +05:00
feat(admin): init
This commit is contained in:
parent
0ea72e749d
commit
1a774ffc9e
25
admin-service/Dockerfile
Normal file
25
admin-service/Dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
FROM gradle:8.5-jdk17 AS build
|
||||
WORKDIR /app
|
||||
|
||||
ENV GRADLE_OPTS="-Dhttps.protocols=TLSv1.2,TLSv1.3"
|
||||
|
||||
COPY build.gradle.kts settings.gradle.kts ./
|
||||
COPY admin-service/build.gradle.kts ./admin-service/
|
||||
COPY common/build.gradle.kts ./common/
|
||||
|
||||
COPY admin-service/src ./admin-service/src
|
||||
COPY common/src ./common/src
|
||||
|
||||
RUN gradle :admin-service:clean :admin-service:bootJar --no-daemon --no-build-cache
|
||||
|
||||
FROM eclipse-temurin:17-jre-jammy
|
||||
WORKDIR /app
|
||||
|
||||
RUN groupadd -r spring && useradd -r -g spring spring
|
||||
USER spring:spring
|
||||
|
||||
COPY --from=build /app/admin-service/build/libs/admin-service.jar app.jar
|
||||
|
||||
EXPOSE 8086
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
57
admin-service/build.gradle.kts
Normal file
57
admin-service/build.gradle.kts
Normal file
@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
id("org.springframework.boot")
|
||||
id("io.spring.dependency-management")
|
||||
id("org.openapi.generator")
|
||||
}
|
||||
|
||||
configure<io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension> {
|
||||
imports {
|
||||
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
|
||||
}
|
||||
}
|
||||
|
||||
openApiGenerate {
|
||||
generatorName.set("spring")
|
||||
inputSpec.set("${project.projectDir}/src/main/resources/static/openapi.yaml")
|
||||
outputDir.set(project.layout.buildDirectory.get().asFile.resolve("generated/openapi").absolutePath)
|
||||
apiPackage.set("ru.itmo.admin.generated.api")
|
||||
modelPackage.set("ru.itmo.admin.generated.model")
|
||||
invokerPackage.set("ru.itmo.admin.generated")
|
||||
configOptions.set(
|
||||
mapOf(
|
||||
"library" to "spring-boot",
|
||||
"useSpringBoot3" to "true",
|
||||
"useBeanValidation" to "true",
|
||||
"openApiNullable" to "false",
|
||||
"useTags" to "true",
|
||||
"configPackage" to "ru.itmo.admin.generated.config",
|
||||
"interfaceOnly" to "true"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
tasks.named("compileJava") {
|
||||
dependsOn("openApiGenerate")
|
||||
}
|
||||
|
||||
sourceSets["main"].java.srcDir(project.layout.buildDirectory.get().asFile.resolve("generated/openapi/src/main/java").absolutePath)
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0")
|
||||
implementation(project(":common"))
|
||||
|
||||
compileOnly("org.projectlombok:lombok")
|
||||
annotationProcessor("org.projectlombok:lombok")
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
|
||||
tasks.bootJar {
|
||||
archiveFileName.set("admin-service.jar")
|
||||
mainClass.set("ru.itmo.admin.AdminServiceApplication")
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package ru.itmo.admin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AdminServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AdminServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package ru.itmo.admin.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import ru.itmo.common.security.JwtUtil;
|
||||
|
||||
@Configuration
|
||||
public class JwtConfig {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Bean
|
||||
public JwtUtil jwtUtil() {
|
||||
return new JwtUtil(secret);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package ru.itmo.admin.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
private static final String BEARER_AUTH = "bearerAuth";
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.components(new Components()
|
||||
.addSecuritySchemes(BEARER_AUTH,
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
.description("Введите JWT access token (получить через POST /api/auth/login)")))
|
||||
.addSecurityItem(new SecurityRequirement().addList(BEARER_AUTH));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package ru.itmo.admin.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import ru.itmo.admin.security.JwtAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/actuator/**", "/health", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||
// All admin endpoints require ADMIN role
|
||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package ru.itmo.admin.controller;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import ru.itmo.admin.generated.api.HealthApi;
|
||||
import ru.itmo.admin.generated.model.HealthResponse;
|
||||
|
||||
@RestController
|
||||
@org.springframework.web.bind.annotation.RequestMapping("${openapi.adminService.base-path:/admin}")
|
||||
public class HealthApiController implements HealthApi {
|
||||
|
||||
@Override
|
||||
public ResponseEntity<HealthResponse> healthCheck() {
|
||||
HealthResponse response = new HealthResponse();
|
||||
response.setStatus("UP");
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package ru.itmo.admin.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<List<String>> handleIllegalArgument(IllegalArgumentException ex) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(List.of(ex.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalStateException.class)
|
||||
public ResponseEntity<List<String>> handleIllegalState(IllegalStateException ex) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(List.of(ex.getMessage()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package ru.itmo.admin.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import ru.itmo.common.security.JwtUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
if (jwtUtil.validateToken(token, "access")) {
|
||||
UUID userId = jwtUtil.getUserIdFromToken(token);
|
||||
Boolean isAdmin = jwtUtil.getIsAdminFromToken(token);
|
||||
|
||||
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
if (Boolean.TRUE.equals(isAdmin)) {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
|
||||
}
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userId,
|
||||
null,
|
||||
authorities
|
||||
);
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// leave context empty, will result in 403 for protected endpoints
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
2
admin-service/src/main/resources/application-docker.yml
Normal file
2
admin-service/src/main/resources/application-docker.yml
Normal file
@ -0,0 +1,2 @@
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:your-256-bit-secret-key-must-be-at-least-32-characters-long-for-security}
|
||||
31
admin-service/src/main/resources/application.yml
Normal file
31
admin-service/src/main/resources/application.yml
Normal file
@ -0,0 +1,31 @@
|
||||
server:
|
||||
port: 8086
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: admin-service
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
|
||||
openapi:
|
||||
adminService:
|
||||
base-path: /admin
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:your-256-bit-secret-key-must-be-at-least-32-characters-long-for-security}
|
||||
|
||||
logging:
|
||||
level:
|
||||
ru.itmo: INFO
|
||||
38
admin-service/src/main/resources/static/openapi.yaml
Normal file
38
admin-service/src/main/resources/static/openapi.yaml
Normal file
@ -0,0 +1,38 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Admin Service API
|
||||
description: API сервиса администрирования
|
||||
version: 0.1.0
|
||||
servers:
|
||||
- url: http://localhost:8086/admin
|
||||
description: Dev server
|
||||
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
summary: Проверка состояния сервиса
|
||||
description: Возвращает статус работоспособности admin-service
|
||||
operationId: healthCheck
|
||||
tags:
|
||||
- Health
|
||||
responses:
|
||||
'200':
|
||||
description: Сервис работает
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthResponse'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
schemas:
|
||||
HealthResponse:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: "UP"
|
||||
@ -153,6 +153,20 @@ services:
|
||||
networks:
|
||||
- domain-registrar-network
|
||||
|
||||
admin-service:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: admin-service/Dockerfile
|
||||
container_name: admin-service
|
||||
ports:
|
||||
- "8086:8086"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=docker
|
||||
networks:
|
||||
- domain-registrar-network
|
||||
|
||||
exdns:
|
||||
build:
|
||||
context: ./exdns
|
||||
|
||||
@ -11,4 +11,4 @@ pluginManagement {
|
||||
|
||||
rootProject.name = "domain-registrar"
|
||||
|
||||
include("api-gateway", "common", "domain-service", "notification-service", "auth-service", "order-service")
|
||||
include("api-gateway", "common", "domain-service", "notification-service", "auth-service", "order-service", "admin-service")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user