8000 feat: Introduce VaadinSecurityConfigurer for modular security configu… · vaadin/flow@d112135 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit d112135

Browse files
authored
feat: Introduce VaadinSecurityConfigurer for modular security configuration (#21373)
Introduced `VaadinWebSecurityConfigurer` to improve modularity and readability, with almost the same logic present in `VaadinWebSecurity`. This change promotes reusability and better separation of concerns while maintaining existing functionality. Note: when using `VaadinWebSecurityConfigurer`, the Spring Security configuration class must most likely be annotated with `@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)` to ensure the `VaadinAwareSecurityContextHolderStrategy` is set up (to be fixed in future releases)
1 parent c17d9cb commit d112135

File tree

17 files changed

+1469
-126
lines changed

17 files changed

+1469
-126
lines changed

flow-tests/vaadin-spring-tests/test-spring-security-flow-contextpath/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<groupId>com.vaadin</groupId>
4848
<artifactId>test-spring-security-flow</artifactId>
4949
<version>${project.version}</version>
50+
<classifier>tests</classifier>
5051
<type>test-jar</type>
5152
<scope>test</scope>
5253
</dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.vaadin.flow.spring.flowsecurity;
2+
3+
import jakarta.servlet.ServletContext;
4+
5+
import java.util.stream.Collectors;
6+
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.context.annotation.Profile;
11+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
12+
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
13+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
14+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
15+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
16+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
17+
import org.springframework.security.core.userdetails.User;
18+
import org.springframework.security.core.userdetails.UserDetails;
19+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
20+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
21+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
22+
23+
import com.vaadin.flow.component.UI;
24+
import com.vaadin.flow.internal.UrlUtil;
25+
import com.vaadin.flow.spring.RootMappedCondition;
26+
import com.vaadin.flow.spring.VaadinConfigurationProperties;
27+
import com.vaadin.flow.spring.flowsecurity.data.UserInfo;
28+
import com.vaadin.flow.spring.flowsecurity.service.UserInfoService;
29+
import com.vaadin.flow.spring.flowsecurity.views.LoginView;
30+
import com.vaadin.flow.spring.security.VaadinWebSecurity;
31+
32+
import static com.vaadin.flow.spring.flowsecurity.service.UserInfoService.ROLE_ADMIN;
33+
34+
@EnableWebSecurity
35+
@EnableMethodSecurity(prePostEnabled = false, jsr250Enabled = true, securedEnabled = true)
36+
@Configuration
37+
@Profile("legacy-vaadin-web-security")
38+
public class LegacySecurityConfig extends VaadinWebSecurity {
39+
40+
@Autowired
41+
private UserInfoService userInfoService;
42+
43+
@Autowired
44+
private ServletContext servletContext;
45+
46+
@Autowired
47+
private VaadinConfigurationProperties vaadinConfigurationProperties;
48+
49+
public String getLogoutSuccessUrl() {
50+
String logoutSuccessUrl;
51+
String mapping = vaadinConfigurationProperties.getUrlMapping();
52+
if (RootMappedCondition.isRootMapping(mapping)) {
53+
logoutSuccessUrl = "/";
54+
} else {
55+
logoutSuccessUrl = mapping.replaceFirst("/\\*$", "/");
56+
}
57+
String contextPath = servletContext.getContextPath();
58+
if (!"".equals(contextPath)) {
59+
logoutSuccessUrl = contextPath + logoutSuccessUrl;
60+
}
61+
return logoutSuccessUrl;
62+
}
63+
64+
@Override
65+
public void configure(HttpSecurity http) throws Exception {
66+
http.authorizeHttpRequests(auth -> auth
67+
.requestMatchers(new AntPathRequestMatcher("/admin-only/**"))
68+
.hasAnyRole(ROLE_ADMIN)
69+
.requestMatchers(new AntPathRequestMatcher("/public/**"))
70+
.permitAll());
71+
super.configure(http);
72+
if (getLogoutSuccessUrl().equals("/")) {
73+
// Test the default url with empty context path
74+
setLoginView(http, LoginView.class);
75+
} else {
76+
setLoginView(http, LoginView.class, getLogoutSuccessUrl());
77+
}
78+
http.logout(cfg -> cfg
79+
.addLogoutHandler((request, response, authentication) -> {
80+
UI ui = UI.getCurrent();
81+
ui.accessSynchronously(() -> ui.getPage()
82+
.setLocation(UrlUtil.getServletPathRelative(
83+
getLogoutSuccessUrl(), request)));
84+
}));
85+
}
86+
87+
@Bean
88+
public InMemoryUserDetailsManager userDetailsService() {
89+
return new InMemoryUserDetailsManager() {
90+
@Override
91+
public UserDetails loadUserByUsername(String username)
92+
throws UsernameNotFoundException {
93+
UserInfo userInfo = userInfoService.findByUsername(username);
94+
if (userInfo == null) {
95+
throw new UsernameNotFoundException(
96+
"No user present with username: " + username);
97+
} else {
98+
return new User(userInfo.getUsername(),
99+
userInfo.getEncodedPassword(),
100+
userInfo.getRoles().stream()
101+
.map(role -> new SimpleGrantedAuthority(
102+
"ROLE_" + role))
103+
.collect(Collectors.toList()));
104+
}
105+
}
106+
};
107+
}
108+
109+
@Bean
110+
protected MethodSecurityExpressionHandler createExpressionHandler() {
111+
return new DefaultMethodSecurityExpressionHandler();
112+
}
113+
114+
}

flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
import java.util.stream.Collectors;
66

7-
import org.springframework.beans.factory.annotation.Autowired;
87
import org.springframework.context.annotation.Bean;
98
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.context.annotation.Import;
10+
import org.springframework.context.annotation.Profile;
1011
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
1112
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
1213
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@@ -17,6 +18,7 @@
1718
import org.springframework.security.core.userdetails.UserDetails;
1819
import org.springframework.security.core.userdetails.UsernameNotFoundException;
1920
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
21+
import org.springframework.security.web.SecurityFilterChain;
2022
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
2123

2224
import com.vaadin.flow.component.UI;
@@ -26,23 +28,31 @@
2628
import com.vaadin.flow.spring.flowsecurity.data.UserInfo;
2729
import com.vaadin.flow.spring.flowsecurity.service.UserInfoService;
2830
import com.vaadin.flow.spring.flowsecurity.views.LoginView;
29-
import com.vaadin.flow.spring.security.VaadinWebSecurity;
31+
import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration;
3032

3133
import static com.vaadin.flow.spring.flowsecurity.service.UserInfoService.ROLE_ADMIN;
34+
import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin;
3235

3336
@EnableWebSecurity
3437
@EnableMethodSecurity(prePostEnabled = false, jsr250Enabled = true, securedEnabled = true)
3538
@Configuration
36-
public class SecurityConfig extends VaadinWebSecurity {
39+
@Profile("default")
40+
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
41+
public class SecurityConfig {
3742

38-
@Autowired
39-
private UserInfoService userInfoService;
43+
private final UserInfoService userInfoService;
4044

41-
@Autowired
42-
private ServletContext servletContext;
45+
private final ServletContext servletContext;
4346

44-
@Autowired
45-
private VaadinConfigurationProperties vaadinConfigurationProperties;
47+
private final VaadinConfigurationProperties vaadinConfigurationProperties;
48+
49+
public SecurityConfig(UserInfoService userInfoService,
50+
ServletContext servletContext,
51+
VaadinConfigurationProperties vaadinConfigurationProperties) {
52+
this.userInfoService = userInfoService;
53+
this.servletContext = servletContext;
54+
this.vaadinConfigurationProperties = vaadinConfigurationProperties;
55+
}
4656

4757
public String getLogoutSuccessUrl() {
4858
String logoutSuccessUrl;
@@ -59,27 +69,29 @@ public String getLogoutSuccessUrl() {
5969
return logoutSuccessUrl;
6070
}
6171

62-
@Override
63-
public void configure(HttpSecurity http) throws Exception {
72+
@Bean
73+
SecurityFilterChain vaadinSecurityFilterChain(HttpSecurity http)
74+
throws Exception {
6475
http.authorizeHttpRequests(auth -> auth
6576
.requestMatchers(new AntPathRequestMatcher("/admin-only/**"))
6677
.hasAnyRole(ROLE_ADMIN)
6778
.requestMatchers(new AntPathRequestMatcher("/public/**"))
6879
.permitAll());
69-
super.configure(http);
70-
if (getLogoutSuccessUrl().equals("/")) {
71-
// Test the default url with empty context path
72-
setLoginView(http, LoginView.class);
73-
} else {
74-
setLoginView(http, LoginView.class, getLogoutSuccessUrl());
75-
}
76-
http.logout(cfg -> cfg
77-
.addLogoutHandler((request, response, authentication) -> {
78-
UI ui = UI.getCurrent();
79-
ui.accessSynchronously(() -> ui.getPage()
80-
.setLocation(UrlUtil.getServletPathRelative(
81-
getLogoutSuccessUrl(), request)));
82-
}));
80+
http.with(vaadin(), cfg -> {
81+
String logoutSuccessUrl = getLogoutSuccessUrl();
82+
if (logoutSuccessUrl.equals("/")) {
83+
cfg.loginView(LoginView.class);
84+
} else {
85+
cfg.loginView(LoginView.class, logoutSuccessUrl);
86+
}
87+
cfg.addLogoutHandler((request, response, authentication) -> {
88+
UI ui = UI.getCurrent();
89+
ui.accessSynchronously(() -> ui.getPage().setLocation(
90+
UrlUtil.getServletPathRelative(getLogoutSuccessUrl(),
91+
request)));
92+
});
93+
});
94+
return http.build();
8395
}
8496

8597
@Bean

flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,11 @@
1616
import org.springframework.security.core.userdetails.UserDetails;
1717
import org.springframework.security.core.userdetails.UsernameNotFoundException;
1818
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
19-
import org.springframework.security.web.DefaultSecurityFilterChain;
2019
import org.springframework.security.web.SecurityFilterChain;
21-
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
2220
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
2321

2422
import com.vaadin.flow.component.UI;
2523
import com.vaadin.flow.internal.UrlUtil;
26-
import com.vaadin.flow.server.HandlerHelper;
27-
import com.vaadin.flow.server.auth.NavigationAccessControl;
2824
import com.vaadin.flow.spring.RootMappedCondition;
2925
import com.vaadin.flow.spring.VaadinConfigurationProperties;
3026
import com.vaadin.flow.spring.flowsecurity.data.UserInfo;
@@ -33,10 +29,10 @@
3329
import com.vaadin.flow.spring.security.AuthenticationContext;
3430
import com.vaadin.flow.spring.security.NavigationAccessControlConfigurer;
3531
import com.vaadin.flow.spring.security.RequestUtil;
36-
import com.vaadin.flow.spring.security.UidlRedirectStrategy;
3732

3833
import static com.vaadin.flow.spring.flowsecurity.service.UserInfoService.ROLE_ADMIN;
3934
import static com.vaadin.flow.spring.security.RequestUtil.antMatchers;
35+
import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin;
4036

4137
@EnableWebSecurity
4238
@Configuration
@@ -64,7 +60,7 @@ public AuthenticationContext authenticationContext() {
6460
@Bean
6561
static NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
6662
return new NavigationAccessControlConfigurer()
67-
.withRoutePathAccessChecker().withLoginView(LoginView.class);
63+
.withRoutePathAccessChecker();
6864
}
6965

7066
@Bean
@@ -81,17 +77,6 @@ public SecurityFilterChain webFilterChain(HttpSecurity http,
8177
// Permit access to static resources
8278
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
8379
.permitAll()
84-
// Permit access to vaadin's internal communication
85-
.requestMatchers(request -> HandlerHelper
86-
.isFrameworkInternalRequest("/*", request))
87-
.permitAll()
88-
.requestMatchers(requestUtil::isAnonymousRoute)
89-
.permitAll()
90-
// Permit technical access to vaadin's static files
91-
.requestMatchers(new AntPathRequestMatcher("/VAADIN/**"))
92-
.permitAll()
93-
// custom request matchers. using 'routeAwareAntMatcher' to
94-
// allow checking route and alias paths against patterns
9580
.requestMatchers(antMatchers("/admin-only/**", "/admin"))
9681
.hasAnyRole(ROLE_ADMIN)
9782
.requestMatchers(antMatchers("/private"))
@@ -105,34 +90,21 @@ public SecurityFilterChain webFilterChain(HttpSecurity http,
10590
.hasAnyRole(ROLE_ADMIN)
10691
.requestMatchers(antMatchers("/home", "/hey/**"))
10792
.permitAll()
108-
// Secure everything else
109-
.anyRequest().authenticated()
110-
);
93+
);
11194
// @formatter:on
112-
113-
http.logout(cfg -> {
114-
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
115-
logoutSuccessHandler.setDefaultTargetUrl(getLogoutSuccessUrl());
116-
logoutSuccessHandler
117-
.setRedirectStrategy(new UidlRedirectStrategy());
118-
cfg.logoutSuccessHandler(logoutSuccessHandler);
119-
cfg.addLogoutHandler((request, response, authentication) -> {
120-
UI ui = UI.getCurrent();
121-
ui.accessSynchronously(() -> ui.getPage().setLocation(
122-
UrlUtil.getServletPathRelative(getLogoutSuccessUrl(),
123-
request)));
124-
});
125-
});
126-
// Custom login page with form authentication
127-
http.formLogin(cfg -> cfg.loginPage("/my/login/page").permitAll());
128-
DefaultSecurityFilterChain filterChain = http.build();
129-
130-
// Test application uses AuthenticationContext, configure it with
131-
// the logout handlers
132-
AuthenticationContext.applySecurityConfiguration(http,
133-
authenticationContext);
134-
135-
return filterChain;
95+
http.with(vaadin(),
96+
cfg -> cfg.loginView(LoginView.class, getLogoutSuccessUrl())
97+
.addLogoutHandler(
98+
(request, response, authentication) -> {
99+
UI ui = UI.getCurrent();
100+
ui.accessSynchronously(() -> ui.getPage()
101+
.setLocation(UrlUtil
102+
.getServletPathRelative(
103+
getLogoutSuccessUrl(),
104+
request)));
105+
}));
106+
107+
return http.build();
136108
}
137109

138110
public String getLogoutSuccessUrl() {

0 commit comments

Comments
 (0)
0