八、匿名访问和跨域
一、匿名访问
新建AnonymousController
@RestController
@RequestMapping("/anoy")
public class AnonymousController {
@RequestMapping("/hello")
public Mono<String> hello() {
return Mono.just("123");
}
}
如果想要访问路径匹配/anoy/**
这种模式的请求不需要登录即可访问。要怎么做呢?在Spring Reactive Security中可以使用匿名访问。
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers(LOGIN_PAGE).permitAll()
.pathMatchers("/verify_code").permitAll()
.pathMatchers("/anoy/**").hasRole("ANONYMOUS")
.anyExchange().authenticated()
)
.formLogin()
.loginPage(LOGIN_PAGE)
.and()
.csrf().disable()
.anonymous()
.and()
.logout()
;
http.addFilterAt(authenticationManager(), SecurityWebFiltersOrder.FORM_LOGIN);
return http.build();
}
在SecurityWebFilterChain中加入anonymous配置并添加pathMatchers("/anoy/**").hasRole("ANONYMOUS")
权限控制。重启后不需要登录即可访问/anoy/hello
。但是访问其他资源还是需要登录。
SecurityWebFilterChain中的anonymous()是配置AnonymousAuthenticationWebFilter
的。可以看下anonymous()默认实现:
public AnonymousSpec anonymous() {
if (this.anonymous == null) {
this.anonymous = new AnonymousSpec();
}
return this.anonymous;
}
AnonymousAuthenticationWebFilter是由AnonymousSpec配置的,看下属性:
private String key;
private AnonymousAuthenticationWebFilter authenticationFilter;
private Object principal = "anonymousUser";
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
可以看到匿名用户角色是ROLE_ANONYMOUS
。
AnonymousAuthenticationWebFilter
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return ReactiveSecurityContextHolder.getContext().switchIfEmpty(Mono.defer(() -> {
Authentication authentication = createAuthentication(exchange);
SecurityContext securityContext = new SecurityContextImpl(authentication);
logger.debug(LogMessage.format("Populated SecurityContext with anonymous token: '%s'", authentication));
return chain.filter(exchange)
.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)))
.then(Mono.empty());
})).flatMap((securityContext) -> {
logger.debug(LogMessage.format("SecurityContext contains anonymous token: '%s'",
securityContext.getAuthentication()));
return chain.filter(exchange);
});
}
protected Authentication createAuthentication(ServerWebExchange exchange) {
return new AnonymousAuthenticationToken(this.key, this.principal, this.authorities);
}
创建了AnonymousAuthenticationToken。
二、跨域
跨域只存现在前后端分离的项目中。当前端的部署ip或端口与后端的ip或端口不同时就会出现跨域。解决方案就是添加允许跨域的四个响应头。分别是Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,Access-Control-Allow-Method。一般不会直接设置响应头。而是通过CORS(跨域资源共享)来设置的。
单独新建html文件模拟前后端分离:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
</head>
<body>
<div id="content"></div>
<button onclick="getData()">点击</button>
</body>
<script>
function getData() {
$.get('http://localhost:8080/anoy/hello',{}, function(res) {
$('#content').html(res)
})
}
</script>
</html>
在浏览器打开后点击按钮,查看浏览器控制台:
修改SecurityWebFilterChain:
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers(LOGIN_PAGE).permitAll()
.pathMatchers("/verify_code").permitAll()
.pathMatchers("/anoy/**").hasRole("ANONYMOUS")
.anyExchange().authenticated()
)
.formLogin()
.loginPage(LOGIN_PAGE)
.and()
.csrf().disable()
.anonymous()
.and()
.cors()
.configurationSource((exchange) -> {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedMethod(HttpMethod.POST);
return corsConfiguration;
})
.and()
.logout()
;
http.addFilterAt(authenticationManager(), SecurityWebFiltersOrder.FORM_LOGIN);
return http.build();
}
增加了cors,addAllowedOrigin("*")
表示所有的ip都可以访问,addAllowedMethod(HttpMethod.POST)
表示添加了POST方法访问。默认允许GET
和HEAD
方法。因为登录接口是POST方式,必须要添加POST的CorsConfiguration。重启后点击页面按钮: