mirror of
https://github.com/getrebuild/rebuild.git
synced 2025-02-16 10:22:59 +08:00
better csrfToken
This commit is contained in:
parent
f88a24e034
commit
4da0f22a18
11 changed files with 96 additions and 27 deletions
|
@ -4,7 +4,7 @@ coverage:
|
|||
precision: 2
|
||||
|
||||
status:
|
||||
project: yes
|
||||
project: true
|
||||
patch: no
|
||||
changes: no
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ module.exports = {
|
|||
$fileExtName: true,
|
||||
$gotoSection: true,
|
||||
$createUploader: true,
|
||||
$initUploader: true,
|
||||
$cleanMenu: true,
|
||||
$cleanMap: true,
|
||||
$pages: true,
|
||||
|
|
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit 7b0d1b946a9c4ba5a832a57dc3db17606c6e912f
|
||||
Subproject commit 0aa958543b1eb60bbfe69bc3c9e689c5d0c224d6
|
|
@ -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'
|
67
src/main/java/com/rebuild/core/support/CsrfToken.java
Normal file
67
src/main/java/com/rebuild/core/support/CsrfToken.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'))
|
||||
|
|
Loading…
Reference in a new issue