项目中自定义了拦截器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小时内与您取得联系。