我已经使用 spring security 和 Keycloak 实现了一个 spring boot web 应用程序,以根据本教程对用户进行身份验证。我使用了 maven、spring boot 2.2.2 和 Keycloak 8.0.1。除了单点注销出现问题外,一切正常。当我在浏览器 ( http://localhost:8080/books ) 和 Keycloak 帐户页面 ( http://localhost:8180/auth/realms/ ) 的选项卡中打开 Spring Boot 应用程序的安全路径时{realm_name}/account) 在另一个选项卡中并使用其中一个用户登录,另一个选项卡将知道登录,并且在重新加载页面后,该页面也将被验证。但问题是,当两个选项卡都登录并且首先我从帐户页面注销并重新加载 spring boot 应用程序时,用户仍然处于活动状态并且应用程序不知道其他选项卡中的注销操作。我该如何处理这个问题?
我的项目资源树如下图所示: 项目资源树
这是我的pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sso</groupId>
<artifactId>demoapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoapp</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>13</java.version>
<keycloak.version>8.0.1</keycloak.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- Keycloak Adapter -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-test-helper</artifactId>
<version>${keycloak.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>${keycloak.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这是SecurityConfig.java:
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* Define an HttpSessionManager bean only if missing.
*/
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
/**
* Define security constraints for the application resources.
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/books").authenticated()
.antMatchers("/manager").hasRole("admin")
.anyRequest().permitAll();
}
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
}
这是LibraryController.java:
@Controller
public class LibraryController {
private final HttpServletRequest request;
private final BookRepository bookRepository;
@Autowired
public LibraryController(HttpServletRequest request, BookRepository bookRepository) {
this.request = request;
this.bookRepository = bookRepository;
}
@GetMapping(value = "/")
public String getHome() {
return "index";
}
@GetMapping(value = "/books")
public String getBooks(Model model) {
configCommonAttributes(model);
model.addAttribute("books", bookRepository.readAll());
return "books";
}
@GetMapping(value = "/manager")
public String getManager(Model model) {
configCommonAttributes(model);
model.addAttribute("books", bookRepository.readAll());
return "manager";
}
@GetMapping(value = "/logout")
public String logout() throws ServletException {
request.logout();
return "redirect:/";
}
private void configCommonAttributes(Model model) {
model.addAttribute("firstname", getKeycloakSecurityContext().getIdToken().getGivenName());
model.addAttribute("lastname", getKeycloakSecurityContext().getIdToken().getFamilyName());
model.addAttribute("email", getKeycloakSecurityContext().getIdToken().getEmail());
}
/**
* The KeycloakSecurityContext provides access to several pieces of information
* contained in the security token, such as user profile information.
*/
private KeycloakSecurityContext getKeycloakSecurityContext() {
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
下面是我的DemoappApplication.java:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoappApplication {
public static void main(String[] args) {
SpringApplication.run(DemoappApplication.class, args);
}
}