feat: 项目初始化
This commit is contained in:
42
dolphin-commons/dolphin-common-core/pom.xml
Normal file
42
dolphin-commons/dolphin-common-core/pom.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>day.gitlab</groupId>
|
||||
<artifactId>dolphin-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dolphin-common-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,8 @@
|
||||
package day.gitlab.dolphin.common.core.constants;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static final String SUCCESS = "success";
|
||||
|
||||
public static final String FAILURE = "failure";
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package day.gitlab.dolphin.common.core.entity;
|
||||
|
||||
import day.gitlab.dolphin.common.core.exception.BusinessException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static day.gitlab.dolphin.common.core.constants.Constants.FAILURE;
|
||||
import static day.gitlab.dolphin.common.core.constants.Constants.SUCCESS;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Result {
|
||||
|
||||
private int code;
|
||||
|
||||
private String message;
|
||||
|
||||
private Object data;
|
||||
|
||||
public static Result success() {
|
||||
return Result.builder().code(200).message(SUCCESS).build();
|
||||
}
|
||||
|
||||
public static Result success(Object data) {
|
||||
Result result = Result.builder().code(200).message(SUCCESS).build();
|
||||
if (data instanceof Supplier) {
|
||||
result.setData(((Supplier<?>) data).get());
|
||||
} else {
|
||||
result.setData(data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Result failure() {
|
||||
return Result.builder().code(500).message(FAILURE).build();
|
||||
}
|
||||
|
||||
public static Result failure(String message) {
|
||||
return Result.builder().code(500).message(message).build();
|
||||
}
|
||||
|
||||
public static Result failure(int code, String message) {
|
||||
return Result.builder().code(code).message(message).build();
|
||||
}
|
||||
|
||||
public static Result failure(BusinessException businessException) {
|
||||
return Result.builder().code(businessException.getCode()).message(businessException.getMessage()).build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package day.gitlab.dolphin.common.core.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package day.gitlab.dolphin.common.core.exception;
|
||||
|
||||
import day.gitlab.dolphin.common.core.entity.Result;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@Component
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionAdvice {
|
||||
|
||||
@ExceptionHandler(value = BusinessException.class)
|
||||
public Result handleBusinessException(BusinessException e) {
|
||||
return Result.failure(e);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
public Result handleException(Exception e) {
|
||||
return Result.failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
59
dolphin-commons/dolphin-common-security/pom.xml
Normal file
59
dolphin-commons/dolphin-common-security/pom.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>day.gitlab</groupId>
|
||||
<artifactId>dolphin-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dolphin-common-security</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>day.gitlab</groupId>
|
||||
<artifactId>dolphin-common-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,36 @@
|
||||
package day.gitlab.dolphin.common.security;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class Authentication {
|
||||
|
||||
private final UserPrincipal userPrincipal;
|
||||
|
||||
private final List<String> userAuthorities;
|
||||
|
||||
public Authentication(UserPrincipal userPrincipal, List<String> userAuthorities) {
|
||||
this.userPrincipal = userPrincipal;
|
||||
this.userAuthorities = userAuthorities;
|
||||
}
|
||||
|
||||
public boolean hasAllAuthorities(String[] userAuthorities) {
|
||||
for (String userAuthority : userAuthorities) {
|
||||
if (!this.userAuthorities.contains(userAuthority)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasAnyAuthorities(String[] userAuthorities) {
|
||||
for (String userAuthority : userAuthorities) {
|
||||
if (this.userAuthorities.contains(userAuthority)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package day.gitlab.dolphin.common.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import day.gitlab.dolphin.common.core.entity.Result;
|
||||
import day.gitlab.dolphin.common.core.exception.BusinessException;
|
||||
import day.gitlab.dolphin.common.security.annotation.AuthorityIgnoreInitializer;
|
||||
import day.gitlab.dolphin.common.security.config.SecurityConfig;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class AuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final SecurityConfig securityConfig;
|
||||
|
||||
private final AuthorityIgnoreInitializer authorityIgnoreInitializer;
|
||||
|
||||
private final AuthenticationInitialize authenticationInitialize;
|
||||
|
||||
public AuthenticationFilter(SecurityConfig securityConfig,
|
||||
AuthorityIgnoreInitializer authorityIgnoreInitializer,
|
||||
AuthenticationInitialize authenticationInitialize) {
|
||||
this.securityConfig = securityConfig;
|
||||
this.authorityIgnoreInitializer = authorityIgnoreInitializer;
|
||||
this.authenticationInitialize = authenticationInitialize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
// 在未启用或是忽略校验的地址时直接放行
|
||||
if (!securityConfig.isEnabled() || authorityIgnoreInitializer.isIgnoreUrl(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
try {
|
||||
Authentication initialize = authenticationInitialize.initialize(request);
|
||||
SecurityContextHolder.setAuthentication(initialize);
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (BusinessException e) {
|
||||
response.setStatus(e.getCode());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.getWriter().write(new ObjectMapper().writeValueAsString(Result.failure(e)));
|
||||
} finally {
|
||||
SecurityContextHolder.clearAuthentication();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package day.gitlab.dolphin.common.security;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public interface AuthenticationInitialize {
|
||||
|
||||
Authentication initialize(HttpServletRequest request);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package day.gitlab.dolphin.common.security;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AuthenticationProvider {
|
||||
|
||||
UserPrincipal getUserPrincipal(String userId);
|
||||
|
||||
List<String> getUserAuthorities(String userId);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package day.gitlab.dolphin.common.security;
|
||||
|
||||
public class SecurityContextHolder {
|
||||
|
||||
private static final ThreadLocal<Authentication> CONTEXT = new ThreadLocal<>();
|
||||
|
||||
private SecurityContextHolder() {}
|
||||
|
||||
public static Authentication getAuthentication() {
|
||||
return CONTEXT.get();
|
||||
}
|
||||
|
||||
public static void setAuthentication(Authentication authentication) {
|
||||
CONTEXT.set(authentication);
|
||||
}
|
||||
|
||||
public static void clearAuthentication() {
|
||||
CONTEXT.remove();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package day.gitlab.dolphin.common.security;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserPrincipal {
|
||||
|
||||
private String id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String nickname;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package day.gitlab.dolphin.common.security.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 使用该注解注解的类会进行权限校验。
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuthorityCheck {
|
||||
|
||||
AuthorityType type() default AuthorityType.AND;
|
||||
|
||||
String[] value();
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package day.gitlab.dolphin.common.security.annotation;
|
||||
|
||||
import day.gitlab.dolphin.common.security.Authentication;
|
||||
import day.gitlab.dolphin.common.security.SecurityContextHolder;
|
||||
import day.gitlab.dolphin.common.security.exception.NotAuthorityException;
|
||||
import day.gitlab.dolphin.common.security.exception.NotLoginException;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
|
||||
@Aspect
|
||||
public class AuthorityCheckAspect {
|
||||
|
||||
@Before("@annotation(authorityCheck)")
|
||||
public void check(JoinPoint joinPoint, AuthorityCheck authorityCheck) {
|
||||
Authentication authentication = SecurityContextHolder.getAuthentication();
|
||||
if (authentication == null) {
|
||||
throw new NotLoginException();
|
||||
}
|
||||
|
||||
// AuthorityCheck的type如果是AND,输出1
|
||||
if (authorityCheck.type() == AuthorityType.AND) {
|
||||
if (!authentication.hasAllAuthorities(authorityCheck.value())) {
|
||||
throw new NotAuthorityException(authorityCheck.value());
|
||||
}
|
||||
} else if (authorityCheck.type() == AuthorityType.OR) {
|
||||
if (!authentication.hasAnyAuthorities(authorityCheck.value())) {
|
||||
throw new NotAuthorityException(authorityCheck.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package day.gitlab.dolphin.common.security.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 使用该注解注解的类或方法会忽略校验
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuthorityIgnore {
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package day.gitlab.dolphin.common.security.annotation;
|
||||
|
||||
import day.gitlab.dolphin.common.security.config.SecurityConfig;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class AuthorityIgnoreInitializer implements ApplicationContextAware {
|
||||
|
||||
private final List<IgnoreUrl> ignoreUrls = new ArrayList<>();
|
||||
|
||||
private SecurityConfig securityConfig;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(@NonNull ApplicationContext ctx) throws BeansException {
|
||||
String contextPath = ctx.getEnvironment().getProperty("server.servlet.context-path");
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping = ctx.getBean(RequestMappingHandlerMapping.class);
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
|
||||
|
||||
if (StringUtils.hasText(securityConfig.getIgnoreUrls())) {
|
||||
Arrays.stream(securityConfig.getIgnoreUrls().split(","))
|
||||
.filter(StringUtils::hasText)
|
||||
.map(s -> {
|
||||
String prefixUrl = StringUtils.hasLength(contextPath) ? contextPath : "";
|
||||
prefixUrl = prefixUrl.endsWith("/") ? prefixUrl.substring(0, prefixUrl.length() - 1) : prefixUrl;
|
||||
return s.startsWith("/") ? prefixUrl + s : prefixUrl + "/" + s;
|
||||
})
|
||||
.map(IgnoreUrl::new)
|
||||
.forEach(url -> this.ignoreUrls.add(url));
|
||||
}
|
||||
|
||||
for (RequestMappingInfo mappingInfo : handlerMethods.keySet()) {
|
||||
HandlerMethod handlerMethod = handlerMethods.get(mappingInfo);
|
||||
Class<?> beanClass = handlerMethod.getBeanType();
|
||||
|
||||
RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
|
||||
if (requestMapping == null) {
|
||||
continue;
|
||||
}
|
||||
AuthorityIgnore authorityIgnore = handlerMethod.getMethodAnnotation(AuthorityIgnore.class);
|
||||
if (authorityIgnore == null && !beanClass.isAnnotationPresent(AuthorityIgnore.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
System.out.println("beanClass:" + beanClass.getCanonicalName());
|
||||
System.out.println("method:" + handlerMethod.getMethod().getName() + " -> " + mappingInfo.getPatternValues());
|
||||
|
||||
List<String> urlPrefix = Arrays.stream(requestMapping.method())
|
||||
.map(RequestMethod::name)
|
||||
.map(StringBuffer::new)
|
||||
.map(sb -> sb.append(":"))
|
||||
.map(sb -> StringUtils.hasText(contextPath) ? sb.append(contextPath) : sb)
|
||||
.map(StringBuffer::toString)
|
||||
.map(sb -> sb.endsWith("/") ? sb.substring(0, sb.length() - 1) : sb)
|
||||
.collect(Collectors.toList());
|
||||
if (urlPrefix.isEmpty()) {
|
||||
String prefixUrl = StringUtils.hasText(contextPath) ? contextPath : "/";
|
||||
if (prefixUrl.endsWith("/")) {
|
||||
prefixUrl = prefixUrl.substring(0, prefixUrl.length() - 1);
|
||||
}
|
||||
urlPrefix.add(prefixUrl);
|
||||
}
|
||||
|
||||
urlPrefix.stream()
|
||||
.flatMap(sub -> mappingInfo.getPatternValues().stream().filter(StringUtils::hasText).map(s -> sub + s))
|
||||
.map(IgnoreUrl::new)
|
||||
.forEach(url -> this.ignoreUrls.add(url));
|
||||
|
||||
System.out.println("ignoreUrls: " + ignoreUrls);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isIgnoreUrl(HttpServletRequest request) {
|
||||
String method = request.getMethod();
|
||||
String url = request.getRequestURI();
|
||||
|
||||
return this.ignoreUrls.stream().anyMatch(ignoreUrl -> ignoreUrl.isIgnoreUrl(method, url));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setSecurityConfig(SecurityConfig securityConfig) {
|
||||
this.securityConfig = securityConfig;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package day.gitlab.dolphin.common.security.annotation;
|
||||
|
||||
public enum AuthorityType {
|
||||
AND,
|
||||
OR
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package day.gitlab.dolphin.common.security.annotation;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class IgnoreUrl {
|
||||
|
||||
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
|
||||
|
||||
private String method;
|
||||
|
||||
private String url;
|
||||
|
||||
public IgnoreUrl(String url) {
|
||||
String urlUpperCase = url.toUpperCase();
|
||||
if (urlUpperCase.startsWith("GET:")) {
|
||||
this.method = "GET";
|
||||
} else if (urlUpperCase.startsWith("HEAD:")) {
|
||||
this.method = "HEAD";
|
||||
} else if (urlUpperCase.startsWith("POST:")) {
|
||||
this.method = "POST";
|
||||
} else if (urlUpperCase.startsWith("PUT:")) {
|
||||
this.method = "PUT";
|
||||
} else if (urlUpperCase.startsWith("PATCH:")) {
|
||||
this.method = "PATCH";
|
||||
} else if (urlUpperCase.startsWith("DELETE:")) {
|
||||
this.method = "DELETE";
|
||||
} else if (urlUpperCase.startsWith("OPTIONS:")) {
|
||||
this.method = "OPTIONS";
|
||||
} else if (urlUpperCase.startsWith("TRACE:")) {
|
||||
this.method = "TRACE";
|
||||
} else {
|
||||
this.method = null;
|
||||
}
|
||||
|
||||
this.url = url.substring(urlUpperCase.indexOf(":") + 1);
|
||||
}
|
||||
|
||||
public boolean isIgnoreUrl(String method, String url) {
|
||||
if (!PATH_MATCHER.match(this.url, url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.method == null || this.method.equalsIgnoreCase(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return method == null ? url : method + ":" + url;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package day.gitlab.dolphin.common.security.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@Component
|
||||
@ConfigurationProperties("dolphin.security")
|
||||
public class SecurityConfig {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String ignoreUrls = "";
|
||||
|
||||
private String secret = "dolphin";
|
||||
|
||||
private long expire = 3600 * 1000L;
|
||||
|
||||
private long refreshTokenExpire = 7 * 24 * 3600 * 1000L;
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package day.gitlab.dolphin.common.security.exception;
|
||||
|
||||
import day.gitlab.dolphin.common.core.exception.BusinessException;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class AuthenticationException extends BusinessException {
|
||||
|
||||
public AuthenticationException(int code, String message) {
|
||||
super(code, message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package day.gitlab.dolphin.common.security.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class InvalidTokenException extends AuthenticationException {
|
||||
|
||||
private final String token;
|
||||
|
||||
public InvalidTokenException(String token) {
|
||||
super(401, "Unauthorized: Invalid token");
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package day.gitlab.dolphin.common.security.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class NotAuthorityException extends AuthenticationException {
|
||||
|
||||
private final String[] userAuthorities;
|
||||
|
||||
public NotAuthorityException(String[] userAuthorities) {
|
||||
super(403, "Forbidden: Insufficient permissions");
|
||||
this.userAuthorities = userAuthorities;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package day.gitlab.dolphin.common.security.exception;
|
||||
|
||||
public class NotLoginException extends AuthenticationException{
|
||||
|
||||
public NotLoginException() {
|
||||
super(401, "Unauthorized");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package day.gitlab.dolphin.common.security.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Getter
|
||||
public class TokenExpiredException extends AuthenticationException {
|
||||
|
||||
private final String token;
|
||||
|
||||
private final Date expiration;
|
||||
|
||||
public TokenExpiredException(String token, Date expiration) {
|
||||
super(401, "Unauthorized: Token expired");
|
||||
this.token = token;
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package day.gitlab.dolphin.common.security.jwt;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Jwt {
|
||||
|
||||
private final String secret;
|
||||
|
||||
private final long expire;
|
||||
|
||||
private final long refreshTokenExpire;
|
||||
|
||||
public Jwt(String secret, long expire, long refreshTokenExpire) {
|
||||
this.secret = secret;
|
||||
this.expire = expire;
|
||||
this.refreshTokenExpire = refreshTokenExpire;
|
||||
}
|
||||
|
||||
public String getTokenFromRequest(HttpServletRequest request) {
|
||||
String token = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
|
||||
return token.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String generateAccessToken(String userId) {
|
||||
return Jwts.builder()
|
||||
.subject(userId)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.issuedAt(new Date())
|
||||
.expiration(new Date(System.currentTimeMillis() + expire))
|
||||
.claim("type", "access")
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String generateRefreshToken(String userId) {
|
||||
return Jwts.builder()
|
||||
.subject(userId)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.issuedAt(new Date())
|
||||
.expiration(new Date(System.currentTimeMillis() + refreshTokenExpire))
|
||||
.claim("type", "refresh")
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Claims parseToken(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package day.gitlab.dolphin.common.security.jwt;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import day.gitlab.dolphin.common.security.Authentication;
|
||||
import day.gitlab.dolphin.common.security.AuthenticationInitialize;
|
||||
import day.gitlab.dolphin.common.security.AuthenticationProvider;
|
||||
import day.gitlab.dolphin.common.security.UserPrincipal;
|
||||
import day.gitlab.dolphin.common.security.config.SecurityConfig;
|
||||
import day.gitlab.dolphin.common.security.exception.InvalidTokenException;
|
||||
import day.gitlab.dolphin.common.security.exception.NotLoginException;
|
||||
import day.gitlab.dolphin.common.security.exception.TokenExpiredException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationInitialize implements AuthenticationInitialize {
|
||||
|
||||
private final SecurityConfig securityConfig;
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
private final AuthenticationProvider authenticationProvider;
|
||||
|
||||
public JwtAuthenticationInitialize(SecurityConfig securityConfig,
|
||||
StringRedisTemplate stringRedisTemplate,
|
||||
AuthenticationProvider authenticationProvider) {
|
||||
this.securityConfig = securityConfig;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.authenticationProvider = authenticationProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication initialize(HttpServletRequest request) {
|
||||
Jwt jwt = new Jwt(securityConfig.getSecret(), securityConfig.getExpire(), securityConfig.getRefreshTokenExpire());
|
||||
|
||||
// 1、获取Token
|
||||
String token = jwt.getTokenFromRequest(request);
|
||||
if (token == null) {
|
||||
throw new NotLoginException();
|
||||
}
|
||||
// 2、解析Token,获取用户ID
|
||||
String userId;
|
||||
Date expiration;
|
||||
try {
|
||||
Claims claims = jwt.parseToken(token);
|
||||
userId = claims.getSubject();
|
||||
expiration = claims.getExpiration();
|
||||
Objects.requireNonNull(userId);
|
||||
Objects.requireNonNull(expiration);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidTokenException(token);
|
||||
}
|
||||
// 3、判断是否过期
|
||||
if (expiration.before(new Date())) {
|
||||
throw new TokenExpiredException(token, expiration);
|
||||
}
|
||||
// 4、从Redis或数据库中加载用户信息
|
||||
UserPrincipal userPrincipal;
|
||||
List<String> userAuthorities;
|
||||
try {
|
||||
String userPrincipalJson = stringRedisTemplate.opsForValue().get("dolphin:user:info:" + userId);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
if (!StringUtils.hasText(userPrincipalJson)) {
|
||||
userPrincipal = authenticationProvider.getUserPrincipal(userId);
|
||||
stringRedisTemplate.opsForValue().set("dolphin:user:info:" + userId, objectMapper.writeValueAsString(userPrincipal));
|
||||
} else {
|
||||
userPrincipal = objectMapper.readValue(userPrincipalJson, UserPrincipal.class);
|
||||
}
|
||||
String userAuthJson = stringRedisTemplate.opsForValue().get("dolphin:user:auth:" + userId);
|
||||
if (!StringUtils.hasText(userAuthJson)) {
|
||||
userAuthorities = authenticationProvider.getUserAuthorities(userId);
|
||||
stringRedisTemplate.opsForValue().set("dolphin:user:auth:" + userId, objectMapper.writeValueAsString(userAuthorities));
|
||||
} else {
|
||||
List<?> list = objectMapper.readValue(userAuthJson, List.class);
|
||||
userAuthorities = list.stream().map(Object::toString).collect(Collectors.toList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new InvalidTokenException(token);
|
||||
}
|
||||
|
||||
return new Authentication(userPrincipal, userAuthorities);
|
||||
}
|
||||
}
|
||||
18
dolphin-commons/pom.xml
Normal file
18
dolphin-commons/pom.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>day.gitlab</groupId>
|
||||
<artifactId>dolphin-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dolphin-commons</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>dolphin-common-core</module>
|
||||
<module>dolphin-common-security</module>
|
||||
</modules>
|
||||
</project>
|
||||
Reference in New Issue
Block a user