Spring Security (Spring Boot)

Introduction

Spring Security authentication, authorization and hierarchical authorities configuration.


Subpages

Manual authentication, custom authorization w/ OAuth & hierarchical authorities configuration (Spring Boot)

https://sites.google.com/site/pawneecity/sprint-boot/spring-security-spring-boot/authorization-custom-w-oauth-spring-boot


Manual authentication, custom authorization w/ JWT & hierarchical authorities configuration (Spring Boot)

https://sites.google.com/site/pawneecity/sprint-boot/spring-security-spring-boot/authorization-custom-w-jwt-spring-boot


Spring Security Expressions


hasRole, hasAnyRole

Remark: hasRole('ROLE_ADMIN') ~ hasRole(‘ADMIN') because the ‘ROLE_‘ prefix gets added automatically.

hasRole('ADMIN')

hasAnyRole('ADMIN','USER')


hasAuthority, hasAnyAuthority

Remark: hasAuthority('ROLE_ADMIN') ~ hasRole('ROLE_ADMIN')

hasAuthority("ADMIN")

hasAnyAuthority("ADMIN", "USER")


permitAll, denyAll

permitAll() // both anonymous and logged in 

denyAll()


isAnonymous, isAuthenticated, isRememberMe, isFullyAuthenticated

isAnonymous()

isAuthenticated()

isRememberMe() // Rember Me functionality using cookies

isFullyAuthenticated() // If the user isn't an anonymous or remember-me user


principal, authentication

Expressions that, respectively, allow to access the principal object representing the current authorized (or anonymous) user and the current Authentication object from the SecurityContext.


hasPermission

It allows to specify authorization constraints on individual domain objects based on abstract permissions.

hasPermission(Object target, Object permission)

hasPermission(Object targetId, String targetType, Object permission)


POM configuration

pom.xml

  <dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-security</artifactId>

  </dependency>


SecurityFilterChain configuration (Spring Boot >= 3.0)

WebSecurityConfig

package edu.uoc.gpradoc;


import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.annotation.Order;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;

import org.springframework.security.config.http.SessionCreationPolicy;

import org.springframework.security.web.SecurityFilterChain;

import org.springframework.security.web.firewall.StrictHttpFirewall;


import jakarta.inject.Inject;


/**

 * Note: Order lower values have higher priority. Higher values will only execute if no adapter with

 * lower value matches.

 */

@Configuration

@EnableWebSecurity

public class WebSecurityConfig {


  /** Data REST base path */

  private String basePathDataRest;


  /**

   * Constructor

   * 

   * @param basePathDataRest -

   * 

   */

  @Inject

  public WebSecurityConfig(

      @Value("${spring.data.rest.base-path:'/merida'}") String basePathDataRest) {

    super();

    this.basePathDataRest = basePathDataRest;

  }


  /**

   * Configuration for allowing special characters, e.g: ';', '\', '/' and '%'.

   * @return -

   */

  @Bean

  WebSecurityCustomizer webSecurityCustomizer() {

    StrictHttpFirewall firewall = new StrictHttpFirewall();

    // symbol '/' encoded '%2F' [didn't work w/ sb-3.1.3 (uses Tomcat-10.1.12)]

    firewall.setAllowUrlEncodedSlash(true);

    // symbol '\'

    firewall.setAllowBackSlash(true);

    // symbol ';'

    firewall.setAllowSemicolon(true);

    // symbol '%'

    firewall.setAllowUrlEncodedPercent(true);


    return web -> web.httpFirewall(firewall);

  }


  /**

   * @param http

   * @return -

   * @throws Exception

   */

  @Order(4)

  @Bean

  SecurityFilterChain filterChain4(HttpSecurity http) throws Exception {

    /*- DOC:

     * Default user and pass is configured in application.properties (spring.security.user.*).

     */


    http// sb3

        .securityMatcher(basePathDataRest + "/**") // Apply only if match

        .authorizeHttpRequests(authorize -> authorize//

            .anyRequest().authenticated() //

        )//

        .httpBasic(basic -> basic.realmName("GPRA_docback"))//

        .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))//

    ;


    return http.build();

  }


  /**

   * @param http

   * @return -

   * @throws Exception

   */

  @Order(5)

  @Bean

  SecurityFilterChain filterChain5(HttpSecurity http) throws Exception {

    /*- DOC:

     * Default user and pass is configured in application.properties (spring.security.user.*).

     */


    http// sb3

        .securityMatcher("/actuator", "/actuator/**") // Apply only if match

        .authorizeHttpRequests(authorize -> authorize//

            .anyRequest().authenticated() //

        )//

        .httpBasic().realmName("GPRA_docback")//

        .and()//

        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//

    ;


    return http.build();

  }


  /**

   * Springdoc v2 (for Spring Boot >= 3.0).

   * 

   * @param http

   * @return -

   * @throws Exception

   */

  @SuppressWarnings({"java:S1612"})

  @Order(6)

  @Bean

  SecurityFilterChain filterChainSpringDocV2(HttpSecurity http) throws Exception {

    final String[] pathAnonymous = // NOSONAR (SONARJAVA-3991)

        { "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**" };


    http// sb3

        .securityMatcher(pathAnonymous) // only invoke if matching

        .authorizeHttpRequests(authorize -> authorize//

            .anyRequest().anonymous()//

        ) //

    ;


    http.csrf(csrf -> csrf.disable())//

        .headers(headers -> headers.frameOptions(fo -> fo.disable()));//

    // DOC. Avoid generation of cookie JSESSIONID (Swagger)

    http.sessionManagement(sm -> 

               sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));


    return http.build();

  }


  /**

   * Other anonymous.

   * 

   * @param http

   * @return -

   * @throws Exception

   */

  @Order(7)

  @Bean

  SecurityFilterChain filterChainOtherAnonymous(HttpSecurity http) throws Exception {

    final String[] pathAnonymous = { "/ui/**", "/monitor/**", "/tech/**", "/favicon*", "/version", "/csrf" };


     http// sb3

        .securityMatcher(pathAnonymous) // Apply only if match

        .authorizeHttpRequests(authorize -> authorize//

            .anyRequest().anonymous()//

        ) //

    ;


    http.csrf().disable();

    http.headers().frameOptions().disable();

    // DOC. Avoid generation of cookie JSESSIONID (Swagger)

    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


    return http.build();

  }


}