Spring Boot OAuth2 Resource Server ආරක්ෂා කරමු | API Security SC Guide

Spring Boot OAuth2 Resource Server ආරක්ෂා කරමු | API Security SC Guide

ආයුබෝවන් කට්ටියට! Spring Boot API එක OAuth2 Resource Server එකක් විදියට ආරක්ෂා කරමු!

අද කාලේ software development වලදී API කියන්නේ අත්‍යවශ්‍යම දෙයක්, නේද? අපි හදන mobile apps වලට, web applications වලට, වෙනත් microservices වලට මේ හැමදේටම data දෙන්නේ API වලින්. හැබැයි ඉතින් මේ API ටික හරියට ආරක්ෂා කරලා නැත්නම්, අපේ customer data, company secrets වගේ ගොඩක් වටින දේවල් අතන මෙතන විසිරිලා යන්න පුළුවන්. ඒක ලොකු අවුලක්, නේද?

දැන් ඔයාලා හිතනවා ඇති, "ඉතින් මේක ආරක්ෂා කරන්නේ කොහොමද?" කියලා. ගොඩක් ක්‍රම තියෙනවා. හැබැයි අද අපි කතා කරන්නේ ඒ අතරින් ගොඩක්ම ජනප්‍රිය, වර්තමානයේදී ගොඩක් දුරට Industry Standard එකක් වෙලා තියෙන OAuth2 ගැන. විශේෂයෙන්ම, අපේ Spring Boot API එකක් OAuth2 Resource Server එකක් විදියට setup කරගෙන ආරක්ෂා කරගන්නේ කොහොමද කියන එක ගැනයි.

මේ blog post එක කියවලා ඉවර වෙනකොට ඔයාලට පුළුවන් වෙයි පොඩි Spring Boot API එකක් OAuth2 Access Tokens වලින් protect කරන්න. ඒ වගේම, මේ concept එකෙන් ඔයාලගේ applications වලට කොහොමද security එක වැඩි කරගන්නේ කියලා පැහැදිලි අවබෝධයක් ගන්නත් පුළුවන් වෙයි.

ඉතින්, ලෑස්තිද? අපි වැඩේට බහිමු!

OAuth2 කියන්නේ හරියටම මොකක්ද?

OAuth2 කියන්නේ Open Authorization කියන එකේ දෙවෙනි version එක. කෙටියෙන්ම කිව්වොත්, මේක authorization protocol එකක්. "Authorization" කියන්නේ කෙනෙක්ට යම් දේකට අවසර දෙන එක. උදාහරණයක් විදියට, ඔයාලා Facebook එකෙන් වෙනත් application එකකට login වෙනකොට, Facebook එකෙන් අහනවා "මේ application එකට ඔයාගේ profile එකට access දෙන්නද?" කියලා. ඔයාලා "Yes" දුන්නම, Facebook එක ඔයාලගේ profile එකට access කරන්න ඒ application එකට අවසරයක් (authorization) දෙනවා. හැබැයි වැදගත්ම දේ තමයි, ඔයාලා Facebook user ID එකයි password එකයි ඒ third-party application එකට දෙන්නේ නැහැ.

මේ OAuth2 වලදී ප්‍රධාන actorsලා ටිකක් ඉන්නවා. පොඩ්ඩක් මේ ගැන බලමු:

  • Resource Owner: මේක තමයි user. ඒ කියන්නේ data වල නියම හිමිකරු. උදාහරණයක් විදියට ඔයාගේ Facebook profile data එකේ හිමිකරු ඔයා.
  • Client: මේක තමයි user'ගේ data request කරන application එක. ඒ කියන්නේ third-party mobile app එකක්, web app එකක් වගේ දෙයක්.
  • Authorization Server: මේ තමයි user'ගේ අවසරය අරගෙන client එකට access token එකක් නිකුත් කරන server එක. Facebook, Google වගේ identity providersලා මේ වගේ Authorization Server එකක් පවත්වාගෙන යනවා.
  • Resource Server: මේ තමයි user'ගේ data තියෙන server එක. අපේ Spring Boot API එක මේ category එකට තමයි වැටෙන්නේ. Client එකෙන් එන access token එක validate කරලා, user'ගේ data දෙන්නද නැද්ද කියලා තීරණය කරන්නේ මේ server එක.

අපේ මේ blog post එකේදී අපි අවධානය යොමු කරන්නේ Resource Server එක ගැනයි. ඒ කියන්නේ, අපේ Spring Boot API එකට එන requests වලට access token එකක් තියෙනවද, ඒ token එක වලංගුද, ඒ token එකට අපේ API එකේ data access කරන්න අවසරය තියෙනවද කියලා check කරලා, ඉන්පසු request එකට ඉඩ දෙන එකයි.

Spring Boot Resource Server එකක් හදාගනිමු

හරි, දැන් අපි theory ටික දැනගත්තා. දැන් බලමු practical විදියට Spring Boot Project එකක් හදාගෙන කොහොමද මේ වැඩේ කරන්නේ කියලා. අපි හදන්නේ simple API එකක් protect කරන විදිය.

Dependencies එකතු කරමු

මුලින්ම, ඔයාලගේ Spring Boot project එකට අවශ්‍ය Maven/Gradle dependencies ටික එකතු කරගන්න ඕනේ. අපි මෙතනදී Maven පාවිච්චි කරමු. ඔයාලගේ pom.xml file එකට මේ dependencies ටික එකතු කරන්න:


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

මෙහිදී spring-boot-starter-oauth2-resource-server dependency එක තමයි core එක. මේකෙන් තමයි OAuth2 Resource Server functionalities අපේ project එකට add වෙන්නේ. spring-boot-starter-web අපි REST API හදන්න පාවිච්චි කරනවා. spring-boot-starter-security සහ spring-security-config තමයි අපේ security configurations handle කරන්න උදව් වෙන්නේ.

Configuration එක සකස් කරමු

දැන් අපි application.yml (හෝ application.properties) file එකට අපේ Authorization Server එකේ details ටික add කරන්න ඕනේ. මේක තමයි අපේ Resource Server එකට කියන්නේ කොහෙන්ද access tokens validate කරන්න ඕනේ කියලා.


spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/realms/my-realm # Replace with your Authorization Server's Issuer URI
          # jwk-set-uri: http://localhost:8080/realms/my-realm/protocol/openid-connect/certs # Optional, if not inferred from issuer-uri
server:
  port: 8081 # Our Resource Server will run on port 8081

issuer-uri කියන්නේ ගොඩක් වැදගත් property එකක්. මේක තමයි අපේ Resource Server එකට කියන්නේ access token එක නිකුත් කරපු Authorization Server එක කොතනද තියෙන්නේ කියලා. Spring Security වලට පුළුවන් මේ issuer-uri එක පාවිච්චි කරලා Authorization Server එකේ public keys (JWKS - JSON Web Key Set) තියෙන endpoint එක automatic discover කරගන්න. ඒ නිසා, jwk-set-uri එක දාන එක අනිවාර්ය නැහැ, හැබැයි සමහර වෙලාවට explicit විදියට දාන්න වෙන අවස්ථා තියෙන්න පුළුවන්.

Security Configuration Class එක හදමු

දැන් අපි Spring Security configurations ටික ලියන්න SecurityConfig කියලා Java class එකක් හදමු.


package com.example.resource.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated() // All requests must be authenticated
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt() // Configure JWT decoding for Resource Server
            )
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // Don't create sessions

        return http.build();
    }
}

මේ code එකේ මොකද වෙන්නේ කියලා පොඩ්ඩක් බලමු:

  • @Configuration: මේ class එක Spring configuration class එකක් කියලා define කරනවා.
  • @EnableWebSecurity: Spring Security features enable කරනවා.
  • securityFilterChain method එක:
    • authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()): මේකෙන් කියන්නේ අපේ API එකට එන හැම request එකක්ම authenticated වෙන්න ඕනේ කියලා. ඒ කියන්නේ valid access token එකක් නැතුව කිසිම request එකකට ඉඩ දෙන්නේ නැහැ.
    • oauth2ResourceServer(oauth2 -> oauth2.jwt()): මේකෙන් තමයි Spring Security වලට කියන්නේ මේ application එක OAuth2 Resource Server එකක් විදියට JWT tokens (JSON Web Tokens) validate කරන්න ඕනේ කියලා.
    • sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)): REST APIs වලදී සාමාන්‍යයෙන් sessions පාවිච්චි කරන්නේ නැහැ. Access token එකේම user'ගේ state තියෙන නිසා session එකක් අවශ්‍ය වෙන්නේ නැහැ. මේකෙන් අපේ server එක Stateless වෙනවා, scalability වලට ගොඩක් හොඳයි.

Access Token එක Verify කරන හැටි

Spring Security වල spring-boot-starter-oauth2-resource-server dependency එක දැම්මම, ඒක automatic විදියට JWT access tokens validate කරන mechanism එක handle කරනවා. මේක කොහොමද වෙන්නේ කියලා බලමු.

JWT (JSON Web Token) සහ JWKS Endpoint

Client එකක් Resource Server එකට request එකක් යවනකොට, Authorization: Bearer <access_token> වගේ header එකක් එක්ක තමයි යවන්නේ. මේ <access_token> එක සාමාන්‍යයෙන් JWT එකක්.

JWT එකට කොටස් තුනක් තියෙනවා: Header, Payload, Signature. Signature එක තමයි වැදගත්ම දේ. මේක Authorization Server එකේ Private Key එකෙන් sign කරලා තියෙන්නේ. Resource Server එකට මේ Signature එක validate කරන්න Authorization Server එකේ Public Key එක ඕනේ.

ඔයාලා application.yml එකේ දීපු issuer-uri එකෙන්, Spring Security වලට පුළුවන් Authorization Server එකේ JWKS (JSON Web Key Set) endpoint එක හොයාගන්න. JWKS endpoint එක කියන්නේ JSON format එකෙන් Authorization Server එකේ public keys ටික deploy කරලා තියෙන තැන. Spring Security ඒකෙන් public key එක download කරගෙන, එන JWT එකේ signature එක validate කරනවා. මේ process එකට JWT decoder එකක් පාවිච්චි කරනවා.

මේ validation එක සාර්ථක වුණොත්, JWT එකේ තියෙන claims (data) ටික Spring Security Context එකට add වෙනවා. මේ claims වල user'ගේ ID, user'ගේ roles, user'ගේ permissions (scopes) වගේ දේවල් තියෙන්න පුළුවන්.

Scopes සහ Claims පාවිච්චි කරලා Authorization කරන හැටි

Access token එක validate වුණාට පස්සේ, අපිට පුළුවන් ඒ token එකේ තියෙන data (claims) පාවිච්චි කරලා fine-grained authorization කරන්න. ඒ කියන්නේ, යම් user කෙනෙක්ට යම් API endpoint එකක් access කරන්න පුළුවන්ද බැරිද කියලා තීරණය කරන්න.

බහුලවම පාවිච්චි කරන ක්‍රම දෙකක් තමයි Scopes සහ Roles.

  • Scopes: මේවා තමයි user කෙනෙක්ට යම් ක්‍රියාවක් (action) කරන්න තියෙන අවසරයන්. උදාහරණයක් විදියට read, write, delete වගේ. මේවා Access Token එකේ scope claim එකේ තියෙන්න පුළුවන්.
  • Roles: මේවා තමයි user කෙනෙක්ට තියෙන භූමිකාවන් (roles). උදාහරණයක් විදියට admin, user, guest වගේ. මේවා roles හෝ groups වගේ custom claims වල තියෙන්න පුළුවන්.

Spring Security වලදී අපිට @PreAuthorize annotation එක පාවිච්චි කරලා මේ authorization rules implement කරන්න පුළුවන්. @PreAuthorize annotation එක Spring Expression Language (SpEL) support කරනවා.


package com.example.resource.server.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyResourceController {

    @GetMapping("/hello")
    @PreAuthorize("hasAuthority('SCOPE_read')") // Requires 'read' scope
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "! You have 'read' scope.";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasAuthority('SCOPE_admin')") // Requires 'admin' scope (or 'admin' role, depends on your AS mapping)
    public String adminOnly(Authentication authentication) {
        return "Welcome, Admin " + authentication.getName() + "! Only for admins.";
    }

    @GetMapping("/write")
    @PreAuthorize("hasAuthority('SCOPE_write')") // Requires 'write' scope
    public String writeData(Authentication authentication) {
        return "You can write data, " + authentication.getName() + "!";
    }
}

මේ උදාහරණයේදී:

  • /hello endpoint එකට access කරන්න read scope එක අනිවාර්යයි.
  • /admin endpoint එකට access කරන්න admin scope එක අනිවාර්යයි.
  • /write endpoint එකට access කරන්න write scope එක අනිවාර්යයි.

සැලකිය යුතුයි: Authorization Server එකෙන් Access Token එකට scopes add කරන විදිය ගැන ඔයාලට දැනුමක් තියෙන්න ඕනේ. Keycloak වගේ Authorization Server එකක් පාවිච්චි කරනවා නම්, Client Scope Mapper වලින් Scopes Token එකට add කරන්න පුළුවන්.

Practical Exercise: API එකක් ආරක්ෂා කරමු

හරි, දැන් අපි හැමදේම set කරගත්තා. දැන් බලමු මේක ඇත්තටම වැඩද කියලා. මේකට ඔයාලට Authorization Server එකක් අවශ්‍යයි. Keycloak, Okta, Auth0 වගේ ඒවා පාවිච්චි කරන්න පුළුවන්. මේ blog post එකේදී මම හිතන්නේ ඔයාලා Keycloak local machine එකේ run කරගෙන ඉන්නවා කියලා.

Keycloak Setup (කෙටියෙන්):

  1. Keycloak download කරගෙන run කරන්න. (උදා: Docker වලින් docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:23.0.0 start-dev)
  2. Admin console එකට login වෙලා අලුත් Realm එකක් හදාගන්න. (උදා: my-realm)
  3. Client එකක් හදන්න. (උදා: my-client, Access Type: confidential, Standard Flow Enabled: ON).
  4. User කෙනෙක් හදන්න. (උදා: username: testuser, password: password)
  5. Client Scopes හදලා, ඒවා client එකට assign කරන්න. (උදා: read, write, admin scopes) - මේවා Token එකට එන විදියට Client Mappers add කරන්න.
  6. application.yml එකේ issuer-uri එක ඔයාගේ Keycloak Realm එකට හරියන්න update කරන්න. (උදා: http://localhost:8080/realms/my-realm).

මේ Keycloak setup එක ගැන වැඩි විස්තර ඕනේ නම් වෙනම blog post එකක් කරන්න පුළුවන්. फिलहाल, අපි හිතමු ඔයාලාට valid access token එකක් generate කරගන්න පුළුවන් කියලා.

API Controller එක

අපි කලින් හදපු MyResourceController.java class එක පාවිච්චි කරමු. අපේ Resource Server එක 8081 port එකේ run වෙනවා.


// com.example.resource.server.controller.MyResourceController.java
// (Code as shown previously)

API එක Test කරමු

දැන් ඔයාලගේ Spring Boot application එක start කරන්න. (mvn spring-boot:run)

1. Access Token එකක් ලබාගැනීම

Authorization Server එකෙන් access token එකක් ලබාගන්න ඕනේ. Keycloak වලින් නම් මේ වගේ Postman request එකක් පාවිච්චි කරන්න පුළුවන් (or cURL):


POST http://localhost:8080/realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

client_id=my-client
client_secret=YOUR_CLIENT_SECRET # Keycloak client settings වල credentials tab එකේ තියෙනවා
grant_type=password
username=testuser
password=password
scope=openid profile email read write admin # Requesting all scopes

මේ request එකෙන් ඔයාලට access_token එකක් ලැබෙයි. මේ token එක copy කරගන්න.

2. Protected Endpoints Test කිරීම

දැන් අපි Postman (හෝ cURL) වලින් අපේ Resource Server එකේ endpoints ටික test කරමු.

A. /hello endpoint එක (Requires SCOPE_read):


GET http://localhost:8081/hello
Authorization: Bearer YOUR_ACCESS_TOKEN

ඔයාලගේ access token එකට read scope එක තියෙනවා නම්, Response එක මේ වගේ වෙයි:


"Hello, testuser! You have 'read' scope."

read scope එක නැත්නම් (හෝ token එක වැරදි නම්), ඔයාලට 403 Forbidden හෝ 401 Unauthorized error එකක් ලැබෙයි. (401 Unauthorized for invalid token, 403 Forbidden for insufficient scope).

B. /admin endpoint එක (Requires SCOPE_admin):


GET http://localhost:8081/admin
Authorization: Bearer YOUR_ACCESS_TOKEN

ඔයාලගේ access token එකට admin scope එක තියෙනවා නම්, Response එක මේ වගේ වෙයි:


"Welcome, Admin testuser! Only for admins."

admin scope එක නැත්නම්, 403 Forbidden error එකක් ලැබෙයි.

C. /write endpoint එක (Requires SCOPE_write):


GET http://localhost:8081/write
Authorization: Bearer YOUR_ACCESS_TOKEN

ඔයාලගේ access token එකට write scope එක තියෙනවා නම්, Response එක මේ වගේ වෙයි:


"You can write data, testuser!"

write scope එක නැත්නම්, 403 Forbidden error එකක් ලැබෙයි.

මේ test කිරීම් වලින් ඔයාලට පැහැදිලි වෙයි, Access Token එක වලංගු නැත්නම් (401) සහ අවශ්‍ය permissions (scopes) නැත්නම් (403) අපේ API එක access කරන්න දෙන්නේ නැහැ කියලා. මේක තමයි අපි බලාපොරොත්තු වුණු ආරක්ෂාව.

දැනටත් වඩා හොදට API ආරක්ෂා කරමු: Best Practices සහ ඊළඟ පියවර

දැන් අපි Spring Boot API එකක් OAuth2 Resource Server එකක් විදියට setup කරගෙන ආරක්ෂා කරන මූලික ක්‍රමවේදය ඉගෙන ගත්තා. හැබැයි production environment එකකදී තව ගොඩක් දේවල් ගැන හිතන්න වෙනවා. මෙන්න ඒ වගේ වැදගත් කරුණු කිහිපයක්:

  • Input Validation: API එකට එන හැම request එකකම data validate කරන්න. SQL injection, XSS වගේ attacks වලින් ආරක්ෂා වෙන්න මේක ගොඩක් වැදගත්.
  • Rate Limiting: එක user කෙනෙක්ට හෝ IP address එකකට යම් කාල සීමාවක් තුළ කරන්න පුළුවන් requests ප්‍රමාණය සීමා කරන්න. මේක DDoS attacks වලින් ආරක්ෂා වෙන්න උදව් වෙනවා.
  • Logging and Monitoring: API එකේ security-related events (උදා: failed login attempts, unauthorized access attempts) හොඳට log කරලා, ඒ logs monitor කරන්න. ඉක්මනටම security incidents detect කරන්න මේක වැදගත්.
  • Role-Based Access Control (RBAC) / Attribute-Based Access Control (ABAC): Scopes වලට අමතරව Complex authorization rules implement කරන්න මේ patterns පාවිච්චි කරන්න පුළුවන්. Spring Security වලට මේවා integrate කරන්න පුළුවන්.
  • HTTPS (SSL/TLS): හැමවිටම API calls HTTPS හරහා යවන්න. මේකෙන් data encryption වෙනවා, Man-in-the-Middle attacks වලින් ආරක්ෂා වෙනවා. Production environment එකකදී HTTP API එකක් කියන්නේ ලොකු අවදානමක්.
  • Error Handling: Security related errors වලදී හැමවිටම generic error messages දෙන්න. Detailed error messages වලින් attacker කෙනෙක්ට system එක ගැන විස්තර දැනගන්න පුළුවන්.
  • Secrets Management: Client secrets, API keys වගේ sensitive information hardcode කරන්නේ නැතුව, HashiCorp Vault, AWS Secrets Manager වගේ solutions පාවිච්චි කරන්න.
  • Security Scans: Continuous Integration/Continuous Delivery (CI/CD) pipeline එකට security scans (SAST, DAST) integrate කරන්න.

මේවා තමයි ඔයාලගේ API එක තවත් ආරක්ෂිත කරගන්න අමතරව හිතන්න ඕනේ දේවල්. Security කියන්නේ continuous process එකක් මිසක් එක පාරක් කරලා ඉවර කරන දෙයක් නෙවෙයි.

ඉතින් මොකද හිතන්නේ?

මේ blog post එකෙන් ඔයාලට Spring Boot application එකක් OAuth2 Resource Server එකක් විදියට protect කරගන්න පුළුවන් විදිය ගැන හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා මම හිතනවා. ඒ වගේම, API Security කියන එකේ වැදගත්කම ගැනත් දැන් කට්ටියට තේරෙනවා ඇති.

දැන් ඔයාලට පුළුවන් මේ concepts පාවිච්චි කරලා ඔයාලගේම Spring Boot API ටික ආරක්ෂා කරගන්න. පොඩ්ඩක් මේ code ටික try කරලා බලන්න. Keycloak වගේ Authorization Server එකක් එක්ක practice කරන්න. පොඩි පොඩි changes කරලා, different scopes try කරලා බලන්න. එතකොට concept එක හොඳටම ඔලුවට යයි.

අපි මේ ගැන තව ගැඹුරට කතා කරමුද? එහෙමත් නැත්නම් ඔයාලට වෙනත් Spring Boot Security topics ගැන දැනගන්න ඕනෙද? පහලින් comment එකක් දාලා ඔයාලගේ අදහස් සහ ප්‍රශ්න අහන්න. මේ blog post එකට කැමති නම් යාළුවන් එක්ක share කරන්නත් අමතක කරන්න එපා!

තවත් අලුත් දෙයක් අරගෙන ඉක්මනටම හම්බවෙමු! ජය වේවා!