Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
項目中自定義了攔截器Filter,項目中使用了spring security,它也有對應的攔截器,我想讓我自定義的Filter在spring security的攔截器前執行。
因為我自定義的攔截器,需要提前做一些邏輯處理;然后spring security的攔截器需要用到這部分的處理結果;所以我必須要想辦法讓我自定義的攔截器靠前執行。
那就一起來看看spring security設置的攔截器的默認優先級等級是多少吧。
自定義攔截器如下:
@Slf4j
public class MyOncePerRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.info("======== MyOncePerRequestFilter ========");
filterChain.doFilter(request, response);
}
}
@Configuration
public class Config {
@Bean
public FilterRegistrationBean<MyOncePerRequestFilter> i18nFilterRegistrationBean() {
FilterRegistrationBean<MyOncePerRequestFilter> registrationBean = new FilterRegistrationBean();
MyOncePerRequestFilter myOncePerRequestFilter = new MyOncePerRequestFilter();
registrationBean.setFilter(myOncePerRequestFilter);
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(-1);
return registrationBean;
}
}
spring security的簡單配置如下:
@Slf4j
public class MyTokenStore implements TokenStore {
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
log.info("======== readAccessToken ========");
return new DefaultOAuth2AccessToken(tokenValue);
}
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
Authentication authentication = new AbstractAuthenticationToken(Sets.newHashSet()) {
{
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return StringUtils.EMPTY;
}
};
OAuth2Request request =
new OAuth2Request(null, null, null, true,
Sets.newHashSet(), Sets.newHashSet(), null, null, null);
return new OAuth2Authentication(request, authentication);
}
@Override public OAuth2Authentication readAuthentication(String token) {
return null;
}
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
}
@Override public void removeAccessToken(OAuth2AccessToken token) {
}
@Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
}
@Override public OAuth2RefreshToken readRefreshToken(String tokenValue) {
return null;
}
@Override public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return null;
}
@Override public void removeRefreshToken(OAuth2RefreshToken token) {
}
@Override public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
}
@Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
return null;
}
@Override public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
return null;
}
@Override public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
return null;
}
}
@Configuration
@EnableResourceServer
@EnableWebSecurity
public class MyResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
MyTokenStore tokenStore = new MyTokenStore();
resources.tokenStore(tokenStore);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().anonymous().key("anonymousUser")
.and().httpBasic();
}
}
啟動類如下:
@RestController
public class MyController {
@GetMapping("/hello")
public String hello() {
return "hello,world!";
}
}
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Starter {
public static void main(String[] args) {
SpringApplication.run(Starter.class, args);
}
}
啟動后,訪問 http://127.0.0.1:8080/hello?access_token=123
日志打印如下:
102149 [http-nio-8080-exec-1] INFO c.e.l.s.mvc.security.MyTokenStore - ======== readAccessToken ========
102149 [http-nio-8080-exec-1] INFO c.e.l.s.m.s.MyOncePerRequestFilter - ======== MyOncePerRequestFilter ========
從結果可以看出,spring security的攔截器是比我們自定義的攔截器先執行的,而我們自定義的攔截器的優先級是registrationBean.setOrder(-1)
我猜應該是這個值決定了執行順序,那就帶著這個猜想往下看一下吧。
在之前的配置中,我們將自定義的攔截器順序置為-1
我們先在MyOncePerRequestFilter.doFilterInternal打個斷點,看一下執行鏈的順序:
從這條鏈中,我們猜測springSecurityFilterChain的order是-100,我們自定義的攔截器是在它后面的
那我們直接把我們的攔截器設置成-101,registrationBean.setOrder(-101);,再來嘗試一下:
從斷點結果可以看出,我們的設置是有效的,并且起到了作用,而且打印日志也說明了結果,如下:
11956 [http-nio-8080-exec-1] INFO c.e.l.s.m.s.MyOncePerRequestFilter - ======== MyOncePerRequestFilter ========
98419 [http-nio-8080-exec-1] INFO c.e.l.s.mvc.security.MyTokenStore - ======== readAccessToken ========
這個過程是極其枯燥的,所以就先給結果了,如下:
spring security的攔截器鏈是在下面這部分創建的:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder()); // 這里
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
}
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
}
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100; // 這里
private final Filter filter = new Filter();
public Filter getFilter() {
return this.filter;
}
public static class Filter {
private int order = DEFAULT_FILTER_ORDER; // 這里
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
}
public interface OrderedFilter extends Filter, Ordered {
int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0; // 這里
}
從上面的代碼可以看出,默認值是-100,同樣也可以使用spring.security.filter.order來自定義值。
下面是尋找此過程的歷程:
繼續從這里開始,ApplicationFilterChain.internalDoFilter如下:
可以看出所有的攔截器都是在filters中,我們可以看這個值是怎么來的,通過調試,是在ApplicationFilterChain.addFilter這個地方,如下:
它是被ApplicationFilterFactory.createFilterChain調用的,如下:
所以filters是根據filterMaps來添加的,我們再來看一下filterMaps是怎么來的,一共涉及到兩個地方,如下:
StandardContext.addFilterMap和StandardContext.addFilterMapBefore如下:
看一下調用鏈:
原來是被ServletWebServerApplicationContext.selfInitialize調用的,如下:
ServletWebServerApplicationContext.getServletContextInitializerBeans如下:
ServletContextInitializerBeans構造函數如下:
所有的攔截器都是通過addServletContextInitializerBeans(beanFactory);和addAdaptableBeans(beanFactory);來把bean加進來的
經過一番調試,終于找到spring security這個攔截器定義順序的位置,SecurityFilterAutoConfiguration.securityFilterChainRegistration如下:
可以看到SecurityProperties securityProperties是注入進來的,找到這個類看一下,securityProperties.filter.order如下:
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
private final Filter filter = new Filter();
private final User user = new User();
public static class Filter {
/**
* Security filter chain order.
*/
private int order = DEFAULT_FILTER_ORDER;
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
}
public interface OrderedFilter extends Filter, Ordered {
/**
* Filters that wrap the servlet request should be ordered less than or equal to this.
*/
int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0;
}
到此我們也找到了這個默認值,是根據spring.security.filter.order來決定的,默認值是-100
第一種就是修改自己的順序:
@Configuration
public class Config {
@Bean
public FilterRegistrationBean<MyOncePerRequestFilter> i18nFilterRegistrationBean() {
FilterRegistrationBean<MyOncePerRequestFilter> registrationBean = new FilterRegistrationBean();
MyOncePerRequestFilter myOncePerRequestFilter = new MyOncePerRequestFilter();
registrationBean.setFilter(myOncePerRequestFilter);
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(-101); // 這里
return registrationBean;
}
}
第二種就是修改spring security攔截器的順序:
spring:
security:
filter:
order: 0
大家可以自己跑跑試試看,完結撒花~~~~~~
原文地址:https://www.cnblogs.com/eaglelihh/p/15009562.html
述:本文將討論如何用最簡單的術語在網站上運行 C# 代碼。半技術講座我使用了 wasm-tools-net7,這是一個基于 wasm-tools 的工作負載,沒有包含任何額外的包。我的重點是簡單性和主要主題。徹底了解該主題可提供完成所有其他任務所需的信息。如何工作?WebAssembly 工作原理:序列圖創建演示創建項目我用的是net7,但這取決于你。Dotnet new console -o WASM_Demo cd WASM_Demo Dotnet workload install wasm-tools-net7此時,需要對 csproj 文件進行修改。Project Sdk=Mi
本文將討論如何用最簡單的術語在網站上運行 C# 代碼。
我使用了 wasm-tools-net7,這是一個基于 wasm-tools 的工作負載,沒有包含任何額外的包。我的重點是簡單性和主要主題。徹底了解該主題可提供完成所有其他任務所需的信息。
WebAssembly 工作原理:序列圖
Dotnet new console -o WASM_Demo
cd WASM_Demo
Dotnet workload install wasm-tools-net7
此時,需要對 csproj 文件進行修改。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<WasmMainJSPath>main.js</WasmMainJSPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="main.js" />
</ItemGroup>
</Project>
我們添加了什么:
返回到program.cs文件,需要考慮某些規則。
讓我們舉個例子。
using System.Runtime.InteropServices.JavaScript;
namespace WASM_Demo;
public partial class Program
{
static void Main(string[] args) { }
[JSExport]
public static string Response()
{
return """
<h1>
Hello World
</h1>
""";
}
}
沒關系,但是我們如何在瀏覽器中運行此代碼?
運行這個程序的代碼是dotnet.js的,它自帶了wasm-tools,所以沒有必要擔心它。要使用此dotnet.js,我們只需使用一個名為 main.js 的文件。
import { dotnet } from './dotnet.js'
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } = await dotnet
.withDiagnosticTracing(false)
.withApplicationArgumentsFromQuery()
.create();
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const html =
exports
.WASM_Demo // Namespace
.Program // Class Name
.Response(); // Function Name
// Regular javascript code
document.getElementById("app").innerHTML = `${html}`;
await runMainAndExit(config.mainAssemblyName, [] /* Console App Args */);
index.html頁面的模板已經準備完畢。
<!DOCTYPE html>
<html lang="en">
<head>
<title>WASM Demo</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="modulepreload" href="./dotnet.js" />
</head>
<body>
<main id="app"></main>
<script type="module" src="./main.js"></script>
</body>
</html>
現在,讓我們再看一遍這個過程,
我們還有一件事要做,你需要打開一個名為 runtimeconfig.template.json 的文件,并將以下 JSON 數據放入其中。
{
"wasmHostProperties": {
"perHostConfig": [
{
"name": "browser",
"html-path": "index.html",
"Host": "browser"
}
]
}
}
我們已經到了盡頭,程序現在可以運行了。唯一需要的命令是:
Dotnet run -c Release
我可以托管所有文件而不是 wasm-tools 嗎?又是如何做到的呢?
當然,但它可能會變得有點復雜,你用 wasm-tools 制作的項目不能用于任何其他目的,即控制臺應用程序不起作用,wasm-tools 可以工作。因為我們選擇 browser-wasm 作為 RuntimeIdentifier,并且多個 RuntimeIdentifiers 在 .NET 中不可用。作為替代方法,您可以打開兩個項目,將第一個項目設置為 WASM 項目,然后在第二個項目中將其設置為控制臺應用程序,然后生成第一個項目并托管輸出文件夾,所有 DLL 和文件都將在那里。
這個演示只是索引文件,我可以做多頁嗎?又是如何做到的呢?
當然,但這比你想象的要難得多,因為這樣做的方法是一種叫做SPA(單頁應用程序)的方法,用戶總是在同一頁面上,只是內容發生了變化。有多種方法可以做到這一點。所以它可以用你的創造力來完成。
我可以像計數器一樣做動態代碼嗎?又是如何做到的呢?
_是的,我也這樣做了,你可以一遍又一遍地調用 C# 函數,如果你只是將導出綁定到 window 對象,你可以從每個 JavaScript 代碼中調用它。
由于MQ是異步處理消息的,所以MQ不適合做同步處理操作,如果需要及時的返回處理結果請不要用MQ;
優點: 解耦,利用MQ我們可以很好的給我們系統解耦,特別是分布式/微服系統!
原來的同步操作,可以用異步處理,也可以帶來更快的響應速度;
場景 (1)
系統解耦,用戶系統或者其他系統需要發送短信可以通過 MQ 執行;很好的將 用戶系統 和 短信系統進行解耦;
場景(2)
順序執行的任務場景,假設 A B C 三個任務,B需要等待 A完成才去執行,C需要等待B完成才去執行;
我見過一些同學的做法是 ,用 三個定時器 錯開時間去執行的,假設 A定時器 9 點執行, B 定時器 10 點執行 , C 11 點執行 , 類似這樣子;
這樣做其實是 不安全的, 因為 后一個任務 無法知道 前一個任務是否 真的執行了! 假設 A 宕機了, 到 10 點 B 定時去 執行,這時候 數據就會產生異常!
當我們 引入 MQ 后 可以這么做, A執行完了 發送 消息給 B ,B收到消息后 執行,C 類似,收到 B消息后執行;
場景(3)
支付網關的通知,我們的系統常常需要接入支付功能,微信或者支付寶通常會以回調的形式通知我們系統支付結果。
我們可以將我們的支付網關獨立出來,通過MQ通知我們業務系統進行處理,這樣處理有利于系統的解耦和擴展!
假設我們還有一個積分系統,用戶支付成功,給用戶添加積分。只需要積分系統監聽這個消息,并處理積分就好,無需去修改再去修改網關層代碼!
如果沒有使用MQ ,我是不是還得去修改網關系統的代碼,遠程調用增加積分的接口?
這就是使用了MQ的好處,解耦和擴展!
當然我們的轉發規則也要保證每個感興趣的隊列能獲取到消息!
場景(4)
微服/分布式系統,分布式事務 - 最終一致性 處理方案!
詳情: 分布式事務處理方案,微服事務處理方案
場景(5)
我們以前的做法是 通常啟用一個定時器,每分鐘或者每小時,去跑一次取出需要處理的訂單或其他數據進行處理。
這種做法一個是 效率比較低,如果數據量大的話,每次都要掃庫,非常要命!
再者時效性不是很高,最差的時候可能需要等待一輪時長!
還有可能出現重復執行的結果,時效和輪詢的頻率難以平衡!
利用MQ(Rabbitmq),DLX (Dead Letter Exchanges)和 消息的 TTL (Time-To-Live Extensions)特性。我們可以高效的完成這個任務場景!不需要掃庫,時效性更好!
DLX:http://www.rabbitmq.com/dlx.html,
TTL:http://www.rabbitmq.com/ttl.html#per-message-ttl
原理:
發送到隊列的消息,可以設置一個存活時間 TTL,在存活時間內沒有被消費,可以設置這個消息轉發到其他隊列里面去;然后我們從這個其他隊列里面消費執行我們的任務,這樣就可以達到一個消息延時的效果!
設置過期時間:
過期時間可以統一設置到消息隊列里面,也可以單獨設置到某個消息!
PS 如果消息設置了過期時間,發生到了設置有過期時間的隊列,已隊列設置的過期時間為準!
已 SpringBoot 為例:
配置轉發隊列和被轉發隊列:
@Component @Configuration public class RabbitMqConfig { @Bean public Queue curQueue() { Map<String, Object> args = new HashMap<String, Object>(); //超時后的轉發器 過期轉發到 delay_queue_exchange args.put("x-dead-letter-exchange", "delay_queue_exchange"); //routingKey 轉發規則 args.put("x-dead-letter-routing-key", "user.#"); //過期時間 20 秒 args.put("x-message-ttl", 20000); return new Queue("cur_queue", false, false, false, args); } @Bean public Queue delayQueue() { return new Queue("delay_queue"); } @Bean TopicExchange exchange() { //當前隊列 return new TopicExchange("cur_queue_exchange"); } @Bean TopicExchange exchange2() { //被轉發的隊列 return new TopicExchange("delay_queue_exchange"); } @Bean Binding bindingHelloQueue(Queue curQueue, TopicExchange exchange) { //綁定隊列到轉發器 return BindingBuilder.bind(curQueue).to(exchange).with("user.#"); } @Bean Binding bindingHelloQueue2(Queue delayQueue, TopicExchange exchange2) { return BindingBuilder.bind(delayQueue).to(exchange2).with("user.#"); } }
發生消息:
@Component public class MqEventSender { Logger logger = LoggerFactory.getLogger(MqEventSender.class); @Autowired private RabbitTemplate rabbitTemplate; /** * 消息沒有設置 時間 * 發生到隊列 cur_queue_exchange * @param msg */ public void sendMsg(String msg) { logger.info("發送消息: " + msg); rabbitTemplate.convertAndSend("cur_queue_exchange", "user.ss", msg); } /** * 消息設置時間 * 發生到隊列 cur_queue_exchange * @param msg */ public void sendMsgWithTime(String msg) { logger.info("發送消息: " + msg); MessageProperties messageProperties = new MessageProperties(); //過期時間設置 10 秒 messageProperties.setExpiration("10000"); Message message = rabbitTemplate.getMessageConverter().toMessage(msg, messageProperties); rabbitTemplate.convertAndSend("cur_queue_exchange", "user.ss", message); } }
消息監聽:
監聽 的隊列是 delay_queue 而不是 cur_queue;
PS cur_queue 不應該有監聽者,否則消息被消費達不到想要的延時消息效果!
/** * Created by linli on 2017/8/21. * 監聽 被丟到 超時隊列內容 */ @Component @RabbitListener(queues = "delay_queue") public class DelayQueueListener { public static Logger logger = LoggerFactory.getLogger(AddCommentsEventListener.class); @RabbitHandler public void process(@Payload String msg) { logger.info("收到消息 "+msg); } }
測試:
/** * Created by linli on 2017/8/21. */ @RestController @RequestMapping("/test") public class TestContorller { @Autowired MqEventSender sender; @RequestMapping("/mq/delay") public String test() { sender.sendMsg("隊列延時消息!"); sender.sendMsgWithTime("消息延時消息!"); return ""; } }
結果:
觀察結果發現:發送時間 和 收到時間 間隔 20秒 ;
我們給消息設置的 10 秒 TTL 時間沒有生效!驗證了 : 如果消息設置了過期時間,發生到了設置有過期時間的隊列,已隊列設置的過期時間為準!
如果希望每個消息都要自己的存活時間,發送到隊列 不要設置
args.put(“x-message-ttl”, 20000);
消息的過期時間 設置在隊列還是消息,根據自己的業務場景去定!
MQ 是一個跨進程的消息隊列,我們可以很好的利用他進行系統的解耦;
引入MQ會給系統帶來一定的復雜度,需要評估!
MQ 適合做異步任務,不適合做同步任務!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。