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
status:
project: yes
project: true
patch: no
changes: no

View file

@ -55,6 +55,7 @@ module.exports = {
$fileExtName: true,
$gotoSection: true,
$createUploader: true,
$initUploader: true,
$cleanMenu: true,
$cleanMap: 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) {
try {
String acceptType = request.getHeader("Accept");
if (acceptType == null) acceptType = request.getContentType();
if (acceptType == null || "*/*".equals(acceptType)) acceptType = request.getContentType();
// Via Spider?
if (acceptType == null) return MimeTypeUtils.TEXT_HTML;
if (StringUtils.isBlank(acceptType)) return MimeTypeUtils.TEXT_HTML;
acceptType = acceptType.split("[,;]")[0];
// Accpet ALL?

View file

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

View file

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

View file

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

View file

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

View file

@ -441,13 +441,15 @@ var $createUploader = function (input, next, complete, error) {
var local = input.data('local')
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) {
input.on('change', function () {
var file = this.files[0]
if (!file) return
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)
o.subscribe({
next: function (res) {
@ -466,7 +468,7 @@ var $createUploader = function (input, next, complete, error) {
return false
},
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 })
},
})
@ -475,7 +477,7 @@ var $createUploader = function (input, next, complete, error) {
} else {
input.html5Uploader({
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) {
if (err === 'ErrorType') {
RbHighbar.create($L(imgOnly ? 'PlsUploadImg' : 'FileTypeError'))
@ -492,7 +494,7 @@ var $createUploader = function (input, next, complete, error) {
onSuccess: function (e, file) {
e = $.parseJSON(e.currentTarget.response)
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 })
} else {
RbHighbar.error($L('ErrorUpload'))