init
This commit is contained in:
46
ski-dashboard-admin/pom.xml
Normal file
46
ski-dashboard-admin/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.ski</groupId>
|
||||
<artifactId>ski-dashboard</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ski-dashboard-admin</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.8.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ski.lichuan</groupId>
|
||||
<artifactId>ski-dashboard-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ski.lichuan</groupId>
|
||||
<artifactId>ski-dashboard-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ski</groupId>
|
||||
<artifactId>ski-dashboard-model</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ski.lichuan.admin;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"com.ski.lichuan"})
|
||||
@MapperScan("com.ski.lichuan.mapper")
|
||||
public class SkiDashboardAdminApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SkiDashboardAdminApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.ski.lichuan.admin.config;
|
||||
|
||||
import com.ski.lichuan.admin.filter.JwtAuthenticationFilter;
|
||||
import com.ski.lichuan.admin.handler.JwtAuthenticationEntryPoint;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
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.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthFilter;
|
||||
@Autowired
|
||||
private JwtAuthenticationEntryPoint jwtAuthEntryPoint; // 自定义认证失败处理器
|
||||
|
||||
|
||||
// 密码加密方式(BCrypt)
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
// 配置认证管理器
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// 关闭 CSRF(前后端分离场景下禁用)
|
||||
.csrf(csrf -> csrf.disable())
|
||||
// 配置跨域
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
// 配置认证失败处理器(返回 JSON 而非默认页面)
|
||||
.exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthEntryPoint))
|
||||
// 配置 URL 权限规则
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// 放行 Swagger UI 相关路径
|
||||
.requestMatchers(
|
||||
"/api/auth/login",
|
||||
"/swagger-ui.html",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**", // SpringDoc OpenAPI 3.0 文档接口
|
||||
"/swagger-resources/**" // 旧版 Swagger 资源
|
||||
).permitAll()
|
||||
// 其他路径需要认证
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// 不使用 Session(无状态)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
// 添加 JWT 过滤器(在 UsernamePasswordAuthenticationFilter 之前执行)
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// 跨域配置
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOriginPattern("*"); // 允许所有源(生产环境需限制)
|
||||
config.addAllowedHeader("*"); // 允许所有请求头
|
||||
config.addAllowedMethod("*"); // 允许所有请求方法
|
||||
config.setAllowCredentials(true); // 允许携带 Cookie
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ski.lichuan.admin.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class SpringDocConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("SKI接口文档") // API 标题(ApiFox 中显示)
|
||||
.version("1.0.0") // 版本号
|
||||
.description("用于用户登录、信息查询的接口文档,适配 ApiFox")); // 描述
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.ski.lichuan.admin.controller.auth;
|
||||
|
||||
import com.ski.lichuan.admin.controller.auth.dto.LoginRequest;
|
||||
import com.ski.lichuan.common.utils.JwtUtils;
|
||||
import com.ski.lichuan.model.SysUser;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@CrossOrigin(origins = "*") // 根据实际需要调整CORS策略
|
||||
@Tag(name = "认证模块", description = "用户登录、登出、获取用户信息等接口")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
|
||||
/**
|
||||
* 用户登录接口
|
||||
*
|
||||
* @param loginRequest 登录请求参数
|
||||
* @return 登录结果
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "用户登录", description = "根据用户名和密码登录,返回JWT Token")
|
||||
public ResponseEntity<Map<String, Object>> login(@RequestBody LoginRequest loginRequest) {
|
||||
// 1. 构造认证请求(用户名+密码)
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
|
||||
|
||||
// 2. 触发认证(会调用 UserDetailsService.loadUserByUsername 验证用户)
|
||||
Authentication authentication = authenticationManager.authenticate(authToken);
|
||||
|
||||
// 3. 认证成功,生成 Token
|
||||
SysUser user = (SysUser) authentication.getPrincipal();
|
||||
String token = jwtUtils.generateToken(user.getUsername());
|
||||
|
||||
// 4. 构建响应体
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", "登录成功");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("token", token);
|
||||
data.put("nickname", user.getNickname());
|
||||
response.put("data", data);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息接口
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
@GetMapping("/userinfo")
|
||||
@Operation(summary = "获取当前用户信息", description = "根据JWT Token获取当前登录用户的基本信息")
|
||||
public ResponseEntity<Map<String, Object>> getUserInfo() {
|
||||
log.info("获取用户信息请求");
|
||||
|
||||
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
String nickname = ((SysUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getNickname();
|
||||
|
||||
return ResponseEntity.ok(Map.of("username", username, "nickname", nickname));
|
||||
}
|
||||
|
||||
// 内部类:响应结果
|
||||
@Data
|
||||
public static class LoginVo {
|
||||
private String token;
|
||||
private String nickname;
|
||||
public LoginVo(String token, String nickname) {
|
||||
this.token = token;
|
||||
this.nickname = nickname;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ski.lichuan.admin.controller.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Schema(description = "登录请求参数")
|
||||
public class LoginRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 验证码(可选)
|
||||
*/
|
||||
private String captcha;
|
||||
|
||||
/**
|
||||
* 记住我
|
||||
*/
|
||||
private Boolean rememberMe = false;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.ski.lichuan.admin.filter;
|
||||
|
||||
import com.ski.lichuan.common.utils.JwtUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
// 1. 从请求头获取 Token(格式:Bearer <token>)
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String token = authHeader.substring(7); // 截取 "Bearer " 后的内容
|
||||
|
||||
// 2. 验证 Token 并获取用户名
|
||||
if (jwtUtils.validateToken(token)) {
|
||||
String username = jwtUtils.getUsernameFromToken(token);
|
||||
// 3. 加载用户信息并设置认证状态
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ski.lichuan.admin.handler;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
@Override
|
||||
public void commence(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 未授权
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"" + authException.getMessage() + "\"}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
spring.application.name=ski-dashboard-admin
|
||||
|
||||
# ??????? postgresql
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/ski_dashboard
|
||||
spring.datasource.username=postgres
|
||||
spring.datasource.password=tanlifan
|
||||
|
||||
# Spring Session??
|
||||
spring.session.store-type=jdbc
|
||||
spring.session.jdbc.initialize-schema=always
|
||||
spring.session.jdbc.table-name=SPRING_SESSION
|
||||
|
||||
JWT_SECRET=vf4JZhcyfdK7tJs0GZ3Qjf0dSv4BId9ITjsM2fol26gOBxM17nUySiMcV0Lo2u0Y
|
||||
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE SPRING_SESSION (
|
||||
PRIMARY_ID CHAR(36) NOT NULL,
|
||||
SESSION_ID CHAR(36) NOT NULL,
|
||||
CREATION_TIME BIGINT NOT NULL,
|
||||
LAST_ACCESS_TIME BIGINT NOT NULL,
|
||||
MAX_INACTIVE_INTERVAL INTEGER NOT NULL,
|
||||
EXPIRY_TIME BIGINT NOT NULL,
|
||||
PRINCIPAL_NAME VARCHAR(100),
|
||||
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
|
||||
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
|
||||
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
|
||||
ATTRIBUTE_BYTES BYTEA NOT NULL,
|
||||
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
|
||||
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
|
||||
);
|
||||
Reference in New Issue
Block a user