完成登录校验

This commit is contained in:
none 2023-02-17 12:07:15 +08:00
parent 24a9db11c1
commit bd5cf234f1
15 changed files with 232 additions and 79 deletions

49
pom.xml
View File

@ -17,13 +17,15 @@
<java.version>17</java.version> <java.version>17</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -38,50 +40,63 @@
<artifactId>mybatis-spring-boot-starter</artifactId> <artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version> <version>3.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </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> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </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> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version> <version>3.5.3</version>
</dependency> </dependency>
<!--表单验证 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<!--图形验证码依赖 -->
<dependency> <dependency>
<groupId>com.github.penggle</groupId> <groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId> <artifactId>kaptcha</artifactId>
<version>2.3.2</version> <version>2.3.2</version>
</dependency> </dependency>
<!--Fastjson2 -->
<dependency> <dependency>
<groupId>com.alibaba.fastjson2</groupId> <groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId> <artifactId>fastjson2</artifactId>
<version>2.0.22</version> <version>2.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension</artifactId>
<version>2.0.21</version>
</dependency> </dependency>
<dependency> <dependency>
@ -96,6 +111,7 @@
<version>3.12.0</version> <version>3.12.0</version>
</dependency> </dependency>
<!--JWT -->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId> <artifactId>jjwt-api</artifactId>
@ -113,6 +129,7 @@
<version>0.11.5</version> <version>0.11.5</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,16 @@
package xyz.playedu.api.bus;
import xyz.playedu.api.constant.BackendConstant;
public class BackendBus {
public static boolean inUnAuthWhitelist(String uri) {
for (int i = 0; i < BackendConstant.UN_AUTH_URI_WHITELIST.length; i++) {
if (uri.equals(BackendConstant.UN_AUTH_URI_WHITELIST[i])) {
return true;
}
}
return false;
}
}

View File

@ -1,10 +1,10 @@
package xyz.playedu.api.config; package xyz.playedu.api.config;
import com.alibaba.fastjson2.support.spring.data.redis.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration @Configuration
@ -13,14 +13,17 @@ public class RedisConfig {
@Bean(name = "redisTemplate") @Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setValueSerializer(JsonRedisSerializer);
// key值采用StringRedisSerializer序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(JsonRedisSerializer);
// value值采用fastjson2序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
redisTemplate.setValueSerializer(fastJsonRedisSerializer);
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
return redisTemplate; return redisTemplate;
} }

View File

@ -0,0 +1,68 @@
package xyz.playedu.api.config;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.support.config.FastJsonConfig;
import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import xyz.playedu.api.middleware.AdminAuthMiddleware;
import java.util.ArrayList;
import java.util.List;
@Configuration
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private AdminAuthMiddleware adminAuthMiddleware;
@Value("${playedu.cors.origins}")
private String ConfigOrigins;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAuthMiddleware).addPathPatterns("/backend/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins(ConfigOrigins).allowedHeaders("*").allowedMethods("GET", "POST", "DELETE", "PUT").maxAge(3600);
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(fastJsonHttpMessageConverter());
}
private HttpMessageConverter<?> fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 中文乱码解决方案
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON);
fastConverter.setSupportedMediaTypes(mediaTypes);
// 配置
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastJsonConfig.setWriterFeatures(JSONWriter.Feature.PrettyFormat);
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}
}

View File

@ -0,0 +1,5 @@
package xyz.playedu.api.constant;
public class BackendConstant {
public final static String[] UN_AUTH_URI_WHITELIST = {"/backend/v1/system/image-captcha", "/backend/v1/auth/login",};
}

View File

@ -1,24 +1,25 @@
package xyz.playedu.api.controller.admin; package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.AdminUser; import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.middleware.AuthMiddleware;
import xyz.playedu.api.service.AdminUserService; import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.types.PaginationResult; import xyz.playedu.api.types.PaginationResult;
import xyz.playedu.api.types.JsonResponse; import xyz.playedu.api.types.JsonResponse;
@RestController @RestController
@Slf4j
@RequestMapping("/backend/v1/admin-user")
public class AdminUserController { public class AdminUserController {
@Autowired @Autowired
private AdminUserService adminUserService; private AdminUserService adminUserService;
@AuthMiddleware(prv = SystemConstant.JWT_PRV_ADMIN_USER) @GetMapping("/index")
@GetMapping("/admin/user/index")
public JsonResponse List(@RequestParam(name = "page", defaultValue = "1") Integer page, @RequestParam(name = "size", defaultValue = "10") Integer size) { public JsonResponse List(@RequestParam(name = "page", defaultValue = "1") Integer page, @RequestParam(name = "size", defaultValue = "10") Integer size) {
PaginationResult<AdminUser> result = adminUserService.paginate(page, size, null); PaginationResult<AdminUser> result = adminUserService.paginate(page, size, null);
return JsonResponse.data(result); return JsonResponse.data(result);

View File

@ -1,4 +1,4 @@
package xyz.playedu.api.controller.admin; package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.constant.SystemConstant; import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.AdminUser; import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.exception.JwtLogoutException;
import xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware; import xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware;
import xyz.playedu.api.request.LoginRequest; import xyz.playedu.api.request.LoginRequest;
import xyz.playedu.api.service.AdminUserService; import xyz.playedu.api.service.AdminUserService;
@ -22,7 +23,7 @@ import java.util.HashMap;
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/admin/v1/auth") @RequestMapping("/backend/v1/auth")
public class LoginController { public class LoginController {
@Autowired @Autowired
@ -57,8 +58,10 @@ public class LoginController {
} }
@PostMapping("/logout") @PostMapping("/logout")
public JsonResponse logout() { public JsonResponse logout() throws JwtLogoutException {
jwtService.logout(RequestUtil.token(), SystemConstant.JWT_PRV_ADMIN_USER);
return JsonResponse.success("success"); return JsonResponse.success("success");
} }
} }

View File

@ -1,4 +1,4 @@
package xyz.playedu.api.controller.admin; package xyz.playedu.api.controller.backend;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -12,7 +12,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
@RestController @RestController
@RequestMapping("/admin/v1/system") @RequestMapping("/backend/v1/system")
public class SystemController { public class SystemController {
@Autowired @Autowired

View File

@ -0,0 +1,63 @@
package xyz.playedu.api.middleware;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.playedu.api.PlayEduThreadLocal;
import xyz.playedu.api.bus.BackendBus;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.service.JWTService;
import xyz.playedu.api.types.JWTPayload;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.RequestUtil;
import java.io.IOException;
@Component
@Slf4j
public class AdminAuthMiddleware implements HandlerInterceptor {
@Autowired
private JWTService jwtService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (BackendBus.inUnAuthWhitelist(request.getRequestURI())) {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
String token = RequestUtil.token();
if (token.length() == 0) {
responseTransform(response, 401, "请登录");
return false;
}
try {
JWTPayload payload = jwtService.parse(token, SystemConstant.JWT_PRV_ADMIN_USER);
// 用户信息写入context
PlayEduThreadLocal.setAdminUserId(payload.getSub());
return HandlerInterceptor.super.preHandle(request, response, handler);
} catch (Exception e) {
responseTransform(response, 401, "请重新登录");
return false;
}
}
private void responseTransform(HttpServletResponse response, int code, String msg) throws IOException {
response.setStatus(code);
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(JsonResponse.error(msg)));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
PlayEduThreadLocal.remove();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

View File

@ -1,12 +0,0 @@
package xyz.playedu.api.middleware;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthMiddleware {
String prv();
}

View File

@ -2,6 +2,7 @@ package xyz.playedu.api.middleware;
import java.lang.annotation.*; import java.lang.annotation.*;
@Documented
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface ImageCaptchaCheckMiddleware { public @interface ImageCaptchaCheckMiddleware {

View File

@ -1,34 +0,0 @@
package xyz.playedu.api.middleware.impl;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.playedu.api.middleware.AuthMiddleware;
import xyz.playedu.api.service.JWTService;
@Aspect
@Component
@Slf4j
public class AuthMiddlewareImpl {
@Autowired
private JWTService jwtService;
@Pointcut("@annotation(xyz.playedu.api.middleware.AuthMiddleware)")
private void doPointcut() {
}
@Around("doPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
AuthMiddleware authMiddleware = methodSignature.getMethod().getAnnotation(AuthMiddleware.class);
log.info("prv的值:" + authMiddleware.prv());
return joinPoint.proceed();
}
}

View File

@ -1,7 +1,15 @@
package xyz.playedu.api.service; package xyz.playedu.api.service;
import xyz.playedu.api.exception.JwtLogoutException;
import xyz.playedu.api.types.JWTPayload;
import xyz.playedu.api.types.JwtToken; import xyz.playedu.api.types.JwtToken;
public interface JWTService { public interface JWTService {
JwtToken generate(Integer userId, String iss, String prv); JwtToken generate(Integer userId, String iss, String prv);
boolean isInBlack(String jti);
void logout(String token, String prv) throws JwtLogoutException;
JWTPayload parse(String token, String prv) throws JwtLogoutException;
} }

View File

@ -23,6 +23,18 @@ public class RequestUtil {
return null; return null;
} }
public static String token() {
HttpServletRequest request = RequestUtil.handler();
if (request == null) {
return "";
}
String token = request.getHeader("Authorization");
if (token == null || token.length() == 0 || token.split(" ").length != 2) {
return "";
}
return token.split(" ")[1];
}
/** /**
* 获取不带参请求URl * 获取不带参请求URl
* 示例: https://127.0.0.1:8082/api/system/menu/menus * 示例: https://127.0.0.1:8082/api/system/menu/menus

View File

@ -42,5 +42,7 @@ playedu:
key: "eJTJSLPv13fw9twbuPoeicypLqnSfYWL" #32个字符,加密key用来加密jwt的数据[运行本系统之前请务必修改] key: "eJTJSLPv13fw9twbuPoeicypLqnSfYWL" #32个字符,加密key用来加密jwt的数据[运行本系统之前请务必修改]
expire: 1296000 #token有效期[单位:秒,默认15天] expire: 1296000 #token有效期[单位:秒,默认15天]
cache-black-prefix: "jwt:blk:" #主动注销的token黑名单缓存前缀 cache-black-prefix: "jwt:blk:" #主动注销的token黑名单缓存前缀
# CORS
cors:
origins: "*"