Genom att använda denna starter så möjliggörs stöd för mer finkornig behörighetshantering av API-resurser när sådant behov uppstår. Funktionaliteten bygger på att klienten skapar ett signerat Java Web Token (JWT) innehållandes den användarinformation som krävs för att kontrollera användarens behörighet. Informationen skickas med i anropet till tjänsten, där den sedan används för att kontrollera behörighet till i första hand resurserna i klassen som specificerar API-resurserna.

För att få tillgång till modulens funktionalitet, lägg till följande i tjänstens pom.xml:

<dependency>
	<groupId>se.sundsvall.dept44</groupId>
	<artifactId>dept44-starter-authorization</artifactId>
</dependency>

Lägg även till annoteringen @EnableJwtAuthorization i den klass som innehåller annoteringen @ServiceApplication.

Inställningar i tjänsten

När auktorisering via JWT är påslagen så behövs följande inställningar hanteras i tjänstens property-fil

Struktur för JWT

Klienter som anropar tjänsten måste skicka med ett Java Web Token (JWT) med behörighetsinformation till tjänsten i de fall de anropar behörighetsskyddade resurser.

En JWT innehåller tre delar. En header med information kring den algoritm och token-typ som JWT:n använder. En payload innehållande behörighetsinformation för anropande användare. Slutligen en signatur för JWT:n som används för validering. Varje del Base-64-encodas. Observera att JWT:n inte är krypterad utan enbart signerad. Därför bör JWT:n inte innehålla känslig information.

Header-struktur

{
  "alg": "<information om algoritm för signering>",
  "typ": "<typ av token>"
}

Payload-struktur

Delen som innehåller användarinformation är uppbyggd enligt följande struktur:

{
  "sub": "<id for subject, for example user-id>",
  "exp": "<epoch timestamp när token blir för gammalt>",
  "roles": {
    "<role_name>": <godtycklig json-struktur>
  }
}

Signatur-struktur

Har ingen struktur, men byggs upp utifrån den algoritm som definierats i huvudet, den information som finns i payloaden samt det värde som valts att användas mellan klient och tjänst. Exempel på hur signaturen byggs upp:

HMACSHA512(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  <client-server-agreed-secret>
)

Exempel

Se tex https://jwt.io/ för mer information kring hur man skapar JWT, samt vilka olika algoritmer som finns att välja för signaturen.

Behörighetskontroll via annotering

För att styra åtkomst till en API-resurs (eller annan metod i tjänsten) så används de auktoriserings-annoteringar som ingår i Springs ramverk. Dessa är

Det går att använda inbyggda funktioner i Spring för att avgöra behörigheter, eller att skriva en egen implementation som gör kontrollen, där det senare kanske är det mest vanliga scenariot. Annoteringarna ovan använder sig av SPeL (Spring expression language) för att få åtkomst till i Spring security fördefinierade objekt, samt andra attribut (tex metodparametrar) som krävs för att avgöra access.

Generisk behörighetsmodell

Den JWT som lästs ut av Dept-44 ligger lagrad som ett objekt av klass GenericGrantedAuthority i Springs security-context. Klassen har ett antal hjälpmetoder för att läsa och verifiera behörighet för en användare. Följande metoder finns:

I de fall mer komplex logik krävs för att avgöra behörighet så finns möjlighet att läsa ut json-strukturen kopplad till rollen som ett DocumentContext-objekt via metoden getAccesses().

Steg för steg guide för att skriva och använda en implementation för auktorisering

Skapa ny klass som annoteras med @Component och en metod som returneras boolean avseende ifall användare är behörig eller ej, inklusive de parametrar som behövs för att metoden ska kunna avgöra utfallet. Nedan följer exempel på implementation som verifierar att användaren har en roll med namn “write“, vilken har en lista som innehåller den den kategori som skickas in (se struktur på JWT-payload i stycket ovan):

@Component
public class AccessAuthorizer {
  public boolean authorize(Authentication authentication, Category category) {
    final AtomicBoolean hasAccess = new AtomicBoolean(false); 

    authentication.getAuthorities().forEach(auth -> {
      GenericGrantedAuthority generic = (GenericGrantedAuthority)auth;
      hasAccess.compareAndExchange(false, 
        generic.hasAuthority("write", 
        String.format("$.[?(@ ==\"%s\")]", category.name())));
    });

    return hasAccess.get();
  }
}

Lägg till någon av ovanstående annoteringar för den resurs som ska behörighetsskyddas. I detta exempel används @PreAuthorize.

@GetMapping(path = "/{category}", produces = { APPLICATION_JSON_VALUE })
@Operation(summary = "Get agreements by category")
@ApiResponse(responseCode = "200", description = "Successful operation")
@ApiResponse(responseCode = "400", description = "Bad request")
@ApiResponse(responseCode = "401", description = "Unauthorized")
@ApiResponse(responseCode = "404", description = "Not found")
@ApiResponse(responseCode = "500", description = "Internal Server error")
@ApiResponse(responseCode = "502", description = "Bad Gateway")
@PreAuthorize("@accessAuthorizer.authorize(authentication, #category)")
public ResponseEntity<AgreementResponse> getAgreementsByCategory(
  @Parameter(name = "category") @PathVariable(name = "category") Category category) {
    ...
}

Med hjälp av SPeL så anropas implementationen för auktorisering, där de argument som krävs av metoden skickas med. För att använda en böna annoterad med @Component så används syntaxen @klassnamn (för att tala om vilken implementation som ska användas för att säkerställa access). För att skicka med argument från annoterad metod så används syntax #variabelnamn. Det finns även ett antal fördefinierade namn att använda, tex authentication som innehåller Spring securitys Authentication-objekt. Se Springs dokumentation kring säkerhet samt SPeL för ytterligare information kring vilka möjligheter som finns att tillgå.

Ifall användaren har access enligt implementationen så kommer exekveringen att fortsätta och metoden att anropas. Ifall användaren saknar access så kommer ramverket att returnera http-kod 401 med information om att behörighet saknas.

Scenarion då ramverket returnerar ‘401: Unauthorized’