diff --git a/build.gradle b/build.gradle index e4a47e1..6c412ac 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,15 @@ dependencies { // Web 기본 implementation 'org.springframework.boot:spring-boot-starter-web' + // security + implementation 'org.springframework.boot:spring-boot-starter-security' + + // jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/alist/api/ApiApplication.java b/src/main/java/com/alist/api/ApiApplication.java index 50a4ea8..222e71e 100644 --- a/src/main/java/com/alist/api/ApiApplication.java +++ b/src/main/java/com/alist/api/ApiApplication.java @@ -2,8 +2,10 @@ package com.alist.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class ApiApplication { public static void main(String[] args) { diff --git a/src/main/java/com/alist/api/common/response/ApiResponse.java b/src/main/java/com/alist/api/common/response/ApiResponse.java new file mode 100644 index 0000000..d0ff98a --- /dev/null +++ b/src/main/java/com/alist/api/common/response/ApiResponse.java @@ -0,0 +1,34 @@ +package com.alist.api.common.response; + +import lombok.Getter; +import org.springframework.http.ResponseEntity; + +@Getter +public class ApiResponse { + private T data; // 실제 데이터 + private String code; // 결과 코드 (SUCCESS, ERROR_001 등) + private String message; // 사용자 메시지 + + public ApiResponse(T data, String code, String message) { + this.data = data; + this.code = code; + this.message = message; + } + + public static ApiResponse body(ApiResponseCode code, Object... args) { + return new ApiResponse<>(null, code.code(), code.message(args)); + } + + public static ApiResponse body(T data, ApiResponseCode code, Object... args) { + return new ApiResponse<>(data, code.code(), code.message(args)); + } + + public static ResponseEntity> entity(ApiResponseCode code, Object... args) { + return ResponseEntity.status(code.httpStatus()).body(ApiResponse.body(code, args)); + } + + public static ResponseEntity> entity(T data, ApiResponseCode code, Object... args) { + return ResponseEntity.status(code.httpStatus()).body(ApiResponse.body(data, code, args)); + } + +} diff --git a/src/main/java/com/alist/api/common/response/ApiResponseCode.java b/src/main/java/com/alist/api/common/response/ApiResponseCode.java new file mode 100644 index 0000000..c515e84 --- /dev/null +++ b/src/main/java/com/alist/api/common/response/ApiResponseCode.java @@ -0,0 +1,53 @@ +package com.alist.api.common.response; + +import org.springframework.http.HttpStatus; + +import java.text.MessageFormat; + +public enum ApiResponseCode { + // ===== Common ===== + CODE_200 ("200", "성공", HttpStatus.OK), + + CODE_400 ("400", "잘못된 요청", HttpStatus.BAD_REQUEST), + CODE_401 ("401", "인증 필요 합니다.", HttpStatus.UNAUTHORIZED), + CODE_403 ("403", "접근 권한 필요 합니다.", HttpStatus.FORBIDDEN), + CODE_404 ("404", "페이지를 찾을 수 없습니다. 입력하신 주소가 올바른지 확인해주세요.", HttpStatus.NOT_FOUND), + CODE_405 ("405", "잘못된 요청입니다. 요청 방식을 확인해 주세요.", HttpStatus.METHOD_NOT_ALLOWED), + CODE_500 ("500", "요청을 처리하는 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), + + // ===== Success detail ===== + CODE_2001("2001", "{0} 정보 조회에 성공하였습니다.", HttpStatus.OK), + CODE_2002("2002", "{0} 등록 되었습니다.", HttpStatus.CREATED), + CODE_2003("2003", "조회된 정보가 없습니다.", HttpStatus.OK), + CODE_2004("2004", "중복된 {0} 정보 입니다.", HttpStatus.CONFLICT), + + // ===== Client input errors ===== + // @Valid / 바인딩 / 타입미스매치 / JSON 파싱 실패 등은 다 여기로 + CODE_4001("4001", "입력값을 확인해주세요.", HttpStatus.BAD_REQUEST), + + // 필수 요청 파라미터 누락 + CODE_4003("4003", "필수 요청 파라미터가 누락되었습니다.", HttpStatus.BAD_REQUEST), + ; + + private final String code; + private final String message; + private final HttpStatus httpStatus; + + ApiResponseCode(String code, String message, HttpStatus httpStatus) { + this.code = code; + this.message = message; + this.httpStatus = httpStatus; + } + + public String code() { return code; } + + public String message() { return message; } + + public String message(Object... args) { + return MessageFormat.format(this.message, args); + } + + public HttpStatus httpStatus() { + return httpStatus; + } +} diff --git a/src/main/java/com/alist/api/common/utils/ApiKeyGenerator.java b/src/main/java/com/alist/api/common/utils/ApiKeyGenerator.java new file mode 100644 index 0000000..6b52442 --- /dev/null +++ b/src/main/java/com/alist/api/common/utils/ApiKeyGenerator.java @@ -0,0 +1,16 @@ +package com.alist.api.common.utils; + +import java.security.SecureRandom; +import java.util.Base64; + +public class ApiKeyGenerator { + + private static final SecureRandom secureRandom = new SecureRandom(); + private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder().withoutPadding(); + + public static String userApiKeyProc() { + byte[] randomBytes = new byte[32]; // 256-bit + secureRandom.nextBytes(randomBytes); + return base64Encoder.encodeToString(randomBytes); + } +} diff --git a/src/main/java/com/alist/api/config/OpenApiConfig.java b/src/main/java/com/alist/api/config/OpenApiConfig.java index 9a74fe0..484ff1c 100644 --- a/src/main/java/com/alist/api/config/OpenApiConfig.java +++ b/src/main/java/com/alist/api/config/OpenApiConfig.java @@ -2,6 +2,11 @@ package com.alist.api.config; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Info; +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 @@ -13,4 +18,20 @@ import org.springframework.context.annotation.Configuration; ) ) public class OpenApiConfig { + private static final String SECURITY_SCHEME_NAME = "bearerAuth"; + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)) + .components(new Components() + .addSecuritySchemes(SECURITY_SCHEME_NAME, + new SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } } diff --git a/src/main/java/com/alist/api/config/SecurityConfig.java b/src/main/java/com/alist/api/config/SecurityConfig.java new file mode 100644 index 0000000..536c467 --- /dev/null +++ b/src/main/java/com/alist/api/config/SecurityConfig.java @@ -0,0 +1,86 @@ +package com.alist.api.config; + +import com.alist.api.config.jwt.JwtAccessDeniedHandler; +import com.alist.api.config.jwt.JwtAuthenticationEntryPoint; +import com.alist.api.config.jwt.JwtAuthenticationFilter; +import com.alist.api.config.jwt.JwtTokenProvider; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; +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.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Slf4j +@Configuration +@EnableWebSecurity +public class SecurityConfig { + @Value("${swagger.login.id}") + private String swaggerLoginId; + + @Value("${swagger.login.password}") + private String swaggerLoginPassword; + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + return new JwtAuthenticationFilter(jwtTokenProvider); + } + + @Bean + @Order(1) + public SecurityFilterChain swaggerFilterChain(HttpSecurity http) throws Exception { + http + .securityMatcher("/", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html") + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()); + + return http.build(); + } + + @Bean + @Order(2) + public SecurityFilterChain apiFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) + .accessDeniedHandler(new JwtAccessDeniedHandler()) + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/actuator/health", "/auth/**", "/api/user/signup").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public UserDetailsService userDetailsService() { + UserDetails alist = User.builder() + .username(swaggerLoginId) + .password(passwordEncoder().encode(swaggerLoginPassword)) + .roles("ADMIN") + .build(); + + return new InMemoryUserDetailsManager(alist); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/alist/api/config/exception/GlobalExceptionHandler.java b/src/main/java/com/alist/api/config/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..e3f6cc6 --- /dev/null +++ b/src/main/java/com/alist/api/config/exception/GlobalExceptionHandler.java @@ -0,0 +1,105 @@ +package com.alist.api.config.exception; + +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.javassist.NotFoundException; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgument(IllegalArgumentException e) { + + log.warn("IllegalArgumentException: {}", e.getMessage()); + + return ApiResponse.entity(ApiResponseCode.CODE_400); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity> handleMissingParam(MissingServletRequestParameterException e) { + + log.warn("Missing request parameter: {} (type={})", e.getParameterName(), e.getParameterType()); + + return ApiResponse.entity(ApiResponseCode.CODE_4003); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity> handleMethodNotSupported(HttpRequestMethodNotSupportedException e) { + + log.warn("Method not supported: {} (supported={})", e.getMethod(), e.getSupportedHttpMethods()); + + return ApiResponse.entity(ApiResponseCode.CODE_405); + } + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity> handleNotFound(NotFoundException e) { + + log.warn("Not found: {}", e.getMessage()); + + return ApiResponse.entity(ApiResponseCode.CODE_404); + } + + // 입력오류 + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity>> handleMethodArgumentNotValid(MethodArgumentNotValidException e) { + Map fieldErrors = new LinkedHashMap<>(); + + for (FieldError fe : e.getBindingResult().getFieldErrors()) { + fieldErrors.putIfAbsent(fe.getField(), fe.getDefaultMessage()); + } + + log.warn("Validation failed: {}", e.getMessage()); + + return ApiResponse.entity(fieldErrors, ApiResponseCode.CODE_4001); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity> handleTypeMismatch(MethodArgumentTypeMismatchException e) { + + log.warn("Type mismatch: name={}, value={}", e.getName(), e.getValue()); + + return ApiResponse.entity(ApiResponseCode.CODE_4001); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity> handleNotReadable(HttpMessageNotReadableException e) { + + log.warn("Unreadable message (json parse?) : {}", e.getMessage()); + + return ApiResponse.entity(ApiResponseCode.CODE_4001); + } + + @ExceptionHandler(BindException.class) + public ResponseEntity>> handleBindException(BindException e) { + Map fieldErrors = new LinkedHashMap<>(); + for (FieldError fe : e.getBindingResult().getFieldErrors()) { + fieldErrors.putIfAbsent(fe.getField(), fe.getDefaultMessage()); + } + log.warn("Bind failed: {}", e.getMessage()); + + return ApiResponse.entity(fieldErrors, ApiResponseCode.CODE_4001); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception e) { + + log.error("Unhandled exception occurred", e); + + return ApiResponse.entity(ApiResponseCode.CODE_500); + } +} diff --git a/src/main/java/com/alist/api/config/jwt/JwtAccessDeniedHandler.java b/src/main/java/com/alist/api/config/jwt/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..2f4f115 --- /dev/null +++ b/src/main/java/com/alist/api/config/jwt/JwtAccessDeniedHandler.java @@ -0,0 +1,29 @@ +package com.alist.api.config.jwt; + +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import java.io.IOException; + +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException e) throws IOException { + + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + ApiResponse body = ApiResponse.body(ApiResponseCode.CODE_403); + objectMapper.writeValue(response.getOutputStream(), body); + } +} diff --git a/src/main/java/com/alist/api/config/jwt/JwtAuthenticationEntryPoint.java b/src/main/java/com/alist/api/config/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..478aaac --- /dev/null +++ b/src/main/java/com/alist/api/config/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,28 @@ +package com.alist.api.config.jwt; + +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import java.io.IOException; + +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException e) throws IOException { + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + ApiResponse body = ApiResponse.body(ApiResponseCode.CODE_401); + objectMapper.writeValue(response.getOutputStream(), body); + } +} diff --git a/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java b/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..231cffb --- /dev/null +++ b/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,60 @@ +package com.alist.api.config.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; + +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtTokenProvider jwtTokenProvider; + + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + try { + String token = resolveToken(request); + + if (token != null && jwtTokenProvider.validateToken(token)) { + String userId = jwtTokenProvider.getUserId(token); + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList()); + + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception e) { + SecurityContextHolder.clearContext(); + // 로깅은 여기서 해도 됨 (SLF4J) + } + + filterChain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request) { + String bearer = request.getHeader(HttpHeaders.AUTHORIZATION); + if (bearer == null) return null; + + // "Bearer " 뒤 토큰만 추출 + if (bearer.startsWith("Bearer ")) { + String token = bearer.substring(7).trim(); + return token.isEmpty() ? null : token; + } + return null; + } +} diff --git a/src/main/java/com/alist/api/config/jwt/JwtTokenProvider.java b/src/main/java/com/alist/api/config/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..a75701b --- /dev/null +++ b/src/main/java/com/alist/api/config/jwt/JwtTokenProvider.java @@ -0,0 +1,92 @@ +package com.alist.api.config.jwt; + +import com.alist.api.config.properties.JwtProperties; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Date; + +@Component +public class JwtTokenProvider { + private final JwtProperties jwtProperties; + private final SecretKey secretKey; + + public JwtTokenProvider(JwtProperties jwtProperties) { + this.jwtProperties = jwtProperties; + this.secretKey = Keys.hmacShaKeyFor( + jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8) + ); + } + + /** 토큰생성 **/ + public String createToken(String userId) { + Date now = new Date(); + Date expiry = new Date( + now.getTime() + jwtProperties.getAccessTokenValiditySeconds() * 1000 + ); + + return Jwts.builder() + .setSubject(userId) + .setIssuedAt(now) + .setExpiration(expiry) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + /* 엑세스 토큰 생성 */ + public String createAccessToken(long userTokenIdx, String role) { + Instant now = Instant.now(); + Instant expiry = now.plusSeconds(jwtProperties.getAccessTokenValiditySeconds()); + + return Jwts.builder() + .setSubject(String.valueOf(userTokenIdx)) + .claim("role", role) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(expiry)) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + /* 리프레시 토큰 생성 */ + public String createRefreshToken(long userTokenIdx) { + Instant now = Instant.now(); + Instant expiry = now.plusSeconds(jwtProperties.getAccessTokenValiditySeconds()); + + return Jwts.builder() + .setSubject(String.valueOf(userTokenIdx)) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(expiry)) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + /** 토큰에서 subject 추출 */ + public String getUserId(String token) { + return parseClaims(token).getSubject(); + } + + /** 토큰 검증 */ + public boolean validateToken(String token) { + try { + parseClaims(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + private Claims parseClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/src/main/java/com/alist/api/config/properties/JwtProperties.java b/src/main/java/com/alist/api/config/properties/JwtProperties.java new file mode 100644 index 0000000..dbda00a --- /dev/null +++ b/src/main/java/com/alist/api/config/properties/JwtProperties.java @@ -0,0 +1,14 @@ +package com.alist.api.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "jwt") +public class JwtProperties { + String secret; + long accessTokenValiditySeconds; + long refreshTokenValiditySeconds; +} diff --git a/src/main/java/com/alist/api/modules/main/MainController.java b/src/main/java/com/alist/api/modules/main/MainController.java new file mode 100644 index 0000000..49c43e2 --- /dev/null +++ b/src/main/java/com/alist/api/modules/main/MainController.java @@ -0,0 +1,14 @@ +package com.alist.api.modules.main; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/") +public class MainController { + @GetMapping(value = "/") + public String index() { + return "redirect:/swagger-ui/index.html"; + } +} diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index c08debb..159f684 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -10,6 +10,16 @@ spring: logging: config: classpath:logback-local.xml +jwt: + secret: "F9k3s!29dkF#1lP0X9QZx8eW!2m@0AbD" + access-token-validity-seconds: 3600 + refresh-token-validity-seconds: 2592000 + +swagger: + login: + id: alist + password: "1qaz2wsx!@" + springdoc: api-docs: enabled: true diff --git a/src/main/resources/application-pjt.yaml b/src/main/resources/application-pjt.yaml index 42cab57..33ef9c3 100644 --- a/src/main/resources/application-pjt.yaml +++ b/src/main/resources/application-pjt.yaml @@ -12,6 +12,16 @@ spring: logging: config: classpath:logback-pjt.xml +jwt: + secret: ${JWT_SECRET} + access-token-validity-seconds: 3600 + refresh-token-validity-seconds: 2592000 + +swagger: + login: + id: ${SWAGGER_ID} + password: ${SWAGGER_PASSWORD} + springdoc: api-docs: enabled: true diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 926d19e..fce5e21 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -15,7 +15,7 @@ mybatis: springdoc: swagger-ui: - path: /swagger + path: /swagger-ui.html api-docs: path: /v3/api-docs