Secure a ZK Application with Spring Security"

From Documentation
 
(12 intermediate revisions by 3 users not shown)
Line 2: Line 2:
  
 
= Secure Your Application in Spring's Way=
 
= Secure Your Application in Spring's Way=
[https://spring.io/projects/spring-security 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 basic of [https://spring.io/projects/spring-boot Spring Boot] and Spring Security. (You can read a Spring Security guide: [https://spring.io/guides/gs/securing-web/ Securing a Web Application] ) So here we just mention those configurations specific to ZK framework.
+
[https://spring.io/projects/spring-security 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 [https://spring.io/projects/spring-boot Spring Boot] and Spring Security. (You can read a Spring Security guide: [https://spring.io/guides/gs/securing-web/ 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 . [https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#use-new-requestmatchers|See upgrade document here]
 +
 
 +
<blockquote>
 +
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).
 +
</blockquote>
 +
 
 +
To note, 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 <code>/zkau</code> from ZK's <code>DhtmlUpdateServlet</code>, you will need to update your http declaration accordingly.
 +
 
 +
To use the new requestMatchers, consider using the <code>servletPath(...).pattern(...)</code> approach:
 +
<source>http
 +
.requestMatchers(new MvcRequestMatcher.Builder(introspector).servletPath("/zkau").pattern("/**"))
 +
</source>
 +
 
 +
Otherwise, to use the previous matchers, such as antMatcher, consider specifying the matcher on your http configuration:
 +
<source><http use-expressions="true" security="none" pattern="/zkau/**" disable-url-rewriting="false" request-matcher="ant"/></source>
  
 
= ZK Spring Boot Starter =
 
= ZK Spring Boot Starter =
Line 21: Line 47:
 
For simplicity, we just register 2 URL mappings:
 
For simplicity, we just register 2 URL mappings:
  
* <tt>/login</tt>: login page
+
* <code>/login</code>: login page
* <tt>/secure/{page}</tt>: all secure pages
+
* <code>/secure/{page}</code>: all secure pages
  
<source lang='java' high='9, 14'>
+
<source lang='java' highlight='9, 14'>
 
@SpringBootApplication
 
@SpringBootApplication
 
@Controller
 
@Controller
Line 45: Line 71:
 
</source>
 
</source>
  
Then put the corresponding zul under <tt>web/zul</tt> folder.
+
Then put the corresponding zul under <code>web/zul</code> folder.
  
 
[[File:zkspring-zul-path.png | center]]
 
[[File:zkspring-zul-path.png | center]]
Line 51: Line 77:
 
= Web Security Configuration =
 
= Web Security Configuration =
  
<source lang='java' high='11, 13, 18,19'>
+
<source lang='java' highlight='11, 13, 18,19'>
 
@Configuration
 
@Configuration
 
@EnableWebSecurity
 
@EnableWebSecurity
Line 69: Line 95:
 
             .requestMatchers(req -> "rmDesktop".equals(req.getParameter("cmd_0"))).permitAll() // allow desktop cleanup from ZATS
 
             .requestMatchers(req -> "rmDesktop".equals(req.getParameter("cmd_0"))).permitAll() // allow desktop cleanup from ZATS
 
             .mvcMatchers("/","/login","/logout").permitAll()
 
             .mvcMatchers("/","/login","/logout").permitAll()
             .mvcMatchers("/secure").hasRole("USER")
+
             .mvcMatchers("/secure/**").hasRole("USER")
 
             .anyRequest().authenticated()
 
             .anyRequest().authenticated()
 
             .and()
 
             .and()
Line 94: Line 120:
 
* Line 7: We need to disable spring CSRF to make ZK AU pass security filter. But don't worry. [[ ZK%20Developer's%20Reference/Security%20Tips/Cross-site%20Request%20Forgery | ZK already has its own CSRF mechanism]].
 
* Line 7: We need to disable spring CSRF to make ZK AU pass security filter. But don't worry. [[ ZK%20Developer's%20Reference/Security%20Tips/Cross-site%20Request%20Forgery | ZK already has its own CSRF mechanism]].
 
* Line 13: This line blocks the public access to [[ZK_Developer%27s_Reference/UI_Composing/ZUML/Include_a_Page#Classpath_Web_Resource_Path | ZK class path web resource folder]].
 
* Line 13: This line blocks the public access to [[ZK_Developer%27s_Reference/UI_Composing/ZUML/Include_a_Page#Classpath_Web_Resource_Path | ZK class path web resource folder]].
* Line 18-19: Assume we want all pages under <tt>/secure</tt> are protected and require an authentication.
+
* Line 18-19: Assume we want all pages under <code>/secure</code> are protected and require an authentication.
  
 
= Login Page=
 
= Login Page=
No matter how you design a login page, remember to enclose it with a <tt><form></tt> and the login URL you specify in web security config.
+
No matter how you design a login page, remember to enclose it with a <code><form></code> and the login URL you specify in the web security config.
  
<source lang='xml' high='1'>
+
<source lang='xml' highlight='1'>
 
     <n:form action="/login" method="POST">
 
     <n:form action="/login" method="POST">
 
         <grid width="450px">
 
         <grid width="450px">
Line 105: Line 131:
 
                 <row spans="2" align="right">
 
                 <row spans="2" align="right">
 
                     <hlayout>
 
                     <hlayout>
                     <button type="reset" label="Reset" /> <button type="submit" label="Submit" />
+
                     <button type="reset" label="Reset" />  
 +
                    <button type="submit" label="Submit" />
 
                     </hlayout>
 
                     </hlayout>
 
                 </row>
 
                 </row>
Line 116: Line 143:
 
[https://github.com/zkoss/zkspringboot/tree/master/zkspringboot-demos/zkspringboot-security-demo github - zkoss/zkspringboot - zkspringboot-security-demo]
 
[https://github.com/zkoss/zkspringboot/tree/master/zkspringboot-demos/zkspringboot-security-demo github - zkoss/zkspringboot - zkspringboot-security-demo]
  
For an example without springboot, please refer to:  
+
For an example without springboot (warfile with spring and zk-spring-security), please refer to:  
 
[https://github.com/zkoss/zkspring/tree/master/zkspringessentials/zkspringcoresec github - zkoss/zkspring - zkspringessentials/zkspringcoresec]
 
[https://github.com/zkoss/zkspring/tree/master/zkspringessentials/zkspringcoresec github - zkoss/zkspring - zkspringessentials/zkspringcoresec]
  
=Version History=
+
= Debug =
{{LastUpdated}}
+
Enable debug log in <code>application.properties</code> like
{| border='1px' | width="100%"
+
<code>logging.level.org.springframework.security.web=DEBUG</code> if you use spring-boot.
! Version !! Date !! Content
+
 
|-
+
For log4j, you can set
| &nbsp;
+
<code>log4j.category.org.springframework.security.web=TRACE</code>
| &nbsp;
+
 
| &nbsp;
+
Check what spring security does internally for a request in the log like:
|}
+
 
 +
<syntaxhighlight lang='text'>
 +
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
 +
 
 +
</syntaxhighlight>
 +
 
 +
= Reference =
 +
* [https://docs.spring.io/spring-security/site/docs/5.5.8/reference/html5/ Spring Security Reference 5.5.8.RELEASE]
  
 
{{ZKSpringEssentialsPageFooter}}
 
{{ZKSpringEssentialsPageFooter}}

Latest revision as of 13:57, 23 December 2024

Secure a ZK Application with 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).

To note, 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. So Please include zk spring boot starter, and it will automatically configure for you with 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.

Zkspring-zul-path.png

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);
    }
}

Login Page

No matter how you design a login page, remember to enclose it with a <form> and the login URL you specify in the web security config.

    <n:form action="/login" method="POST">
        <grid width="450px">
            ...
                <row spans="2" align="right">
                    <hlayout>
                    <button type="reset" label="Reset" /> 
                    <button type="submit" label="Submit" />
                    </hlayout>
                </row>
          ...
        </grid>
    </n:form>

Download Demo Project

github - zkoss/zkspringboot - zkspringboot-security-demo

For an example without springboot (warfile with spring and zk-spring-security), please refer to: github - zkoss/zkspring - zkspringessentials/zkspringcoresec

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



Last Update : 2024/12/23

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.