From ead0bccfe7f42c257700b013d378ff332ab81618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=A4ume?= Date: Fri, 27 Sep 2024 12:17:17 +0200 Subject: [PATCH] PMT-14: Implement JWT Auth --- .../java/de/hmmh/pmt/auth/AuthConfig.java | 57 +++++++++++++++ src/main/java/de/hmmh/pmt/auth/JWT.java | 69 +++++++++++++++++++ src/main/resources/api.yml | 7 ++ 3 files changed, 133 insertions(+) create mode 100644 src/main/java/de/hmmh/pmt/auth/AuthConfig.java create mode 100644 src/main/java/de/hmmh/pmt/auth/JWT.java diff --git a/src/main/java/de/hmmh/pmt/auth/AuthConfig.java b/src/main/java/de/hmmh/pmt/auth/AuthConfig.java new file mode 100644 index 0000000..268a9ec --- /dev/null +++ b/src/main/java/de/hmmh/pmt/auth/AuthConfig.java @@ -0,0 +1,57 @@ +package de.hmmh.pmt.auth; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.core.session.SessionRegistry; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.session.HttpSessionEventPublisher; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class AuthConfig { + + private final JWT jwt; + + AuthConfig(JWT jwt) { + this.jwt = jwt; + } + + @Bean + public SessionRegistry sessionRegistry() { + return new SessionRegistryImpl(); + } + + @Bean + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new RegisterSessionAuthenticationStrategy(sessionRegistry()); + } + + @Bean + public HttpSessionEventPublisher httpSessionEventPublisher() { + return new HttpSessionEventPublisher(); + } + + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + .anyRequest() + .authenticated() + ) + .oauth2ResourceServer(resourceServer -> resourceServer + .jwt(jwt -> jwt + .jwtAuthenticationConverter(this.jwt.jwtAuthenticationConverter()) + ) + ); + return http.build(); + } +} diff --git a/src/main/java/de/hmmh/pmt/auth/JWT.java b/src/main/java/de/hmmh/pmt/auth/JWT.java new file mode 100644 index 0000000..5360d62 --- /dev/null +++ b/src/main/java/de/hmmh/pmt/auth/JWT.java @@ -0,0 +1,69 @@ +package de.hmmh.pmt.auth; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Component +public class JWT implements LogoutHandler { + private static final String REALM_ACCESS_CLAIM = "realm_access"; + private static final String ROLES_CLAIM = "roles"; + private static final String ROLE_PREFIX = "ROLE_"; + private static final String OIDC_LOGOUT_ROUTE = "/protocol/openid-connect/logout"; + private static final String OIDC_TOKEN_HINT_QUERY_PARAMETER = "id_token_hin"; + + private final RestTemplate template = new RestTemplate(); + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + OidcUser user = (OidcUser) authentication.getPrincipal(); + String endSessionEndpoint = user.getIssuer() + OIDC_LOGOUT_ROUTE; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam(OIDC_TOKEN_HINT_QUERY_PARAMETER, user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = template.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + System.out.println("Logged out successfully"); + } else { + System.out.println("Failed to logout"); + } + } + + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { + List grantedAuthorities = new ArrayList<>(); + + Map realmAccess = jwt.getClaim(REALM_ACCESS_CLAIM); + if (realmAccess == null || !realmAccess.containsKey(ROLES_CLAIM)) { + return grantedAuthorities; + } + + Object rolesClaim = realmAccess.get(ROLES_CLAIM); + if (!(rolesClaim instanceof List)) { + return grantedAuthorities; + } + for (Object role : (List) rolesClaim) { + assert role instanceof String; + grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + role)); + } + + return grantedAuthorities; + }); + return jwtAuthenticationConverter; + } +} diff --git a/src/main/resources/api.yml b/src/main/resources/api.yml index 7e46e48..06e99b1 100644 --- a/src/main/resources/api.yml +++ b/src/main/resources/api.yml @@ -5,8 +5,15 @@ info: version: 1.0.0 servers: - url: /api/v1 +security: + - JWTAuth: [] components: + securitySchemes: + JWTAuth: + type: http + scheme: bearer + bearerFormat: JWT schemas: HelloOut: description: "A Test Schema"