1.1.55.1. fejezet, SpringBoot3 JWT integráció

JWT alapú konfiguráció

SecurityConfig

package hu.infokristaly.keycloakauthenticatoin.security;
 
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.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
@EnableMethodSecurity
public class SecurityConfig {
    @Autowired
    private JwtAuthConverter jwtAuthenticationConverter;
 
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(t->t.disable());
        http.authorizeRequests(authorize->{
            authorize
                .requestMatchers(HttpMethod.GET,"/client/public/list").permitAll() // GET kérés azonosító nélkül elérhető
                .anyRequest().authenticated();
        });
        http.oauth2ResourceServer(t->{
            t.jwt(configure -> configure.jwtAuthenticationConverter(jwtAuthenticationConverter));
        });
        http.sessionManagement(t->{
            t.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        });
        return http.build();
    }
}

Egyedi JwtAuthConverter

package hu.infokristaly.keycloakauthenticatoin.security;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.core.convert.converter.Converter;
 
import java.util.*;
 
@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {
 
    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        Collection<GrantedAuthority> roles = extractAuthorities(jwt);
        return new JwtAuthenticationToken(jwt, roles);
    }
 
    private Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
        if(jwt.getClaims().containsKey("resource_access")) {
            Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
            Map<String, Object> client = (Map<String, Object>) resourceAccess.get("forras-admin");
            ObjectMapper mapper = new ObjectMapper();
            List<String> keycloakRoles = mapper.convertValue(client.get("roles"), new TypeReference<List<String>>(){});
            List<GrantedAuthority> roles = new ArrayList<>();
 
            for(String keycloakRole : keycloakRoles) {
                roles.add(new SimpleGrantedAuthority(keycloakRole.toUpperCase(Locale.ROOT)));
            }
            return roles;
        }
        return new ArrayList<>();
    }
}

Beépített JwtAuthenticationConverter konfigurálása

Ehhez Client scope-ot kell felvenni (Client scopes/ create (name = roles), [roles client scope] / mappers / Configure new mapper / User Client Role (name = roles, Token Claim Name = roles))

package hu.infokristaly.keycloakauthenticatoin.security;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
 
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(t->t.disable());
        http.authorizeRequests(authorize->{
            authorize
                    .anyRequest().authenticated();
        });
        http.oauth2ResourceServer(t->{
            t.jwt(Customizer.withDefaults());
        });
        http.sessionManagement(t->{
            t.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        });
        return http.build();
    }
 
    @Bean
    public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setDefaultRolePrefix("");
        return expressionHandler;
    }
 
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter c = new JwtAuthenticationConverter();
        JwtGrantedAuthoritiesConverter cv = new JwtGrantedAuthoritiesConverter();
        cv.setAuthorityPrefix("");
        cv.setAuthoritiesClaimName("roles");
        c.setJwtGrantedAuthoritiesConverter(cv);
        return c;
    }
}

MethodSecurity

package hu.infokristaly.keycloakauthenticatoin.controller;
 
import hu.infokristaly.keycloakauthenticatoin.entity.Doctor;
import hu.infokristaly.keycloakauthenticatoin.repository.DoctorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@RestController
@RequestMapping("/doctor")
public class DoctorController  {
    @Autowired
    DoctorRepository doctorRepository;
 
    @GetMapping("/list")
    @PreAuthorize("hasRole('USER') or hasRole('MANAGER')")
    public List<Doctor> getAllDoctors() {
        return doctorRepository.findAll();
    }
}

UserController

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping(path = "/info")
    public HashMap index() {
 
        Jwt user = (Jwt)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 
        return new HashMap(){{
            put("hello", user.getClaimAsStringList("name"));
            put("your email is", user.getClaimAsStringList("email"));
        }};
    }
}

application.properties beállítások

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://server.me.local:8080/realms/infokristaly