SpringBoot integrates UEditor rich text editor and solves CSRF problems

(1) Basic configuration and use

Download the plugin
https://github.com/fex-team/ueditor/releases

Insert picture description here
Backend dependency

 <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20160810</version>
        </dependency>
        <dependency>
            <groupId>com.gitee.qdbp.thirdparty</groupId>
            <artifactId>ueditor</artifactId>
            <version>1.4.3.3</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>

ueditor.config.js configuration server unified request interface path


        // 服务器统一请求接口路径
        , serverUrl:   "/admin/ueditor/config"

Custom file upload path

<!-- 编辑器源码文件 -->
<!-- 实例化编辑器 -->
<script type="text/javascript">
    let ue = UE.getEditor('container');
    UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
    UE.Editor.prototype.getActionUrl = function (action) {
     
     
        if (action === 'uploadimage' || action === 'uploadscrawl' || action === 'uploadimage') {
     
     
            return '/admin/ueditor/upload';
        } else if (action === 'uploadvideo') {
     
     
            return '/admin/ueditor/upload';
        } else {
     
     
            return this._bkGetActionUrl.call(this, action);
        }
    }
</script>

External file reference

Insert picture description here
Backend interface

import cn.hutool.core.text.StrBuilder;
import com.aisino.common.config.client.FastDfsClient;
import com.aisino.common.entity.OperatorFile;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;


/***
 *
 *  UEditor富文本编辑器处理器
 *
 * @author ZhangYu
 * @date 2021/3/4
 */
@RestController
@RequestMapping("/admin/ueditor")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UEditorServerRestController {
    
    

    private final Logger logger = LoggerFactory.getLogger(UEditorServerRestController.class);

    private final FastDfsClient fastDFSClient;

    private static final String CONFIG_JSON_PATH = "static/js/ueditor/config.json";


    /***
     *  UEditor获取config.json配置信息
     * @author ZhangYu
     * @date 2021/3/4
     */
    @RequestMapping(value = "/config")
    public String config() throws IOException {
    
    
        ClassPathResource resource = new ClassPathResource(CONFIG_JSON_PATH);
        JSONObject jsonObject = new JSONObject();
        InputStream is = null;
        try {
    
    
            is = new FileInputStream(resource.getFile());
            StrBuilder strBuilder = new StrBuilder();
            byte[] bytes = new byte[1024];
            int length;
            while (-1 != (length = is.read(bytes))) {
    
    
                strBuilder.append(new String(bytes, 0, length, StandardCharsets.UTF_8));
            }
            String result = strBuilder.toString().replaceAll("/\\*(.|[\\r\\n])*?\\*/", "");
            jsonObject = JSON.parseObject(result);
        } catch (IOException e) {
    
    
            logger.error("UEditor读取config.json配置文件错误");
        } finally {
    
    
            assert is != null;
            is.close();
        }
        return jsonObject.toJSONString();
    }


    /***
     *  UEditor文件上传接口
     * @param files 文件列表
     * @param response 输出流
     * @date 2021/3/4
     */
    @PostMapping("/upload")
    public void upload(@NotNull @RequestParam("upfile") MultipartFile[] files, HttpServletResponse response) throws IOException {
    
    
        for (MultipartFile file : files) {
    
    
            //上传文件至文件服务器
            OperatorFile operatorFile = fastDFSClient.uploadFile(file);
            String url = operatorFile.getUrl();
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("state", "SUCCESS");
            jsonObject.put("url", url);
            jsonObject.put("title", "");
            jsonObject.put("original", "");
            //输出结果回显UEditor文本编辑器
            response.getWriter().write(jsonObject.toJSONString());
        }
    }
}

(2) UEditor rich text editor solves the CSRF interception problem

后端开启了CSRF拦截,使用的安全框架是SpringSecurity,但是就自己了解到UEditor并没有提供自定义方法设置头部,但是对于CSRF又不能放行,于是自己就根据源码进行了修改。

According to the CSRF rules, you need to provide a Token in the header to ensure that the permission is passed. Then, after observing UEditor, it is found that three places are involved in the upload of pictures, and each of them is a different component method.
1. Single picture upload (form submission)
2 、Multiple picture upload (Ajax)
3. Drag and drop picture upload (Ajax)

Since CSRF_TOEKN needs to be obtained in many places, the Token is directly placed in localStorage for convenience, and the following Tokens will be obtained from here

<script>
    localStorage.setItem("X-CSRF-TOKEN", "[[${_csrf.token}]]");
</script>
【Single picture upload】

The upload processing logic of a single image is in ueditor.all.js
Insert picture description here

By observing the code, we can find that the form is used to submit the file. For this problem, I used two ways to solve the problem. The first is to add a hidden type space to the original form and add a csrf hidden field to solve it. The second way is to comment out the logic of the source code, use AJAX to rewrite it and then add the Header, and add the CSRF attribute to the header. Both methods are possible. Here we use a simpler form to pass values.

Add the following line of code to the original HTML and pass csrf_token

 ' <input type="hidden"   id="csrf_token"   name="_csrf"   value="' + localStorage.getItem("X-CSRF-TOKEN") + '" > ' +

Insert picture description here
Note:
The name value of the hidden control here is very important. Don’t make a mistake. I see all kinds of things written on the Internet are the same. I don’t know if the others are right. But if you are using Spring Security, there are only two. Ways
1. Request header with X-CSRF-TOKEN attribute
2. Request parameters with _csrf attribute

In the Spring Security source code,
org.springframework.security.web.csrf.CsrfFilter.doFilterInternal() will do the following value judgment processing

Insert picture description here

Insert picture description here

Be careful not to make a mistake. I think that many csrf_tokens on the Internet are not acceptable. The upload of a single image can be modified. If you want to bring other tokens, you should modify this to upload in AJAX.

Insert picture description here

【Multiple Picture Upload】

The corresponding component for multi-image upload is the dialogs/images/images.js component, which uses the Ajax submission method
Insert picture description here
Insert picture description here

Find this registration event method, and then add the header that carries csrf

header['X-CSRF-TOKEN'] = localStorage.getItem("X-CSRF-TOKEN");

Insert picture description here

[Drag and drop pictures to upload]

Generally, we will directly drag the pictures in the local folder to the rich text editor or upload them by CTRL+V. This also requires a separate modification of the source code

The main processing logic is in the following components

Insert picture description here

Just add the following line of code

 xhr.setRequestHeader('X-CSRF-TOKEN',localStorage.getItem("X-CSRF-TOKEN"));

Insert picture description here

Guess you like

Origin blog.csdn.net/Octopus21/article/details/114536939
Recommended