Spring Security
Secure Your Application in Spring's Way
Spring Security is a widely-adopted framework. It can also work with ZK without problems. This doesn't even need zkspring-security. This page will show you how to do it. We assume you know the basics of Spring Boot and Spring Security. (You can read a Spring Security guide: Securing a Web Application ) So here we just mention those configurations specific to ZK framework.
The example code mentioned here only works for Spring Security 4/5.
Spring 6 update
Spring security 6 uses a new RequestMatcher method . upgrade document here
In Spring Security 5.8, the antMatchers, mvcMatchers, and regexMatchers methods were deprecated in favor of new requestMatchers methods.
The new requestMatchers methods were added to authorizeHttpRequests, authorizeRequests, CSRF configuration, WebSecurityCustomizer and any other places that had the specialized RequestMatcher methods. The deprecated methods are removed in Spring Security 6.
These new methods have more secure defaults since they choose the most appropriate RequestMatcher implementation for your application. In summary, the new methods choose the MvcRequestMatcher implementation if your application has Spring MVC in the classpath, falling back to the AntPathRequestMatcher implementation if Spring MVC is not present (aligning the behavior with the Kotlin equivalent methods).
Note that the new default requestMatchers parse paths relative to the root of the servletContext, not to the root of the applicationContext.
As a result, if you are excluding URLs from ZK's servlets, such as /zkau
from ZK's DhtmlUpdateServlet
, you will need to update your http declaration accordingly.
To use the new requestMatchers, consider using the servletPath(...).pattern(...)
approach:
http
.requestMatchers(new MvcRequestMatcher.Builder(introspector).servletPath("/zkau").pattern("/**"))
Otherwise, to use the previous matchers, such as antMatcher, consider specifying the matcher on your HTTP configuration:
<http use-expressions="true" security="none" pattern="/zkau/**" disable-url-rewriting="false" request-matcher="ant"/>
ZK Spring Boot Starter
Spring encourages users to start with Spring Boot. Please include zk spring boot starter, and it will automatically configure for you with the most commonly used settings.
Spring Boot Starter Security
Follow Securing a Web Application, we add the following elements:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${springboot.version}</version>
</dependency>
Spring Controller
For simplicity, we just register 2 URL mappings:
/login
: login page/secure/{page}
: all secure pages
@SpringBootApplication
@Controller
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/secure/{page}")
public String secure(@PathVariable String page) {
return "secure/" + page;
}
}
Then put the corresponding zul under web/zul
folder.
Web Security Configuration
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public static final String ZUL_FILES = "/zkau/web/**/*.zul";
public static final String[] ZK_RESOURCES = {"/zkau/web/**/js/**", "/zkau/web/**/zul/css/**", "/zkau/web/**/img/**"};
// allow desktop cleanup after logout or when reloading login page
public static final String REMOVE_DESKTOP_REGEX = "/zkau\\?dtid=.*&cmd_0=rmDesktop&.*";
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers(ZUL_FILES).denyAll() // block direct access to zul files
.antMatchers(HttpMethod.GET, ZK_RESOURCES).permitAll() // allow zk resources
.regexMatchers(HttpMethod.GET, REMOVE_DESKTOP_REGEX).permitAll() // allow desktop cleanup
.requestMatchers(req -> "rmDesktop".equals(req.getParameter("cmd_0"))).permitAll() // allow desktop cleanup from ZATS
.mvcMatchers("/","/login","/logout").permitAll()
.mvcMatchers("/secure/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/secure/main")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/");
}
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
- Line 7: We need to disable spring CSRF to make ZK AU pass security filter. But don't worry. ZK already has its own CSRF mechanism.
- Line 13: This line blocks the public access to ZK class path web resource folder.
- Line 18-19: Assume we want all pages under
/secure
are protected and require authentication.
Building Login Page
Feature | ZUL with Form Submission | Non-ZUL Page |
---|---|---|
Technology Used | ZUL with an HTML form | HTML, JSP...etc |
Resource Handling | Uses ZK Resource Engine to serve static resources via /zkres |
Direct request to a server |
Security Configuration |
|
No need to allow anonymous access to any zk-related URL |
Consistency with ZK UI | Ensures a uniform UI theme within the ZK application | May require additional CSS customization |
Using ZUL with Form Submission
We recommend you build a login page in zul with a form submission way which means enclosing a user name and password fields with an HTML form tag like:
<zk xmlns:h="native">
<h:form id="f" name="f" action="your-login-url" method="POST">
<!-- your login form -->
</h:form>
</zk>
- Line 2: Replace
/your-login-url
in theaction
with your application's specific login handling URL.
Configure ZK Resource Engine
Configure ZK Resource Engine then ZK can retrieve zul-related resources (including wpd, wcs, and images) in a different URL (/zkres
by default) instead of the same URL of sending events, /zkau
.
The login page requires anonymous access because an end-user has not been authenticated yet. If you have configured ZK Resource Engine, you have to allow anonymous access to /zkres
instead of /zkau
and make zul pages work.
If you don't configure ZK Resource Engine, you need to allow anonymous access to /zkau
. This /zkau
is the same channel all zul pages communicate to a server. I assume all your zul pages are protected except the login zul page. If you allow anonymous access to /zkau
, just like opening an unchecked channel in your application, which makes the application less secure.
AJAX or Not
When building the login page in zul, you need to consider using AJAX or not.
No AJAX
Security Considerations
AJAX-based login requires allowing anonymous access to /zkau
for handling authentication in an event listener. Form submission relies on standard HTTP authentication mechanisms, making it easier to integrate with Spring Security.
Session Handling & Redirects
With form submission, session creation and redirection after login are handled naturally by the server Spring Security. In contrast, AJAX-based authentication may require additional logic to process session tokens and manually handle page redirection after authentication.
Usin AJAX
If you require ZK features within your login page, you can apply a specific RequestMatcher
to allow the AU requests coming from the login page, see LoginZkauMatcher.
Benefits
Still Protect ZK AJAX Channel
By configuring the ZK Resource Engine, developers can separate resource retrieval from AJAX event processing. This allows static resources to be accessed via /zkres (instead of /zkau), enabling anonymous access only to required resources while keeping /zkau protected under the security filter. This approach ensures the login page functions correctly without introducing security vulnerabilities.
Consistent UI theme
Using ZUL helps maintain a consistent visual theme across your ZK application. If the login page is built with HTML, additional styling may be necessary to align it with the existing UI.
Non-ZUL Login Page
The alternative is to build a login page without a zul.
This can be done with form-based authentication by implementing the login form in HTML or JSP. And this can also be achieved with other authentication methods which don't require a ZK page to be served, such as redirecting to an external provider, using a SSO provider, etc.
With these options, the security filter doesn't have to be configured to allow anonymous access to ZK AU requests. After a user logs in, all requests including ZK AU can be intercepted by the security filter and rejected if the user is unauthorized or unauthenticated.
Download Demo Project
Debug
Enable debug log in application.properties
like
logging.level.org.springframework.security.web=DEBUG
if you use spring-boot.
For log4j, you can set
log4j.category.org.springframework.security.web=TRACE
Check what spring security does internally for a request in the log like:
2022-11-29 09:29:25 [TRACE] FilterChainProxy:245 - Trying to match request against DefaultSecurityFilterChain [RequestMatcher=Mvc [pattern='/login.zul*'], Filters=[]] (1/2)
2022-11-29 09:29:25 [TRACE] FilterChainProxy:245 - Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@5534e6f1, org.springframework.security.web.context.SecurityContextHolderFilter@4c6fc3e7, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@aa8dce8, org.springframework.security.web.authentication.logout.LogoutFilter@6ad112de, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@18a0721b, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@2ae2fa13, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@66e12c3b, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@44485db, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1f6f0fe2, org.springframework.security.web.access.ExceptionTranslationFilter@22604c7e, org.springframework.security.web.access.intercept.AuthorizationFilter@4d8f2cfd]] (2/2)
2022-11-29 09:29:25 [TRACE] FilterChainProxy:245 - Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@5534e6f1, org.springframework.security.web.context.SecurityContextHolderFilter@4c6fc3e7, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@aa8dce8, org.springframework.security.web.authentication.logout.LogoutFilter@6ad112de, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@18a0721b, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@2ae2fa13, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@66e12c3b, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@44485db, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1f6f0fe2, org.springframework.security.web.access.ExceptionTranslationFilter@22604c7e, org.springframework.security.web.access.intercept.AuthorizationFilter@4d8f2cfd]] (2/2)
2022-11-29 09:29:25 [DEBUG] FilterChainProxy:223 - Securing POST /zkau
2022-11-29 09:29:25 [DEBUG] FilterChainProxy:223 - Securing GET /index.zul
Reference