Secure a ZK Application with Spring Security"

From Documentation
 
(10 intermediate revisions by 2 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.
  
 
= ZK Spring Boot Starter =
 
= ZK Spring Boot Starter =
Line 21: Line 23:
 
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 47:
 
</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 53:
 
= 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 71:
 
             .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 96:
 
* 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 107:
 
                 <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 119:
 
[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 01:22, 20 December 2022

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.

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 : 2022/12/20

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