Reactive Security | Spring WebFlux වලට ආරක්ෂාව | SC Guide

Reactive Security | Spring WebFlux වලට ආරක්ෂාව | SC Guide

ආයුබෝවන් කට්ටිය, කොහොමද යාලුවනේ! අද අපි කතා කරන්න යන්නේ අතිශය වැදගත් සහ කාලීන මාතෘකාවක් ගැන. ඒ තමයි Reactive Security. විශේෂයෙන්ම, අපේ වේගවත්, පරිමාණය කළ හැකි (scalable) Spring WebFlux Applications වලට ආරක්ෂාව එකතු කරන්නේ කොහොමද කියලා තමයි අද අපි කතා කරන්නේ.

මේ දවස්වල Applications හදනකොට performance, scalability, responsiveness කියන දේවල් ගොඩක් වැදගත්. ඒකට තමයි Reactive Programming කියන concepts එක ආවේ. Spring Framework එකත් ඒකට ගැළපෙන්න Spring WebFlux වගේ Module හඳුන්වා දුන්නා. මේවා අපේ Applications Non-Blocking විදියට වැඩ කරන්න උදව් කරනවා.

හැබැයි, මේ Non-Blocking ලෝකයේ අපේ Applications ආරක්ෂා කරගන්නේ කොහොමද කියන එකත් ප්‍රශ්නයක්. සාමාන්‍යයෙන් අපේ traditional Spring MVC Applications වල security implement කරන විදිය මේ Reactive Applications වලට ගැළපෙන්නේ නැහැ. මොකද, Security operations (like fetching user details from a database, validating passwords) ගොඩක් වෙලාවට Blocking operations. ඉතින්, ඒවා Reactive Application එකකදී පාවිච්චි කළොත් Non-Blocking nature එක නැතිවෙලා යනවා.

මේකට විසඳුම තමයි Spring Security වල Reactive support එක. අද අපි බලමු කොහොමද මේකෙන් උපරිම ප්‍රයෝජන අරන් අපේ WebFlux Applications වලට ශක්තිමත් ආරක්ෂාවක් ලබාදෙන්නේ කියලා. එහෙනම්, අපි පටන් ගමු!

Reactive Security කියන්නේ මොකක්ද? (What is Reactive Security?)

මුලින්ම අපි තේරුම් ගමු Reactive Programming කියන්නේ මොකක්ද කියලා. සරලව කිව්වොත්, මේක Asynchronous, Non-Blocking, Event-Driven Applications හදන්න පාවිච්චි කරන Programming paradigm එකක්. මෙහිදී, අපේ code එක data stream එකක් විදියට handle වෙනවා. Data එකක් ආපු ගමන් ඒකට ප්‍රතිචාර දක්වනවා මිසක්, data එක එනකම් නිකරුණේ බලාගෙන ඉන්නේ නැහැ.

සාමාන්‍යයෙන් අපේ Servlet-based (Blocking) Applications වල request එකක් ආවම, ඒ request එකට අදාළ thread එකක් මුළු request එකටම අරගෙන, ඒක ඉවර වෙනකම් අල්ලගෙන ඉන්නවා. හැබැයි Reactive Applications වලදී එහෙම නැහැ. Request එකක් ආවම, thread එකක් ඒක handle කරන්න පටන් ගන්නවා. හැබැයි ඒ thread එක Blocking operation එකකදී (Database call එකක්, External API call එකක්) බ්ලොක් වෙන්නේ නැතුව, වෙන වැඩකට යනවා. කලින් Blocking operation එකෙන් ප්‍රතිචාරයක් ආවම, ආයෙත් thread එකක් අරගෙන ඉතුරු වැඩේ කරනවා. මේක නිසා අපිට එක thread එකකින් request ගොඩක් handle කරන්න පුළුවන් වෙනවා, resource usage එකත් අඩුයි.

දැන් හිතන්න, මේ Non-Blocking flow එකට security ගලපන්නේ කොහොමද කියලා? සාමාන්‍ය Spring Security වල UserDetailsService එක user details load කරන්නේ Blocking විදියට. ඒ වගේම HttpServletRequest එක මත පදනම් වෙලා තමයි filter chain එකක් හැදෙන්නේ. Reactive ලෝකයේ මේවා නැහැ.

Reactive Security කියන්නේ මේ Non-Blocking nature එකට ගැළපෙන්න, Spring Security වලින් හඳුන්වා දීලා තියෙන විසඳුම. මෙහිදී Security related operations (like Authentication, Authorization) Blocking නැතුව, Mono සහ Flux වගේ Reactive types පාවිච්චි කරලා implement කරනවා. මේකෙන් අපිට Application එකේ Performance සහ Scalability එකට කිසිම හානියක් නැතුව ශක්තිමත් Security එකක් ලබාදෙන්න පුළුවන්.

Spring Security WebFlux වලට එන හැටි (How Spring Security Comes to WebFlux)

Spring Framework එකත් එක්කම Spring Securityත් Reactive විදියට අලුත් වුණා. මේකෙන් Reactive Applications වලට security එකතු කරන්න අවශ්‍ය කරන සියලුම tools සහ abstractions අපිට ලැබෙනවා.

ප්‍රධාන වෙනස්කම්:

  1. SecurityWebFilterChain: Traditional Spring Security වල තිබුණු SecurityFilterChain එක වෙනුවට WebFlux වලදී SecurityWebFilterChain එක පාවිච්චි කරනවා. මේක ServerHttpSecurity කියන class එකෙන් configure කරනවා. මේක HTTP requests Reactive විදියට handle කරන්න නිර්මාණය කරපු එකක්.
  2. ReactiveUserDetailsService: සාමාන්‍ය UserDetailsService එක වෙනුවට WebFlux වලදී ReactiveUserDetailsService එක පාවිච්චි කරනවා. මේකෙන් user details Mono<UserDetails> එකක් විදියට return කරනවා. ඒ කියන්නේ, user details load කිරීමත් Non-Blocking විදියට සිද්ධ වෙනවා.
  3. Reactive AuthenticationManager: ReactiveAuthenticationManager එකෙන් authentication process එක Reactive විදියට handle කරනවා.
  4. Reactive Context: Security context එකත් Reactive flow එකට අනුකූලව ReactiveSecurityContextHolder හරහා handle වෙනවා.

මේ වගේ වෙනස්කම් නිසා, අපිට අපේ WebFlux Application එකේ performance එකට කිසිම බලපෑමක් නැතුව, user authentication, authorization, CSRF protection, CORS configuration වගේ හැම security aspect එකක්ම implement කරන්න පුළුවන්.

Project එකට Reactive Security එකතු කරමු (Let's Add Reactive Security to Our Project)

හරි, දැන් අපි theoretical පැත්ත ඇති තරම් කතා කළා. Practical පැත්තට යමු! අපි බලමු කොහොමද Simple Spring WebFlux project එකකට Reactive Security එකතු කරන්නේ කියලා.

පියවර 1: Dependencies එකතු කිරීම

මුලින්ම, අපේ pom.xml (Maven project නම්) හෝ build.gradle (Gradle project නම්) file එකට අවශ්‍ය Dependencies එකතු කරගමු. අපි Maven පාවිච්චි කරනවා කියලා හිතමු.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

spring-boot-starter-webflux එකෙන් WebFlux functionality එක ලැබෙනවා, spring-boot-starter-security එකෙන් Spring Security Reactive support එක ලැබෙනවා.

පියවර 2: Simple WebFlux Controller එකක් හදමු

අපි ආරක්ෂා කරන්න ඕන endpoints ටිකක් හදමු. Controller එකක් මේ වගේ වෙන්න පුළුවන්:

package com.example.reactiveguide.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/")
public class MyReactiveController {

    @GetMapping("/public")
    public Mono<String> publicEndpoint() {
        return Mono.just("Hello from Public API! (No Auth Needed)");
    }

    @GetMapping("/user")
    public Mono<String> userEndpoint() {
        return Mono.just("Hello from User API! (User Role Needed)");
    }

    @GetMapping("/admin")
    public Mono<String> adminEndpoint() {
        return Mono.just("Hello from Admin API! (Admin Role Needed)");
    }

    @GetMapping("/secure")
    public Mono<String> secureEndpoint() {
        return Mono.just("Hello from Secure API! (Authenticated User Needed)");
    }
}

මෙහිදී අපි Public, User, Admin, සහ සාමාන්‍ය Secure Endpoint එකක් හැදුවා. මේවාට Spring Security Rules දාන්නේ කොහොමද කියලා බලමු.

පියවර 3: Reactive Security Configuration එක හදමු

මේක තමයි වැඩේ තියෙන වැදගත්ම කොටස. අපි SecurityConfig කියන class එකක් හදමු.

package com.example.reactiveguide.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public MapReactiveUserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        // In-memory users for demonstration.
        // In a real application, you'd load users from a database or external source.
        UserDetails user = User.builder()
                .username("user")
                .password(passwordEncoder.encode("userpass"))
                .roles("USER")
                .build();

        UserDetails admin = User.builder()
                .username("admin")
                .password(passwordEncoder.encode("adminpass"))
                .roles("ADMIN", "USER")
                .build();

        return new MapReactiveUserDetailsService(user, admin);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // Always use a strong password encoder in production!
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .csrf(ServerHttpSecurity.CsrfSpec::disable) // CSRF disabled for simplicity in this example
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/public").permitAll() // /public endpoint is accessible to everyone
                .pathMatchers("/admin/**").hasRole("ADMIN") // /admin/** endpoints require ADMIN role
                .pathMatchers("/user/**").hasRole("USER") // /user/** endpoints require USER role
                .anyExchange().authenticated() // All other endpoints require authentication
            )
            .httpBasic(Customizer.withDefaults()); // Enable HTTP Basic authentication

        return http.build();
    }
}

මේ code එක පැහැදිලි කරමු:

  • @Configuration සහ @EnableWebFluxSecurity: මේ annotations කියන්නේ මේ class එක Spring Security configuration එකක් කියලා.
  • userDetailsService() Bean එක:
    • මෙහිදී අපි MapReactiveUserDetailsService එකක් පාවිච්චි කරලා in-memory user කෙනෙක් (user/userpass) සහ admin කෙනෙක් (admin/adminpass) හැදුවා.
    • passwordEncoder.encode(): Password එක සෘජුවම නොදී, PasswordEncoder එකකින් encode කරලා දෙනවා. Production එකකදී මේක අනිවාර්යයෙන්ම කරන්න ඕන!
    • Production එකකදී නම් අපිට Database එකකින්, LDAP එකකින්, හෝ වෙනත් user store එකකින් user details load කරන්න ReactiveUserDetailsService interface එක implement කරන්න වෙනවා.
  • passwordEncoder() Bean එක:
    • අපි මෙතැනදී BCryptPasswordEncoder එකක් පාවිච්චි කරනවා. මේක password encrypt කරන්න හොඳ, ශක්තිමත් algorithm එකක්.
  • springSecurityFilterChain(ServerHttpSecurity http) Bean එක:
    • මේක තමයි ප්‍රධාන Security configuration එක. ServerHttpSecurity object එක පාවිච්චි කරලා security rules define කරනවා.
    • .csrf(ServerHttpSecurity.CsrfSpec::disable): මේ example එකේ simplicity එක වෙනුවෙන් CSRF protection එක disable කළා. Real-world applications වල මේක enable කරලා තියෙන්න ඕන.
    • .authorizeExchange(exchanges -> ...): මේකෙන් request එකක් එන path එක අනුව, ඒකට access දෙන්නේ කාටද කියලා define කරනවා.
      • .pathMatchers("/public").permitAll(): /public කියන endpoint එකට authentication නැතුව ඕන කෙනෙක්ට access කරන්න පුළුවන්.
      • .pathMatchers("/admin/**").hasRole("ADMIN"): /admin/ වලින් පටන් ගන්න ඕනෑම path එකකට access කරන්න පුළුවන් වෙන්නේ 'ADMIN' role එක තියෙන කෙනෙක්ට විතරයි.
      • .pathMatchers("/user/**").hasRole("USER"): /user/ වලින් පටන් ගන්න ඕනෑම path එකකට access කරන්න පුළුවන් වෙන්නේ 'USER' role එක තියෙන කෙනෙක්ට විතරයි.
      • .anyExchange().authenticated(): මේ උඩින් කිසිම rule එකකට අහුවෙන්නේ නැති, ඉතුරු හැම endpoint එකකටම access කරන්න authenticated user කෙනෙක් අනිවාර්යයි (ඒ කියන්නේ login වෙලා ඉන්න ඕන).
    • .httpBasic(Customizer.withDefaults()): Basic HTTP Authentication enable කරනවා. මේකෙන් browser එකකින් access කරනකොට username/password pop-up එකක් එනවා.

පියවර 4: Application එක Run කරලා බලමු

දැන් ඔයාලාගේ Spring Boot Application එක run කරන්න. සාමාන්‍යයෙන් 8080 port එකෙන් මේක start වේවි.

  • http://localhost:8080/public: මේක access කරනකොට කිසිම authentication එකක් නැතුව "Hello from Public API! (No Auth Needed)" කියලා output වෙන්න ඕන.
  • http://localhost:8080/user: මේක access කරනකොට authentication pop-up එකක් එනවා. User: user, Password: userpass දීලා login වුනොත් "Hello from User API! (User Role Needed)" කියලා output වෙන්න ඕන. Admin user එකෙන් (admin/adminpass) ගියොත් ඒකත් වැඩ කරන්න ඕන, මොකද admin user එකට USER role එකත් දීලා තියෙනවා.
  • http://localhost:8080/admin: මේකට admin user (admin/adminpass) එකෙන් විතරයි access කරන්න පුළුවන්. "Hello from Admin API! (Admin Role Needed)" කියලා output වෙයි. User එකෙන් (user/userpass) access කරන්න හැදුවොත් "403 Forbidden" error එකක් එයි, මොකද user ට ADMIN role එක නැහැ.
  • http://localhost:8080/secure: මේකට ඕනෑම authenticated user කෙනෙක්ට access කරන්න පුළුවන්. User (user/userpass) හෝ Admin (admin/adminpass) කෙනෙක් විදියට login වුනොත් "Hello from Secure API! (Authenticated User Needed)" කියලා output වෙයි.

මේ විදියට අපිට Spring Security WebFlux වලට එකතු කරලා, අපේ Application එකේ endpoints ආරක්ෂා කරගන්න පුළුවන්.

උසස් ක්‍රමවේද සහ හොඳම පුරුදු (Advanced Scenarios and Best Practices)

මේ අපි දැක්කේ Basic Authentication සහ In-memory users පාවිච්චි කරන Simple scenario එකක්. හැබැයි real-world applications වලදී අපිට මේකට වඩා දේවල් ඕන වෙනවා.

1. Custom ReactiveUserDetailsService

ඔයාලාගේ users database එකක, LDAP එකක, හෝ වෙනත් තැනක තියෙනවා නම්, අපිට ReactiveUserDetailsService interface එක implement කරන්න වෙනවා. මේකෙන් අපිට users Non-Blocking විදියට load කරන්න පුළුවන්.

package com.example.reactiveguide.service;

import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Service
public class CustomReactiveUserDetailsService implements ReactiveUserDetailsService {

    // Assume you have a UserRepository or similar for fetching users from a database
    // @Autowired
    // private UserRepository userRepository;

    @Override
    public Mono<UserDetails> findByUsername(String username) {
        // Replace this with actual database/external service call
        // For demonstration, let's use a dummy user
        if ("dbuser".equals(username)) {
            return Mono.just(
                    org.springframework.security.core.userdetails.User.withUsername("dbuser")
                            .password("{bcrypt}$2a$10$Q4u/A2g8W.F.R.3Xy.Z.4.K.A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z.1.2.3.4.5.6.7.8.9.0.abcdefgh") // Encoded "dbpass"
                            .roles("USER")
                            .build()
            );
        } else {
            return Mono.error(new UsernameNotFoundException("User not found: " + username));
        }
        // return userRepository.findByUsername(username)
        //                      .switchIfEmpty(Mono.error(new UsernameNotFoundException("User not found: " + username)));
    }
}

මේ CustomReactiveUserDetailsService එක SecurityConfig එකේ userDetailsService Bean එක වෙනුවට inject කරන්න පුළුවන්.

2. JWT (JSON Web Token) Security

Modern applications වල authentication වලට JWT බහුලව පාවිච්චි කරනවා. Spring Security WebFlux එක්කත් JWT integrate කරන්න පුළුවන්. මේ සඳහා Third-party libraries (e.g., JWT libraries) හෝ Spring Security ගේ OAuth2 Resource Server support එක පාවිච්චි කරන්න පුළුවන්. මෙහිදී authentication filter එකක් හදලා incoming request එකේ JWT එක validate කරලා user details load කරන්න වෙනවා.

3. OAuth2 / OpenID Connect

Third-party login (Google, Facebook, GitHub) හෝ Single Sign-On (SSO) scenarios වලදී OAuth2 සහ OpenID Connect අත්‍යවශ්‍යයි. Spring Security 5.x වල Reactive OAuth2 Client සහ Resource Server support එක තියෙනවා. මේකෙන් අපිට authorization servers එක්ක secure විදියට communicate කරන්න පුළුවන්.

4. Method Level Security

Endpoint level security වලට අමතරව, අපිට individual methods වලටත් security rules දාන්න පුළුවන්. මේ සඳහා @EnableReactiveMethodSecurity annotation එක පාවිච්චි කරලා, methods උඩ @PreAuthorize, @PostAuthorize, @Secured වගේ annotations පාවිච්චි කරන්න පුළුවන්.

import org.springframework.security.access.prepost.PreAuthorize;
import reactor.core.publisher.Mono;

// ...
@GetMapping("/admin-method")
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> adminMethodEndpoint() {
    return Mono.just("Hello from Admin Method! (Admin Role Needed)");
}

5. Error Handling

Unauthorized access (401) හෝ Forbidden access (403) වගේ අවස්ථාවලදී custom error responses දෙන්න ServerAuthenticationEntryPoint සහ ServerAccessDeniedHandler වගේ interfaces implement කරන්න පුළුවන්. මේවා Reactive Streams විදියට errors handle කරනවා.

නිගමනය (Conclusion)

ඉතින් යාලුවනේ, Reactive Security කියන්නේ අද දවසේ Microservices සහ high-performance applications වලට අත්‍යවශ්‍ය දෙයක්. Spring Security WebFlux එක්ක ඒක අපිට ගොඩක් පහසුවෙන් implement කරන්න පුළුවන්. Non-Blocking nature එක ආරක්ෂා කරගනිමින්, අපේ Application එකට ශක්තිමත් ආරක්ෂාවක් ලබාදෙන්න මේකෙන් හැකියාව ලැබෙනවා.

මේ ලිපියේ තියෙන concepts සහ code samples ඔයාලාගේ project එකකට දාලා try කරලා බලන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, comment section එකේ අහන්න අමතක කරන්න එපා. අපි උදව් කරන්න ලෑස්තියි.

දැනුම බෙදාගැනීමෙන් තමයි අපි හැමෝටම දියුණු වෙන්න පුළුවන්. ඒ නිසා මේ ලිපිය ඔයාලාගේ යාළුවෝ එක්ක share කරන්නත් අමතක කරන්න එපා.

එහෙනම්, තවත් අලුත් ලිපියකින් හමුවෙමු! සුභ දවසක්!