better csrfToken

This commit is contained in:
devezhao 2021-02-05 11:28:52 +08:00
parent f88a24e034
commit 4da0f22a18
11 changed files with 96 additions and 27 deletions

View file

@ -4,7 +4,7 @@ coverage:
precision: 2 precision: 2
status: status:
project: yes project: true
patch: no patch: no
changes: no changes: no

View file

@ -55,6 +55,7 @@ module.exports = {
$fileExtName: true, $fileExtName: true,
$gotoSection: true, $gotoSection: true,
$createUploader: true, $createUploader: true,
$initUploader: true,
$cleanMenu: true, $cleanMenu: true,
$cleanMap: true, $cleanMap: true,
$pages: true, $pages: true,

2
@rbv

@ -1 +1 @@
Subproject commit 7b0d1b946a9c4ba5a832a57dc3db17606c6e912f Subproject commit 0aa958543b1eb60bbfe69bc3c9e689c5d0c224d6

View file

@ -1,8 +0,0 @@
commit_message: '[Crowdin] Update translations'
files:
- source: '/src/main/resources/i18n/language.zh_CN.json'
translation: '/src/main/resources/i18n/language.%locale%.json'
languages_mapping:
locale:
'zh-CN': 'zh_CN'
'en-US': 'en'

View file

@ -0,0 +1,67 @@
/*
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.core.support;
import cn.devezhao.commons.CodecUtils;
import com.rebuild.core.Application;
import com.rebuild.core.cache.CommonsCache;
import com.rebuild.utils.AppUtils;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* @author devezhao
* @since 2020/12/15
*/
public class CsrfToken {
// Token 存储前缀
private static final String TOKEN_PREFIX = "RBCSRF.";
/**
* 生成并存储 Token
*
* @return
*/
public static String generate() {
String token = CodecUtils.randomCode(60);
Application.getCommonsCache().putx(TOKEN_PREFIX + token,
System.currentTimeMillis(), CommonsCache.TS_HOUR * 2);
return token;
}
/**
* 验证 Token
*
* @param token
* @param destroy
* @return
*/
public static boolean verify(String token, boolean destroy) {
if (StringUtils.isBlank(token)) return false;
token = TOKEN_PREFIX + token;
Object exists = Application.getCommonsCache().getx(token);
if (exists != null && destroy) {
Application.getCommonsCache().evict(token);
}
return exists != null;
}
/**
* @param request
* @param destroy
* @return
*/
public static boolean verify(HttpServletRequest request, boolean destroy) {
String token = request.getHeader("X-Csrf-Token");
if (token == null) token = request.getParameter("_token");
return verify(token, destroy);
}
}

View file

@ -187,10 +187,10 @@ public class AppUtils {
public static MimeType parseMimeType(HttpServletRequest request) { public static MimeType parseMimeType(HttpServletRequest request) {
try { try {
String acceptType = request.getHeader("Accept"); String acceptType = request.getHeader("Accept");
if (acceptType == null) acceptType = request.getContentType(); if (acceptType == null || "*/*".equals(acceptType)) acceptType = request.getContentType();
// Via Spider? // Via Spider?
if (acceptType == null) return MimeTypeUtils.TEXT_HTML; if (StringUtils.isBlank(acceptType)) return MimeTypeUtils.TEXT_HTML;
acceptType = acceptType.split("[,;]")[0]; acceptType = acceptType.split("[,;]")[0];
// Accpet ALL? // Accpet ALL?

View file

@ -18,6 +18,7 @@ import com.rebuild.core.UserContextHolder;
import com.rebuild.core.cache.CommonsCache; import com.rebuild.core.cache.CommonsCache;
import com.rebuild.core.privileges.bizz.ZeroEntry; import com.rebuild.core.privileges.bizz.ZeroEntry;
import com.rebuild.core.support.ConfigurationItem; import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.CsrfToken;
import com.rebuild.core.support.License; import com.rebuild.core.support.License;
import com.rebuild.core.support.RebuildConfiguration; import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.setup.InstallState; import com.rebuild.core.support.setup.InstallState;
@ -28,8 +29,8 @@ import org.apache.commons.lang.StringUtils;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.util.MimeTypeUtils; import org.springframework.util.MimeTypeUtils;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -45,7 +46,7 @@ import java.io.IOException;
* @since 2.0 * @since 2.0
*/ */
@Slf4j @Slf4j
public class RebuildWebInterceptor extends HandlerInterceptorAdapter implements InstallState { public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallState {
private static final ThreadLocal<RequestEntry> REQUEST_ENTRY = new NamedThreadLocal<>("RequestEntry"); private static final ThreadLocal<RequestEntry> REQUEST_ENTRY = new NamedThreadLocal<>("RequestEntry");
@ -143,6 +144,11 @@ public class RebuildWebInterceptor extends HandlerInterceptorAdapter implements
} }
} else if (!isIgnoreAuth(requestUri)) { } else if (!isIgnoreAuth(requestUri)) {
// 外部表单特殊处理媒体字段上传/预览
if (requestUri.contains("/filex/") && CsrfToken.verify(request, false)) {
return true;
}
log.warn("Unauthorized access {} via {}", log.warn("Unauthorized access {} via {}",
RebuildWebConfigurer.getRequestUrls(request), ServletUtils.getRemoteAddr(request)); RebuildWebConfigurer.getRequestUrls(request), ServletUtils.getRemoteAddr(request));
@ -160,7 +166,7 @@ public class RebuildWebInterceptor extends HandlerInterceptorAdapter implements
@Override @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView); // Notings
} }
@Override @Override

View file

@ -20,12 +20,13 @@ server:
# SPRING # SPRING
spring: spring:
mvc:
static-path-pattern: /assets/**
resources:
static-locations: classpath:/web/assets
servlet: servlet:
multipart.enabled: false multipart.enabled: false
web:
resources:
static-locations: classpath:/web/assets
mvc:
static-path-pattern: /assets/**
thymeleaf: thymeleaf:
prefix: classpath:/web prefix: classpath:/web
cache: true cache: true
@ -37,3 +38,4 @@ spring:
size: 5 size: 5
main: main:
banner-mode: off banner-mode: off

View file

@ -20135,12 +20135,11 @@ label {
z-index: -1; z-index: -1;
} }
.inputfile + label { .inputfile + label, .inputfile-label {
padding: 0 12px; padding: 0 12px;
font-size: 1rem; font-size: 1rem;
line-height: 30px; line-height: 30px;
border: 1px solid transparent; border: 1px solid #d5d8de;
border-color: #d5d8de;
border-radius: 2px; border-radius: 2px;
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;

View file

@ -1014,7 +1014,7 @@ a {
border: 0 none; border: 0 none;
color: #404040; color: #404040;
border-left: 2px solid #ccc; border-left: 2px solid #ccc;
width: 205px; width: 239px;
border-radius: 2px; border-radius: 2px;
max-height: 38px; max-height: 38px;
cursor: default; cursor: default;

View file

@ -441,13 +441,15 @@ var $createUploader = function (input, next, complete, error) {
var local = input.data('local') var local = input.data('local')
if (!input.attr('data-maxsize')) input.attr('data-maxsize', 1024 * 1024 * 100) // default 100M if (!input.attr('data-maxsize')) input.attr('data-maxsize', 1024 * 1024 * 100) // default 100M
var useToken = rb.csrfToken ? ('&_token=' + rb.csrfToken) : ''
if (window.qiniu && rb.storageUrl && !local) { if (window.qiniu && rb.storageUrl && !local) {
input.on('change', function () { input.on('change', function () {
var file = this.files[0] var file = this.files[0]
if (!file) return if (!file) return
var putExtra = imgOnly ? { mimeType: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp'] } : null var putExtra = imgOnly ? { mimeType: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp'] } : null
$.get('/filex/qiniu/upload-keys?file=' + $encode(file.name), function (res) { $.get('/filex/qiniu/upload-keys?file=' + $encode(file.name) + useToken, function (res) {
var o = qiniu.upload(file, res.data.key, res.data.token, putExtra) var o = qiniu.upload(file, res.data.key, res.data.token, putExtra)
o.subscribe({ o.subscribe({
next: function (res) { next: function (res) {
@ -466,7 +468,7 @@ var $createUploader = function (input, next, complete, error) {
return false return false
}, },
complete: function (res) { complete: function (res) {
if (file.size > 0) $.post('/filex/store-filesize?fs=' + file.size + '&fp=' + $encode(res.key)) if (file.size > 0) $.post('/filex/store-filesize?fs=' + file.size + '&fp=' + $encode(res.key) + useToken)
typeof complete === 'function' && complete({ key: res.key }) typeof complete === 'function' && complete({ key: res.key })
}, },
}) })
@ -475,7 +477,7 @@ var $createUploader = function (input, next, complete, error) {
} else { } else {
input.html5Uploader({ input.html5Uploader({
name: input.attr('id') || input.attr('name') || 'H5Upload', name: input.attr('id') || input.attr('name') || 'H5Upload',
postUrl: rb.baseUrl + '/filex/upload?type=' + (imgOnly ? 'image' : 'file') + '&temp=' + (local === 'temp'), postUrl: rb.baseUrl + '/filex/upload?type=' + (imgOnly ? 'image' : 'file') + '&temp=' + (local === 'temp') + useToken,
onSelectError: function (file, err) { onSelectError: function (file, err) {
if (err === 'ErrorType') { if (err === 'ErrorType') {
RbHighbar.create($L(imgOnly ? 'PlsUploadImg' : 'FileTypeError')) RbHighbar.create($L(imgOnly ? 'PlsUploadImg' : 'FileTypeError'))
@ -492,7 +494,7 @@ var $createUploader = function (input, next, complete, error) {
onSuccess: function (e, file) { onSuccess: function (e, file) {
e = $.parseJSON(e.currentTarget.response) e = $.parseJSON(e.currentTarget.response)
if (e.error_code === 0) { if (e.error_code === 0) {
if (local !== 'temp' && file.size > 0) $.post('/filex/store-filesize?fs=' + file.size + '&fp=' + $encode(e.data)) if (local !== 'temp' && file.size > 0) $.post('/filex/store-filesize?fs=' + file.size + '&fp=' + $encode(e.data) + useToken)
complete({ key: e.data }) complete({ key: e.data })
} else { } else {
RbHighbar.error($L('ErrorUpload')) RbHighbar.error($L('ErrorUpload'))