parent
426e24c18e
commit
e14af37775
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.web.server.DelegatingAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.LogoutWebFiter;
|
||||
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
@ -175,6 +176,7 @@ public class HttpSecurity {
|
|||
filters.add(new LoginPageGeneratingWebFilter());
|
||||
}
|
||||
filters.add(this.formLogin.build());
|
||||
filters.add(new LogoutWebFiter());
|
||||
}
|
||||
filters.add(new AuthenticationReactorContextFilter());
|
||||
if(this.authorizeExchangeBuilder != null) {
|
||||
|
|
|
@ -67,7 +67,14 @@ public class FormLoginTests {
|
|||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
|
||||
DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class);
|
||||
DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class)
|
||||
.assertAt();
|
||||
|
||||
loginPage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("invalid")
|
||||
.submit(DefaultLoginPage.class)
|
||||
.assertError();
|
||||
|
||||
HomePage homePage = loginPage.loginForm()
|
||||
.username("user")
|
||||
|
@ -75,6 +82,12 @@ public class FormLoginTests {
|
|||
.submit(HomePage.class);
|
||||
|
||||
homePage.assertAt();
|
||||
|
||||
driver.get("http://localhost/logout");
|
||||
|
||||
DefaultLoginPage.create(driver)
|
||||
.assertAt()
|
||||
.assertLogout();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -161,6 +174,8 @@ public class FormLoginTests {
|
|||
public static class DefaultLoginPage {
|
||||
|
||||
private WebDriver driver;
|
||||
@FindBy(css = "div[role=alert]")
|
||||
private WebElement alert;
|
||||
|
||||
private LoginForm loginForm;
|
||||
|
||||
|
@ -169,6 +184,25 @@ public class FormLoginTests {
|
|||
this.loginForm = PageFactory.initElements(webDriver, LoginForm.class);
|
||||
}
|
||||
|
||||
static DefaultLoginPage create(WebDriver driver) {
|
||||
return PageFactory.initElements(driver, DefaultLoginPage.class);
|
||||
}
|
||||
|
||||
public DefaultLoginPage assertAt() {
|
||||
assertThat(this.driver.getTitle()).isEqualTo("Please sign in");
|
||||
return this;
|
||||
}
|
||||
|
||||
public DefaultLoginPage assertError() {
|
||||
assertThat(this.alert.getText()).isEqualTo("Invalid credentials");
|
||||
return this;
|
||||
}
|
||||
|
||||
public DefaultLoginPage assertLogout() {
|
||||
assertThat(this.alert.getText()).isEqualTo("You have been signed out");
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginForm loginForm() {
|
||||
return this.loginForm;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
*
|
||||
* * Copyright 2002-2017 the original author or authors.
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.server.authentication.logout;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface LogoutHandler {
|
||||
Mono<Void> logout(WebFilterExchange exchange, Authentication authentication);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
*
|
||||
* * Copyright 2002-2017 the original author or authors.
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.server.authentication.logout;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class LogoutWebFiter implements WebFilter {
|
||||
private AnonymousAuthenticationToken anonymousAuthenticationToken = new AnonymousAuthenticationToken("key", "anonymous",
|
||||
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
private LogoutHandler logoutHandler = new SecurityContextRepositoryLogoutHandler();
|
||||
|
||||
private ServerWebExchangeMatcher requiresLogout = ServerWebExchangeMatchers
|
||||
.pathMatchers("/logout");
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return this.requiresLogout.matches(exchange)
|
||||
.filter( result -> result.isMatch())
|
||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||
.flatMap( result -> exchange.getPrincipal().cast(Authentication.class))
|
||||
.defaultIfEmpty(this.anonymousAuthenticationToken)
|
||||
.flatMap( authentication -> this.logoutHandler.logout(new WebFilterExchange(exchange, chain), authentication));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
*
|
||||
* * Copyright 2002-2017 the original author or authors.
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.server.authentication.logout;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.server.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.server.RedirectStrategy;
|
||||
import org.springframework.security.web.server.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.context.WebSessionSecurityContextRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SecurityContextRepositoryLogoutHandler implements LogoutHandler {
|
||||
private SecurityContextRepository repository = new WebSessionSecurityContextRepository();
|
||||
|
||||
private URI logoutSuccessUrl = URI.create("/login?logout");
|
||||
|
||||
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
@Override
|
||||
public Mono<Void> logout(WebFilterExchange exchange,
|
||||
Authentication authentication) {
|
||||
return this.repository.save(exchange.getExchange(), null)
|
||||
.then(this.redirectStrategy.sendRedirect(exchange.getExchange(), this.logoutSuccessUrl));
|
||||
}
|
||||
}
|
|
@ -32,7 +32,13 @@ public class WebSessionSecurityContextRepository implements SecurityContextRepos
|
|||
|
||||
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
|
||||
return exchange.getSession()
|
||||
.doOnNext(session -> session.getAttributes().put(SESSION_ATTR, context))
|
||||
.doOnNext(session -> {
|
||||
if(context == null) {
|
||||
session.getAttributes().remove(SESSION_ATTR);
|
||||
} else {
|
||||
session.getAttributes().put(SESSION_ATTR, context);
|
||||
}
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
@ -50,18 +51,21 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
|
|||
}
|
||||
|
||||
private Mono<Void> render(ServerWebExchange exchange) {
|
||||
boolean isError = exchange.getRequest().getQueryParams().containsKey("error");
|
||||
MultiValueMap<String, String> queryParams = exchange.getRequest()
|
||||
.getQueryParams();
|
||||
boolean isError = queryParams.containsKey("error");
|
||||
boolean isLogoutSuccess = queryParams.containsKey("logout");
|
||||
ServerHttpResponse result = exchange.getResponse();
|
||||
result.setStatusCode(HttpStatus.FOUND);
|
||||
result.getHeaders().setContentType(MediaType.TEXT_HTML);
|
||||
byte[] bytes = createPage(isError);
|
||||
byte[] bytes = createPage(isError, isLogoutSuccess);
|
||||
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
|
||||
DataBuffer buffer = bufferFactory.wrap(bytes);
|
||||
return result.writeWith(Mono.just(buffer))
|
||||
.doOnError( error -> DataBufferUtils.release(buffer));
|
||||
}
|
||||
|
||||
private static byte[] createPage(boolean isError) {
|
||||
private static byte[] createPage(boolean isError, boolean isLogoutSuccess) {
|
||||
String page = "<!DOCTYPE html>\n"
|
||||
+ "<html lang=\"en\">\n"
|
||||
+ " <head>\n"
|
||||
|
@ -78,6 +82,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
|
|||
+ " <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
|
||||
+ " <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
|
||||
+ createError(isError)
|
||||
+ createLogoutSuccess(isLogoutSuccess)
|
||||
+ " <p>\n"
|
||||
+ " <label for=\"username\" class=\"sr-only\">Username</label>\n"
|
||||
+ " <input type=\"text\" id=\"username\" name=\"username\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
|
||||
|
@ -98,4 +103,8 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
|
|||
private static String createError(boolean isError) {
|
||||
return isError ? "<div class=\"alert alert-danger\" role=\"alert\">Invalid credentials</div>" : "";
|
||||
}
|
||||
|
||||
private static String createLogoutSuccess(boolean isLogoutSuccess) {
|
||||
return isLogoutSuccess ? "<div class=\"alert alert-success\" role=\"alert\">You have been signed out</div>" : "";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue