Compare commits
	
		
			4 Commits 
		
	
	
		
			5e5fb9a07d
			...
			3b598b0e81
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						3b598b0e81 | 2 years ago | 
| 
							
							
								 | 
						077751021e | 2 years ago | 
| 
							
							
								 | 
						7e63ad937d | 2 years ago | 
| 
							
							
								 | 
						e044f02d32 | 2 years ago | 
				 19 changed files with 887 additions and 112 deletions
			
			
		@ -0,0 +1,18 @@ | 
				
			||||
package cn.soul2.jyjc.admin.annotation; | 
				
			||||
 | 
				
			||||
import java.lang.annotation.ElementType; | 
				
			||||
import java.lang.annotation.Retention; | 
				
			||||
import java.lang.annotation.RetentionPolicy; | 
				
			||||
import java.lang.annotation.Target; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * 跳过加解密 | 
				
			||||
 * | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @date 2024-04-08 | 
				
			||||
 */ | 
				
			||||
@Target({ElementType.METHOD, ElementType.TYPE}) | 
				
			||||
@Retention(RetentionPolicy.RUNTIME) | 
				
			||||
public @interface SkinEncrypt { | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -1,28 +0,0 @@ | 
				
			||||
package cn.soul2.jyjc.admin.config; | 
				
			||||
 | 
				
			||||
import org.springframework.beans.factory.annotation.Value; | 
				
			||||
import org.springframework.context.annotation.Configuration; | 
				
			||||
import org.springframework.web.servlet.config.annotation.CorsRegistry; | 
				
			||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @date 2024-03-25 | 
				
			||||
 */ | 
				
			||||
@Configuration | 
				
			||||
public class CorsConfig implements WebMvcConfigurer { | 
				
			||||
 | 
				
			||||
    @Value("${cors.allow-origin}") | 
				
			||||
    private String[] allowOrigin; | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public void addCorsMappings(CorsRegistry registry) { | 
				
			||||
        registry.addMapping("/**") | 
				
			||||
                .allowedHeaders("*") | 
				
			||||
                .allowedOrigins(allowOrigin) | 
				
			||||
                .allowCredentials(true) | 
				
			||||
                .allowedMethods("POST") | 
				
			||||
                .maxAge(3600); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,41 @@ | 
				
			||||
package cn.soul2.jyjc.admin.config; | 
				
			||||
 | 
				
			||||
import cn.soul2.jyjc.admin.filter.ReplaceStreamFilter; | 
				
			||||
import org.springframework.boot.web.servlet.FilterRegistrationBean; | 
				
			||||
import org.springframework.context.annotation.Bean; | 
				
			||||
import org.springframework.context.annotation.Configuration; | 
				
			||||
 | 
				
			||||
import javax.servlet.Filter; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @description 过滤器配置类 | 
				
			||||
 * @date 2024-04-08 | 
				
			||||
 * <p>照抄自 <a href="https://blog.csdn.net/shaoduo/article/details/122322578">Shao duo</a></p> | 
				
			||||
 */ | 
				
			||||
@Configuration | 
				
			||||
public class FilterConfig { | 
				
			||||
    /** | 
				
			||||
     * 注册过滤器 | 
				
			||||
     * | 
				
			||||
     * @return FilterRegistrationBean | 
				
			||||
     */ | 
				
			||||
    @Bean | 
				
			||||
    public FilterRegistrationBean someFilterRegistration() { | 
				
			||||
        FilterRegistrationBean registration = new FilterRegistrationBean(); | 
				
			||||
        registration.setFilter(replaceStreamFilter()); | 
				
			||||
        registration.addUrlPatterns("/*"); | 
				
			||||
        registration.setName("streamFilter"); | 
				
			||||
        return registration; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 实例化StreamFilter | 
				
			||||
     * | 
				
			||||
     * @return Filter | 
				
			||||
     */ | 
				
			||||
    @Bean(name = "replaceStreamFilter") | 
				
			||||
    public Filter replaceStreamFilter() { | 
				
			||||
        return new ReplaceStreamFilter(); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,60 @@ | 
				
			||||
package cn.soul2.jyjc.admin.filter; | 
				
			||||
 | 
				
			||||
import cn.soul2.jyjc.admin.utils.EncryptUtils; | 
				
			||||
import lombok.extern.slf4j.Slf4j; | 
				
			||||
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; | 
				
			||||
 | 
				
			||||
import javax.servlet.*; | 
				
			||||
import javax.servlet.http.HttpServletRequest; | 
				
			||||
import javax.servlet.http.HttpServletResponse; | 
				
			||||
import java.io.IOException; | 
				
			||||
import java.io.PrintWriter; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author shaoduo | 
				
			||||
 * @description 替换HttpServletRequest | 
				
			||||
 * @since 1.0 | 
				
			||||
 **/ | 
				
			||||
@Slf4j | 
				
			||||
public class ReplaceStreamFilter implements Filter { | 
				
			||||
    @Override | 
				
			||||
    public void init(FilterConfig filterConfig) throws ServletException { | 
				
			||||
        log.info("StreamFilter初始化..."); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | 
				
			||||
        //如果是文件上传则会报错可以判断是否是文件上传不读取流即可
 | 
				
			||||
        if (ServletFileUpload.isMultipartContent((HttpServletRequest) request)) { | 
				
			||||
            chain.doFilter(request, response); | 
				
			||||
        } else if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) { | 
				
			||||
            chain.doFilter(request, response); | 
				
			||||
        } else { | 
				
			||||
            ServletRequest requestWrapper = new ShaoduoRequestWrapper((HttpServletRequest) request); | 
				
			||||
//            System.out.printf("ReplaceStreamFilter触发, Method: %s, URI: %s%n", ((HttpServletRequest) request).getMethod(), ((HttpServletRequest) request).getRequestURI());
 | 
				
			||||
            HttpServletResponse httpResponse = (HttpServletResponse) response; | 
				
			||||
            Soul2ResponseWrapper soul2ResponseWrapper = new Soul2ResponseWrapper(httpResponse); | 
				
			||||
            chain.doFilter(requestWrapper, soul2ResponseWrapper); | 
				
			||||
            try { | 
				
			||||
                byte[] data = soul2ResponseWrapper.getResponseData(); | 
				
			||||
                log.debug("原始返回 -> " + new String(data)); | 
				
			||||
                String encryptBody = EncryptUtils.encrypt(new String(data)); | 
				
			||||
                log.debug("加密返回 -> " + encryptBody); | 
				
			||||
                soul2ResponseWrapper.setHeader("encrypt", "1"); | 
				
			||||
                soul2ResponseWrapper.setHeader("Access-Control-Expose-Headers", "encrypt"); | 
				
			||||
                PrintWriter out = response.getWriter(); | 
				
			||||
                out.print(encryptBody); | 
				
			||||
                out.flush(); | 
				
			||||
                out.close(); | 
				
			||||
            } catch (Exception e) { | 
				
			||||
                e.printStackTrace(); | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public void destroy() { | 
				
			||||
        log.info("StreamFilter销毁..."); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,130 @@ | 
				
			||||
package cn.soul2.jyjc.admin.filter; | 
				
			||||
 | 
				
			||||
import lombok.extern.slf4j.Slf4j; | 
				
			||||
 | 
				
			||||
import javax.servlet.ReadListener; | 
				
			||||
import javax.servlet.ServletInputStream; | 
				
			||||
import javax.servlet.ServletRequest; | 
				
			||||
import javax.servlet.http.HttpServletRequest; | 
				
			||||
import javax.servlet.http.HttpServletRequestWrapper; | 
				
			||||
import java.io.*; | 
				
			||||
import java.nio.charset.Charset; | 
				
			||||
import java.nio.charset.StandardCharsets; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author shaoduo | 
				
			||||
 * @description 包装HttpServletRequest,目的是让其输入流可重复读 | 
				
			||||
 * @since <a href="https://blog.csdn.net/shaoduo/article/details/122322578">shaoduo</a> | 
				
			||||
 **/ | 
				
			||||
@Slf4j | 
				
			||||
public class ShaoduoRequestWrapper extends HttpServletRequestWrapper { | 
				
			||||
    /** | 
				
			||||
     * 存储body数据的容器 | 
				
			||||
     */ | 
				
			||||
    private byte[] body; | 
				
			||||
 | 
				
			||||
    public ShaoduoRequestWrapper(HttpServletRequest request) { | 
				
			||||
        super(request); | 
				
			||||
 | 
				
			||||
        // 将body数据存储起来
 | 
				
			||||
        String bodyStr = getBodyString(request); | 
				
			||||
        body = bodyStr.getBytes(Charset.defaultCharset()); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 获取请求Body | 
				
			||||
     * | 
				
			||||
     * @param request request | 
				
			||||
     * @return String | 
				
			||||
     */ | 
				
			||||
    public String getBodyString(final ServletRequest request) { | 
				
			||||
        try { | 
				
			||||
            return inputStream2String(request.getInputStream()); | 
				
			||||
        } catch (IOException e) { | 
				
			||||
            log.error("", e); | 
				
			||||
            throw new RuntimeException(e); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 获取请求Body | 
				
			||||
     * | 
				
			||||
     * @return String | 
				
			||||
     */ | 
				
			||||
    public String getBodyString() throws IOException { | 
				
			||||
        InputStream inputStream = new ByteArrayInputStream(body); | 
				
			||||
        return inputStream2String(inputStream); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 修改body 将json 重新设置成body | 
				
			||||
     * | 
				
			||||
     * @param val | 
				
			||||
     */ | 
				
			||||
    public void setBody(String val) { | 
				
			||||
        body = val.getBytes(StandardCharsets.UTF_8); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 将inputStream里的数据读取出来并转换成字符串 | 
				
			||||
     * | 
				
			||||
     * @param inputStream inputStream | 
				
			||||
     * @return String | 
				
			||||
     */ | 
				
			||||
    private String inputStream2String(InputStream inputStream) throws IOException { | 
				
			||||
        StringBuilder sb = new StringBuilder(); | 
				
			||||
        BufferedReader reader = null; | 
				
			||||
 | 
				
			||||
        try { | 
				
			||||
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); | 
				
			||||
            String line; | 
				
			||||
            while ((line = reader.readLine()) != null) { | 
				
			||||
                sb.append(line); | 
				
			||||
            } | 
				
			||||
        } catch (IOException e) { | 
				
			||||
            log.error("", e); | 
				
			||||
            throw new RuntimeException(e); | 
				
			||||
        } finally { | 
				
			||||
            if (reader != null) { | 
				
			||||
                try { | 
				
			||||
                    reader.close(); | 
				
			||||
                } catch (IOException e) { | 
				
			||||
                    log.error("", e); | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
        return sb.toString(); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public BufferedReader getReader() throws IOException { | 
				
			||||
        return new BufferedReader(new InputStreamReader(getInputStream())); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public ServletInputStream getInputStream() { | 
				
			||||
 | 
				
			||||
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); | 
				
			||||
 | 
				
			||||
        return new ServletInputStream() { | 
				
			||||
            @Override | 
				
			||||
            public int read() { | 
				
			||||
                return inputStream.read(); | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            @Override | 
				
			||||
            public boolean isFinished() { | 
				
			||||
                return false; | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            @Override | 
				
			||||
            public boolean isReady() { | 
				
			||||
                return false; | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            @Override | 
				
			||||
            public void setReadListener(ReadListener readListener) { | 
				
			||||
            } | 
				
			||||
        }; | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,120 @@ | 
				
			||||
package cn.soul2.jyjc.admin.filter; | 
				
			||||
 | 
				
			||||
import lombok.SneakyThrows; | 
				
			||||
 | 
				
			||||
import javax.servlet.ServletOutputStream; | 
				
			||||
import javax.servlet.WriteListener; | 
				
			||||
import javax.servlet.http.HttpServletResponse; | 
				
			||||
import javax.servlet.http.HttpServletResponseWrapper; | 
				
			||||
import java.io.*; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @description 包装ServletResponse, 照抄自 <a href="https://blog.csdn.net/temp_44/article/details/107762290">temp_44</a> | 
				
			||||
 * @date 2024-04-15 | 
				
			||||
 */ | 
				
			||||
public class Soul2ResponseWrapper extends HttpServletResponseWrapper { | 
				
			||||
    private ByteArrayOutputStream buffer = null; | 
				
			||||
    private ServletOutputStream out = null; | 
				
			||||
    private PrintWriter writer = null; | 
				
			||||
 | 
				
			||||
    public Soul2ResponseWrapper(HttpServletResponse resp) throws IOException { | 
				
			||||
        super(resp); | 
				
			||||
        // 真正存储数据的流
 | 
				
			||||
        buffer = new ByteArrayOutputStream(); | 
				
			||||
        out = new WapperedOutputStream(buffer); | 
				
			||||
        writer = new PrintWriter(new OutputStreamWriter(buffer, | 
				
			||||
                this.getCharacterEncoding())); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 重载父类获取outputstream的方法 | 
				
			||||
     */ | 
				
			||||
    @Override | 
				
			||||
    public ServletOutputStream getOutputStream() throws IOException { | 
				
			||||
        return out; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 重载父类获取writer的方法 | 
				
			||||
     */ | 
				
			||||
    @Override | 
				
			||||
    public PrintWriter getWriter() throws UnsupportedEncodingException { | 
				
			||||
        return writer; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * The default behavior of this method is to call | 
				
			||||
     * setCharacterEncoding(String charset) on the wrapped response object. | 
				
			||||
     * | 
				
			||||
     * @param charset | 
				
			||||
     * @since 2.4 | 
				
			||||
     */ | 
				
			||||
    @SneakyThrows | 
				
			||||
    @Override | 
				
			||||
    public void setCharacterEncoding(String charset) { | 
				
			||||
        super.setCharacterEncoding(charset); | 
				
			||||
        writer = new PrintWriter(new OutputStreamWriter(buffer, | 
				
			||||
                this.getCharacterEncoding())); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 重载父类获取flushBuffer的方法 | 
				
			||||
     */ | 
				
			||||
    @Override | 
				
			||||
    public void flushBuffer() throws IOException { | 
				
			||||
        if (out != null) { | 
				
			||||
            out.flush(); | 
				
			||||
        } | 
				
			||||
        if (writer != null) { | 
				
			||||
            writer.flush(); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public void reset() { | 
				
			||||
        buffer.reset(); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 | 
				
			||||
     */ | 
				
			||||
    public byte[] getResponseData() throws IOException { | 
				
			||||
        flushBuffer(); | 
				
			||||
        return buffer.toByteArray(); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 内部类,对ServletOutputStream进行包装 | 
				
			||||
     */ | 
				
			||||
    private class WapperedOutputStream extends ServletOutputStream { | 
				
			||||
        private ByteArrayOutputStream bos = null; | 
				
			||||
 | 
				
			||||
        public WapperedOutputStream(ByteArrayOutputStream stream) | 
				
			||||
                throws IOException { | 
				
			||||
            bos = stream; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public void write(int b) throws IOException { | 
				
			||||
            bos.write(b); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public void write(byte[] b) throws IOException { | 
				
			||||
            bos.write(b, 0, b.length); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public boolean isReady() { | 
				
			||||
            // TODO Auto-generated method stub
 | 
				
			||||
            return false; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public void setWriteListener(WriteListener writeListener) { | 
				
			||||
            // TODO Auto-generated method stub
 | 
				
			||||
 | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,140 @@ | 
				
			||||
package cn.soul2.jyjc.admin.interceptor; | 
				
			||||
 | 
				
			||||
import cn.soul2.jyjc.admin.annotation.SkinEncrypt; | 
				
			||||
import cn.soul2.jyjc.admin.annotation.SkinLogin; | 
				
			||||
import cn.soul2.jyjc.admin.bean.UserLoginStatusBean; | 
				
			||||
import cn.soul2.jyjc.admin.filter.ShaoduoRequestWrapper; | 
				
			||||
import cn.soul2.jyjc.admin.service.IUserService; | 
				
			||||
import cn.soul2.jyjc.admin.utils.EncryptUtils; | 
				
			||||
import cn.soul2.jyjc.admin.vo.base.Back; | 
				
			||||
import com.alibaba.fastjson.JSON; | 
				
			||||
import com.alibaba.fastjson.JSONObject; | 
				
			||||
import com.fasterxml.jackson.databind.ObjectMapper; | 
				
			||||
import lombok.extern.slf4j.Slf4j; | 
				
			||||
import org.springframework.beans.factory.annotation.Autowired; | 
				
			||||
import org.springframework.web.method.HandlerMethod; | 
				
			||||
import org.springframework.web.servlet.HandlerInterceptor; | 
				
			||||
 | 
				
			||||
import javax.annotation.Resource; | 
				
			||||
import javax.servlet.http.HttpServletRequest; | 
				
			||||
import javax.servlet.http.HttpServletResponse; | 
				
			||||
import java.io.PrintWriter; | 
				
			||||
import java.lang.reflect.Method; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Token拦截器 | 
				
			||||
 * | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @date 2024-04-02 15:31 | 
				
			||||
 */ | 
				
			||||
@Slf4j | 
				
			||||
public class FinallyInterceptor implements HandlerInterceptor { | 
				
			||||
 | 
				
			||||
    @Resource | 
				
			||||
    private UserLoginStatusBean userLoginStatusBean; | 
				
			||||
 | 
				
			||||
    @Autowired | 
				
			||||
    private IUserService userService; | 
				
			||||
 | 
				
			||||
    private boolean load = true; | 
				
			||||
 | 
				
			||||
    private void loadUserLoginStatus() { | 
				
			||||
        userLoginStatusBean.loadUserLoginStatus(userService.getLoginedUser()); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | 
				
			||||
        if (load) { | 
				
			||||
            loadUserLoginStatus(); | 
				
			||||
            load = false; | 
				
			||||
        } | 
				
			||||
        boolean pass = false; | 
				
			||||
        // 允许OPTIONS请求通过
 | 
				
			||||
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { | 
				
			||||
            return true; | 
				
			||||
        } | 
				
			||||
        // 如果不是映射到方法直接通过
 | 
				
			||||
        if (!(handler instanceof HandlerMethod)) { | 
				
			||||
            return true; | 
				
			||||
        } | 
				
			||||
        HandlerMethod handlerMethod = (HandlerMethod) handler; | 
				
			||||
        Method method = handlerMethod.getMethod(); | 
				
			||||
        String httpMethod = request.getMethod(); | 
				
			||||
        SkinEncrypt skinEncrypt = handlerMethod.getMethodAnnotation(SkinEncrypt.class); | 
				
			||||
        // 检查方法上是否存在SkinLogin注解
 | 
				
			||||
        boolean hasSkinLogin = handlerMethod.getMethodAnnotation(SkinLogin.class) != null; | 
				
			||||
        // 从请求头中获取 token
 | 
				
			||||
        String token = request.getHeader("jyjc-Token"); | 
				
			||||
        try { | 
				
			||||
        /* | 
				
			||||
            加解密 | 
				
			||||
         */ | 
				
			||||
            // 不拦截get请求
 | 
				
			||||
            if ("GET".equals(httpMethod)) { | 
				
			||||
                pass = true; | 
				
			||||
                // 如果是post请求且是json
 | 
				
			||||
            } else if ("POST".equals(httpMethod)) { | 
				
			||||
                // 如果类型为空就放行
 | 
				
			||||
                if (request.getContentType() == null) { | 
				
			||||
                    pass = true; | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
            // 跳过使用 @SkinEncrypt 的情况
 | 
				
			||||
            if (skinEncrypt != null) { | 
				
			||||
                pass = true; | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            if (!pass) { | 
				
			||||
                ShaoduoRequestWrapper shaoduoRequestWrapper; | 
				
			||||
                try { | 
				
			||||
                    shaoduoRequestWrapper = (ShaoduoRequestWrapper) request; | 
				
			||||
                } catch (Exception e) { | 
				
			||||
                    System.out.printf("request.ClassTypeError: %s%n", request.getClass().getName()); | 
				
			||||
                    return true; | 
				
			||||
                } | 
				
			||||
                String sourceParamBody = shaoduoRequestWrapper.getBodyString(); | 
				
			||||
                JSONObject obj = JSON.parseObject(EncryptUtils.decrypt(sourceParamBody)); | 
				
			||||
                String afterBody = JSONObject.toJSONString(obj); | 
				
			||||
                shaoduoRequestWrapper.setBody(afterBody); | 
				
			||||
                log.debug(String.format("解密: %s -> %s", sourceParamBody, afterBody)); | 
				
			||||
 | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            /* token验证 */ | 
				
			||||
            if (hasSkinLogin) { | 
				
			||||
                // 跳过使用 @SkinLogin 的情况
 | 
				
			||||
                pass = true; | 
				
			||||
            } else { | 
				
			||||
                // 验证token
 | 
				
			||||
                // 检查 token 是否存在并且有效
 | 
				
			||||
                if (token == null) { | 
				
			||||
                    // 没有Token,拒绝请求
 | 
				
			||||
                    response.setStatus(40401); | 
				
			||||
                    pass = false; | 
				
			||||
                } else if (userLoginStatusBean != null) { | 
				
			||||
                    if (!userLoginStatusBean.containsToken(token)) { | 
				
			||||
                        // Token 无效,拒绝请求
 | 
				
			||||
                        response.setContentType("application/json"); | 
				
			||||
                        Back<String> back = new Back<String>().setCode(40401).setMessage("Token invalid!"); | 
				
			||||
 | 
				
			||||
                        // 转换为 JSON 字符串
 | 
				
			||||
                        ObjectMapper objectMapper = new ObjectMapper(); | 
				
			||||
                        String responseBody = objectMapper.writeValueAsString(back); | 
				
			||||
 | 
				
			||||
                        // 输出错误信息到响应中
 | 
				
			||||
                        PrintWriter writer = response.getWriter(); | 
				
			||||
                        writer.print(responseBody); | 
				
			||||
                        writer.flush(); | 
				
			||||
                        pass = false; | 
				
			||||
                    } else { | 
				
			||||
                        // token存在, 通过拦截器
 | 
				
			||||
                        pass = true; | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
        } catch (Exception e) { | 
				
			||||
            throw new RuntimeException(e); | 
				
			||||
        } | 
				
			||||
        return pass; | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -1,63 +0,0 @@ | 
				
			||||
package cn.soul2.jyjc.admin.interceptor; | 
				
			||||
 | 
				
			||||
import cn.soul2.jyjc.admin.annotation.SkinLogin; | 
				
			||||
import cn.soul2.jyjc.admin.config.UserLoginStatusBean; | 
				
			||||
import org.springframework.beans.factory.annotation.Autowired; | 
				
			||||
import org.springframework.web.method.HandlerMethod; | 
				
			||||
import org.springframework.web.servlet.HandlerInterceptor; | 
				
			||||
 | 
				
			||||
import javax.servlet.http.HttpServletRequest; | 
				
			||||
import javax.servlet.http.HttpServletResponse; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @date 2024-04-02 15:31 | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
public class TokenInterceptor implements HandlerInterceptor { | 
				
			||||
 | 
				
			||||
    @Autowired | 
				
			||||
    private UserLoginStatusBean userLoginStatusBean; | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | 
				
			||||
        // 允许OPTIONS请求通过
 | 
				
			||||
        if (isCorsRequest(request)) { | 
				
			||||
            return true; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        // 跳过登录
 | 
				
			||||
        // 如果处理器是一个方法处理器
 | 
				
			||||
        if (handler instanceof HandlerMethod) { | 
				
			||||
            HandlerMethod handlerMethod = (HandlerMethod) handler; | 
				
			||||
            // 检查方法上是否存在SkinLogin注解
 | 
				
			||||
            if (handlerMethod.getMethod().isAnnotationPresent(SkinLogin.class)) { | 
				
			||||
                // 如果存在,绕过拦截器
 | 
				
			||||
                return true; | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
        // 验证token
 | 
				
			||||
        // 从请求头中获取 token
 | 
				
			||||
        String token = request.getHeader("jyjc-Token"); | 
				
			||||
        // 检查 token 是否存在并且有效,这里可以根据实际情况自行实现验证逻辑
 | 
				
			||||
        if (token != null && isValidToken(token)) { | 
				
			||||
            // Token 有效,允许请求通过
 | 
				
			||||
            return true; | 
				
			||||
        } else { | 
				
			||||
            // Token 无效,拒绝请求,可以返回特定的响应状态码,例如 401 Unauthorized
 | 
				
			||||
            response.setStatus(40401); | 
				
			||||
            return false; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private boolean isValidToken(String token) { | 
				
			||||
        // 实现 token 验证逻辑,例如验证 token 的签名或者在数据库中验证 token 的有效性
 | 
				
			||||
        return userLoginStatusBean.containsToken(token); | 
				
			||||
        // 返回 true 表示 token 有效,返回 false 表示 token 无效
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private boolean isCorsRequest(HttpServletRequest request) { | 
				
			||||
        // 检查请求头中是否包含 Origin 字段,如果存在,则认为是跨域请求
 | 
				
			||||
        return request.getHeader("Origin") != null; | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,132 @@ | 
				
			||||
package cn.soul2.jyjc.admin.utils; | 
				
			||||
 | 
				
			||||
import org.apache.tomcat.util.codec.binary.Base64; | 
				
			||||
 | 
				
			||||
import javax.crypto.Cipher; | 
				
			||||
import javax.crypto.KeyGenerator; | 
				
			||||
import javax.crypto.spec.SecretKeySpec; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author Soul2 | 
				
			||||
 * @date 2024-04-08 15:00 | 
				
			||||
 * <p>照抄自 <a href="https://blog.csdn.net/shaoduo/article/details/122322578">Shao duo</a></p> | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
public class EncryptUtils { | 
				
			||||
 | 
				
			||||
    private static final String KEY = "620b8d3c3be1e725"; | 
				
			||||
 | 
				
			||||
    //参数分别代表 算法名称/加密模式/数据填充方式
 | 
				
			||||
    private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding"; | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 加密 | 
				
			||||
     * | 
				
			||||
     * @param content    加密的字符串 | 
				
			||||
     * @param encryptKey key值 | 
				
			||||
     * @return {@link String} | 
				
			||||
     */ | 
				
			||||
    public static String encrypt(String content, String encryptKey) throws Exception { | 
				
			||||
        KeyGenerator kgen = KeyGenerator.getInstance("AES"); | 
				
			||||
        kgen.init(128); | 
				
			||||
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR); | 
				
			||||
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES")); | 
				
			||||
        byte[] b = cipher.doFinal(content.getBytes("utf-8")); | 
				
			||||
        // 采用base64算法进行转码,避免出现中文乱码
 | 
				
			||||
        return Base64.encodeBase64String(b); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 解密 | 
				
			||||
     * | 
				
			||||
     * @param encryptStr 解密的字符串 | 
				
			||||
     * @param decryptKey 解密的key值 | 
				
			||||
     * @return {@link String} | 
				
			||||
     */ | 
				
			||||
    public static String decrypt(String encryptStr, String decryptKey) throws Exception { | 
				
			||||
        KeyGenerator kgen = KeyGenerator.getInstance("AES"); | 
				
			||||
        kgen.init(128); | 
				
			||||
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR); | 
				
			||||
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes("utf-8"), "AES")); | 
				
			||||
        // 采用base64算法进行转码,避免出现中文乱码
 | 
				
			||||
 | 
				
			||||
        //byte[] b = hex2Bytes(encryptStr) ;
 | 
				
			||||
        byte[] encryptBytes = Base64.decodeBase64(encryptStr); | 
				
			||||
        byte[] decryptBytes = cipher.doFinal(encryptBytes); | 
				
			||||
        return new String(decryptBytes); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * @param content 加密的字符串 | 
				
			||||
     * @return {@link String} | 
				
			||||
     */ | 
				
			||||
    public static String encrypt(String content) throws Exception { | 
				
			||||
        return encrypt(content, KEY); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * @param encryptStr 解密的字符串 | 
				
			||||
     * @return {@link String} | 
				
			||||
     */ | 
				
			||||
    public static String decrypt(String encryptStr) throws Exception { | 
				
			||||
        return decrypt(encryptStr, KEY); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/*    public static void main(String[] args) throws Exception { | 
				
			||||
        Map map=new HashMap<String,String>(); | 
				
			||||
        map.put("key","value"); | 
				
			||||
        map.put("中文","汉字"); | 
				
			||||
        String content = JSONObject.toJSONString(map); | 
				
			||||
        System.out.println("加密前:" + content); | 
				
			||||
        String encrypt = encrypt(content, KEY); | 
				
			||||
        System.out.println("加密后:" + encrypt); | 
				
			||||
        String decrypt = decrypt(encrypt, KEY); | 
				
			||||
        System.out.println("解密后:" + decrypt); | 
				
			||||
    }*/ | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * byte数组 转换成 16进制小写字符串 | 
				
			||||
     */ | 
				
			||||
    public static String bytes2Hex(byte[] bytes) { | 
				
			||||
        if (bytes == null || bytes.length == 0) { | 
				
			||||
            return null; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        StringBuilder hex = new StringBuilder(); | 
				
			||||
 | 
				
			||||
        for (byte b : bytes) { | 
				
			||||
            hex.append(HEXES[(b >> 4) & 0x0F]); | 
				
			||||
            hex.append(HEXES[b & 0x0F]); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        return hex.toString(); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 16进制字符串 转换为对应的 byte数组 | 
				
			||||
     */ | 
				
			||||
    public static byte[] hex2Bytes(String hex) { | 
				
			||||
        if (hex == null || hex.length() == 0) { | 
				
			||||
            return null; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        char[] hexChars = hex.toCharArray(); | 
				
			||||
        // 如果 hex 中的字符不是偶数个, 则忽略最后一个
 | 
				
			||||
        byte[] bytes = new byte[hexChars.length / 2]; | 
				
			||||
 | 
				
			||||
        for (int i = 0; i < bytes.length; i++) { | 
				
			||||
            bytes[i] = (byte) Integer.parseInt("" + hexChars[i * 2] + hexChars[i * 2 + 1], 16); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        return bytes; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private static final char[] HEXES = { | 
				
			||||
            '0', '1', '2', '3', | 
				
			||||
            '4', '5', '6', '7', | 
				
			||||
            '8', '9', 'a', 'b', | 
				
			||||
            'c', 'd', 'e', 'f' | 
				
			||||
    }; | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,127 @@ | 
				
			||||
<?xml version="1.0" encoding="UTF-8"?> | 
				
			||||
<configuration scan="true" scanPeriod="10 seconds"> | 
				
			||||
 | 
				
			||||
    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />--> | 
				
			||||
 | 
				
			||||
    <contextName>logback</contextName> | 
				
			||||
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --> | 
				
			||||
    <!--    <property name="log.path" value=".logs/"/>--> | 
				
			||||
    <springProperty scope="context" name="log.path" source="logging.path" defaultValue="./logs/"/> | 
				
			||||
    <property name="log.pattern" | 
				
			||||
              value="%cyan(%d{HH:mm:ss}) %green(%-5level) %blue(%logger{50}) - %boldMagenta(%msg) \t\t ---- %yellow([%thread] %d{yyyy-MM-dd HH:mm:ss.SSS}) %n" | 
				
			||||
    /> | 
				
			||||
    <property name="debug.path" value="${log.path}/%d{yyyyMM,aux}/%d{dd,aux}/debug-%d{yyyyMMdd}.%i.log.gz"/> | 
				
			||||
    <property name="info.path" value="${log.path}/%d{yyyyMM,aux}/%d{dd,aux}/info-%d{yyyyMMdd}.%i.log.gz"/> | 
				
			||||
    <property name="error.path" value="${log.path}/%d{yyyyMM,aux}/%d{dd,aux}/error-%d{yyyyMMdd}.%i.log.gz"/> | 
				
			||||
 | 
				
			||||
    <!--输出到控制台--> | 
				
			||||
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | 
				
			||||
        <!-- 控制台输出的日志级别是大于或等于此级别的日志信息 --> | 
				
			||||
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | 
				
			||||
            <level>DEBUG</level> | 
				
			||||
        </filter> | 
				
			||||
        <!--日志文件输出格式与字符集--> | 
				
			||||
        <encoder> | 
				
			||||
            <Pattern>${log.pattern}</Pattern> | 
				
			||||
            <charset>UTF-8</charset> | 
				
			||||
        </encoder> | 
				
			||||
    </appender> | 
				
			||||
 | 
				
			||||
    <!--输出到文件--> | 
				
			||||
    <!-- 时间滚动输出 level为 DEBUG 日志 --> | 
				
			||||
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | 
				
			||||
        <!-- 正在记录的日志文件的路径及文件名 --> | 
				
			||||
        <file>${log.path}/debug.log</file> | 
				
			||||
        <!--日志文件输出格式与字符集--> | 
				
			||||
        <encoder> | 
				
			||||
            <pattern>${log.pattern}</pattern> | 
				
			||||
            <charset>UTF-8</charset> | 
				
			||||
        </encoder> | 
				
			||||
        <!-- 日志记录器的滚动策略,按日期,按大小记录 --> | 
				
			||||
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | 
				
			||||
            <!-- 日志归档 --> | 
				
			||||
            <fileNamePattern>${debug.path}</fileNamePattern> | 
				
			||||
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> | 
				
			||||
                <maxFileSize>10MB</maxFileSize> | 
				
			||||
            </timeBasedFileNamingAndTriggeringPolicy> | 
				
			||||
            <!--日志文件保留天数--> | 
				
			||||
            <maxHistory>7</maxHistory> | 
				
			||||
        </rollingPolicy> | 
				
			||||
        <!-- 此日志文件记录debug级别以上的 --> | 
				
			||||
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | 
				
			||||
            <level>DEBUG</level> | 
				
			||||
        </filter> | 
				
			||||
    </appender> | 
				
			||||
 | 
				
			||||
    <!-- 时间滚动输出 level为 INFO 日志 --> | 
				
			||||
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | 
				
			||||
        <!-- 正在记录的日志文件的路径及文件名 --> | 
				
			||||
        <file>${log.path}/info.log</file> | 
				
			||||
        <!--日志文件输出格式--> | 
				
			||||
        <encoder> | 
				
			||||
            <pattern>${log.pattern}</pattern> | 
				
			||||
            <charset>UTF-8</charset> | 
				
			||||
        </encoder> | 
				
			||||
        <!-- 日志记录器的滚动策略,按日期,按大小记录 --> | 
				
			||||
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | 
				
			||||
            <!-- 每天日志归档路径以及格式 --> | 
				
			||||
            <fileNamePattern>${info.path}</fileNamePattern> | 
				
			||||
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> | 
				
			||||
                <maxFileSize>100MB</maxFileSize> | 
				
			||||
            </timeBasedFileNamingAndTriggeringPolicy> | 
				
			||||
            <!--日志文件保留天数--> | 
				
			||||
            <maxHistory>60</maxHistory> | 
				
			||||
        </rollingPolicy> | 
				
			||||
        <!-- 此日志文件记录info级别以上的 --> | 
				
			||||
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | 
				
			||||
            <level>INFO</level> | 
				
			||||
        </filter> | 
				
			||||
    </appender> | 
				
			||||
 | 
				
			||||
    <!-- 时间滚动输出 level为 ERROR 日志 --> | 
				
			||||
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | 
				
			||||
        <!-- 正在记录的日志文件的路径及文件名 --> | 
				
			||||
        <file>${log.path}/error.log</file> | 
				
			||||
        <!--日志文件输出格式--> | 
				
			||||
        <encoder> | 
				
			||||
            <pattern>${log.pattern}</pattern> | 
				
			||||
            <charset>UTF-8</charset> <!-- 此处设置字符集 --> | 
				
			||||
        </encoder> | 
				
			||||
        <!-- 日志记录器的滚动策略,按日期,按大小记录 --> | 
				
			||||
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | 
				
			||||
            <fileNamePattern>${error.path}</fileNamePattern> | 
				
			||||
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> | 
				
			||||
                <maxFileSize>10MB</maxFileSize> | 
				
			||||
            </timeBasedFileNamingAndTriggeringPolicy> | 
				
			||||
            <!--日志文件保留天数--> | 
				
			||||
            <maxHistory>60</maxHistory> | 
				
			||||
        </rollingPolicy> | 
				
			||||
        <!-- 此日志文件记录error级别以上的 --> | 
				
			||||
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | 
				
			||||
            <level>ERROR</level> | 
				
			||||
        </filter> | 
				
			||||
    </appender> | 
				
			||||
 | 
				
			||||
    <!--开发环境:打印控制台--> | 
				
			||||
    <springProfile name="local,dev,test"> | 
				
			||||
        <logger level="DEBUG" additivity="false" name="cn.soul2"> | 
				
			||||
            <appender-ref ref="CONSOLE"/> | 
				
			||||
            <appender-ref ref="DEBUG_FILE"/> | 
				
			||||
            <appender-ref ref="INFO_FILE"/> | 
				
			||||
            <appender-ref ref="ERROR_FILE"/> | 
				
			||||
        </logger> | 
				
			||||
        <root level="INFO"> | 
				
			||||
            <appender-ref ref="CONSOLE"/> | 
				
			||||
            <appender-ref ref="INFO_FILE"/> | 
				
			||||
            <appender-ref ref="ERROR_FILE"/> | 
				
			||||
        </root> | 
				
			||||
    </springProfile> | 
				
			||||
 | 
				
			||||
    <!--生产环境:输出到文件--> | 
				
			||||
    <springProfile name="pre,prod,prod-yun"> | 
				
			||||
        <root level="INFO"> | 
				
			||||
            <appender-ref ref="INFO_FILE"/> | 
				
			||||
            <appender-ref ref="ERROR_FILE"/> | 
				
			||||
        </root> | 
				
			||||
    </springProfile> | 
				
			||||
</configuration> | 
				
			||||
					Loading…
					
					
				
		Reference in new issue