Compare commits

...

3 Commits

18 changed files with 194 additions and 24 deletions

View File

@ -6,7 +6,21 @@ plugins {
}
group = "org.mmga"
version = "0.0.5-SNAPSHOT"
version = "0.0.5"
tasks.compileJava {
options.encoding = "UTF-8"
}
tasks.javadoc {
options.encoding = "UTF-8"
}
tasks.register<Jar>("javadocJar") {
archiveClassifier.set("javadoc")
from(tasks.named("javadoc"))
}
tasks.register<Jar>("sourcesJar") {
archiveClassifier.set("sources")
from(sourceSets.main.get().allSource)
}
java {
toolchain {
@ -71,6 +85,9 @@ publishing {
groupId = "org.mmga"
artifactId = "make-minecraft-great-again-spring-boot-starter"
version = version
artifact(tasks.named("javadocJar"))
artifact(tasks.named("sourcesJar"))
pom {
name.set("MakeMinecraftGreatAgainSpringBootStarter")
description.set("A Spring Boot Starter for mmga team.")

View File

@ -6,9 +6,9 @@ import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.mmga.spring.boot.starter.componet.JwtUtils;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.mmga.spring.boot.starter.exception.ServerException;
import org.mmga.spring.boot.starter.interfaces.IdHolder;
import org.springframework.http.HttpStatus;
import org.mmga.spring.boot.starter.properties.AuthorizationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@ -19,6 +19,8 @@ import org.springframework.web.context.request.ServletRequestAttributes;
@RequiredArgsConstructor
public class AuthMappingHandler {
private final JwtUtils jwtUtils;
private final AuthorizationProperties properties;
public Long getUserIdFromResponse(Object response) {
if (response instanceof Result<?> result) {
@ -50,9 +52,10 @@ public class AuthMappingHandler {
if (returnValue instanceof Result<?> result) {
if (!result.isOK()) return;
}
throw new AuthorizationException(Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "登录失败服务端未找到可用的用户id"));
throw new ServerException("登录失败服务端未找到可用的用户id");
}
response.setHeader("Add-Authorization", token);
String setAuthorizationHeader = properties.getSetAuthorizationHeader();
response.setHeader(setAuthorizationHeader, token);
}
}
}

View File

@ -7,13 +7,38 @@ import org.springframework.web.context.request.WebRequest;
import java.lang.annotation.Annotation;
import java.util.Optional;
/**
* 验证处理类
* @param <T> 用户信息类型
*/
public interface AuthorizationHandler<T> {
/**
* 进行验证
* @param token token
* @param ann 注解
* @return 若登录失败则返回空Optional或抛出AuthorizationException异常若登录成功则返回用户信息
*/
Optional<T> auth(String token, Annotation ann);
/**
* 对于WebRequest的token处理方式
* @param request 原始请求
* @param ann 注解
* @return 若登录失败则返回空Optional或抛出AuthorizationException异常若登录成功则返回用户信息
* @throws AuthorizationException 当验证失败时可抛出
*/
default Optional<T> auth(WebRequest request, Annotation ann) throws AuthorizationException {
return auth(request.getHeader("Authorization"), ann);
}
/**
* 对于HttpServletRequest的token处理方式
* @param request 原始请求
* @param ann 注解
* @return 若登录失败则返回空Optional或抛出AuthorizationException异常若登录成功则返回用户信息
* @throws AuthorizationException 当验证失败时可抛出
*/
default Optional<T> auth(HttpServletRequest request, Annotation ann) throws AuthorizationException {
return auth(request.getHeader("Authorization"), ann);
}

View File

@ -2,8 +2,21 @@ package org.mmga.spring.boot.starter.componet;
import java.util.Optional;
/**
* jwt处理接口
*/
public interface JwtUtils {
/**
* 通过用户ID生成jwt
* @param uid 用户ID
* @return jwtToken
*/
String createToken(long uid);
/**
* 验证token
* @param token token
* @return 若成功则返回用户ID否则返回空Optional
*/
Optional<Long> verifyToken(String token);
}

View File

@ -2,6 +2,7 @@ package org.mmga.spring.boot.starter.configuration;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.mmga.spring.boot.starter.properties.AuthorizationProperties;
import org.mmga.spring.boot.starter.properties.Env;
import org.mmga.spring.boot.starter.properties.MainProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -11,9 +12,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties({MainProperties.class})
@EnableConfigurationProperties({MainProperties.class, AuthorizationProperties.class})
public class CorsConfiguration implements WebMvcConfigurer {
private final MainProperties mainProperties;
private final AuthorizationProperties properties;
@Override
@ -21,7 +23,7 @@ public class CorsConfiguration implements WebMvcConfigurer {
if (mainProperties.getEnv().equals(Env.DEV)) {
registry.addMapping("/**") //允许所有路径进行CORS
.allowedHeaders("*")
.exposedHeaders("Add-Authorization")
.exposedHeaders(properties.getSetAuthorizationHeader())
.allowedMethods("*"); //允许所有请求方式
}
}

View File

@ -1,16 +1,16 @@
package org.mmga.spring.boot.starter.configuration;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.mmga.spring.boot.starter.exception.BaseResultException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthorizationException.class)
public ResponseEntity<Result<Void>> handleAuthorizationException(AuthorizationException e) {
Result<Void> result = e.getResult();
@ExceptionHandler(BaseResultException.class)
public ResponseEntity<Result<?>> handleAuthorizationException(BaseResultException e) {
Result<?> result = e.getResult();
return ResponseEntity.ok(result);
}
}

View File

@ -6,6 +6,10 @@ import lombok.Data;
import java.util.List;
/**
* 分页数据
* @param <T> 数据类型
*/
@Data
@Schema(name = "分页数据")
@AllArgsConstructor

View File

@ -1,14 +1,29 @@
package org.mmga.spring.boot.starter.exception;
import lombok.Getter;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.http.HttpStatus;
@Getter
public class AuthorizationException extends RuntimeException {
private final Result<Void> result;
/**
* 登录失败错误
*/
public class AuthorizationException extends BaseResultException {
/**
* @deprecated 此方法不安全
* @see #AuthorizationException(String)
* @param result 使用result
*/
@Deprecated
public AuthorizationException(Result<Void> result) {
super(result.getMsg());
this.result = result;
super(result);
super.setResult(result);
}
/**
将会发送401状态码
* @param message 消息
*/
public AuthorizationException(String message) {
super(Result.failed(HttpStatus.UNAUTHORIZED, message));
}
}

View File

@ -0,0 +1,13 @@
package org.mmga.spring.boot.starter.exception;
import lombok.*;
import org.mmga.spring.boot.starter.entities.Result;
@Getter
@Setter(AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = false)
@ToString
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class BaseResultException extends RuntimeException {
private Result<?> result;
}

View File

@ -0,0 +1,17 @@
package org.mmga.spring.boot.starter.exception;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.http.HttpStatus;
/**
* 异常类
*/
public class ResultException extends BaseResultException {
/**
* @param httpStatus http状态码
* @param message 消息
*/
public ResultException(HttpStatus httpStatus, String message) {
super(Result.failed(httpStatus, message));
}
}

View File

@ -0,0 +1,17 @@
package org.mmga.spring.boot.starter.exception;
import org.mmga.spring.boot.starter.entities.Result;
import org.springframework.http.HttpStatus;
/**
* 服务端错误500 Internal Server Error
*/
public class ServerException extends BaseResultException {
/**
* 将会返回500
* @param message 消息
*/
public ServerException(String message) {
super(Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, message));
}
}

View File

@ -1,5 +1,12 @@
package org.mmga.spring.boot.starter.interfaces;
/**
* id拥有者容器
*/
public interface IdHolder {
/**
* 获取ID
* @return ID
*/
Long getId();
}

View File

@ -5,12 +5,34 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 用户验证相关配置
*/
@ConfigurationProperties(prefix = "mmga.auth")
@Data
public class AuthorizationProperties {
/**
* jwt使用的hmac密钥RANDOM为随机密钥
*/
private String hmacKey = "RANDOM";
/**
* 设置token所使用的请求头
*/
private String setAuthorizationHeader = "Add-Authorization";
/**
* token有效期
*/
private int tokenLifeTimeHour = 24 * 5;
/**
* 密钥的存储方式
*/
private ECKeySave keySave = ECKeySave.MEMORY;
/**
* 密钥的存储文件名当keySave为DISK时不可为空
* @see #keySave
* @see ECKeySave
*/
@Nullable
private String keySaveFilename = null;
}

View File

@ -3,9 +3,18 @@ package org.mmga.spring.boot.starter.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 文档相关配置
*/
@ConfigurationProperties(prefix = "mmga.docs")
@Data
public class DocsProperties {
/**
* 文档标题
*/
private String title = "API文档";
/**
* 文档详情
*/
private String description = "";
}

View File

@ -1,5 +1,12 @@
package org.mmga.spring.boot.starter.properties;
public enum Env {
PROD, DEV
/**
* 生产环境
*/
PROD,
/**
* 开发环境
*/
DEV
}

View File

@ -6,5 +6,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "mmga")
@Data
public class MainProperties {
/**
* 环境类型
*/
private Env env = Env.DEV;
}

View File

@ -3,10 +3,8 @@ package org.mmga.spring.boot.starter.utils;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.mmga.spring.boot.starter.componet.AuthorizationHandler;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
@ -35,7 +33,7 @@ public class AuthorizationArgumentResolver implements HandlerMethodArgumentResol
assert parameterAnnotation != null;
Optional<?> auth = this.handler.auth(webRequest, parameterAnnotation);
if (auth.isEmpty()) {
throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "缺少token"));
throw new AuthorizationException("缺少token");
}
return auth.get();
}

View File

@ -4,9 +4,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.mmga.spring.boot.starter.componet.AuthorizationHandler;
import org.mmga.spring.boot.starter.entities.Result;
import org.mmga.spring.boot.starter.exception.AuthorizationException;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
@ -30,7 +28,7 @@ public class AuthorizationHandlerInterceptor implements HandlerInterceptor {
Annotation annotation = method.getMethodAnnotation(authAnnotation);
Optional<?> auth = this.handler.auth(request, annotation);
if (auth.isEmpty()) {
throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "缺少token"));
throw new AuthorizationException("缺少token");
}
return true;
}