Skip to content

Commit

Permalink
Adding jwt authorization using keycloak
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammed-ezzedine committed Dec 28, 2023
1 parent 3c3949d commit eab7151
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 4 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ repositories {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0")

compileOnly("org.projectlombok:lombok")
Expand Down
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3.9"
services:
mongo:
image: mongo:latest
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin
ports:
- "27017:27017"
keycloak:
image: keycloak/keycloak:23.0
ports:
- "8081:8080"
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
command: start-dev
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import me.ezzedine.mohammed.personalspace.category.core.CategoryNotFoundException;
import me.ezzedine.mohammed.personalspace.util.pagination.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RequestMapping("articles")
Expand All @@ -16,8 +17,10 @@ public interface ArticleApi {
ResponseEntity<ArticleApiModel> getArticle(@PathVariable String id) throws ArticleNotFoundException;

@PostMapping
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<ArticleCreationApiResponse> create(@RequestBody ArticleCreationApiRequest request) throws CategoryNotFoundException;

@PutMapping("{id}")
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<Void> editArticle(@PathVariable String id, @RequestBody ArticleUpdateApiRequest request) throws CategoryNotFoundException, ArticleNotFoundException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import me.ezzedine.mohammed.personalspace.article.core.highlight.ArticleAlreadyHighlightedException;
import me.ezzedine.mohammed.personalspace.article.core.highlight.ArticleWasNotHighlightedException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -13,17 +14,21 @@
public interface HighlightedArticlesApi {

@PostMapping("{id}")
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<Void> addArticleToHighlights(@PathVariable String id) throws ArticleNotFoundException, ArticleAlreadyHighlightedException;

@DeleteMapping("{id}")
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<Void> removeArticleFromHighlights(@PathVariable String id) throws ArticleNotFoundException, ArticleWasNotHighlightedException;

@PutMapping
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<Void> updateHighlightedArticles(@RequestBody HighlightedArticlesUpdateRequest request);

@GetMapping
ResponseEntity<List<ArticleSummaryApiModel>> getHighlightedArticles();

@GetMapping("summary")
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<List<ArticleHighlightApiModel>> getHighlightedArticlesSummary();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import me.ezzedine.mohammed.personalspace.category.core.CategoryValidationViolationException;
import me.ezzedine.mohammed.personalspace.category.core.deletion.CategoryDeletionRejectedException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -19,11 +20,14 @@ public interface CategoryApi {
ResponseEntity<CategorySummaryApiModel> fetchCategoryDetails(@PathVariable String id) throws CategoryNotFoundException;

@PostMapping
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<CategoryCreationResultApiModel> create(@RequestBody CategoryCreationRequest request) throws CategoryValidationViolationException, CategoryIdAlreadyExistsException;

@PutMapping("orders")
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<Void> updateCategoriesOrders(@RequestBody UpdateCategoriesOrdersApiRequest request);

@DeleteMapping("{id}")
@PreAuthorize("hasAuthority('admin')")
ResponseEntity<Void> delete(@PathVariable String id) throws CategoryNotFoundException, CategoryDeletionRejectedException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package me.ezzedine.mohammed.personalspace.config.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
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.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

return http.build();
}

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter = jwt -> {
Map<String, Object> realmAccess = jwt.getClaim("realm_access");

List<String> roles = (List<String>) realmAccess.get("roles");

return roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
};

JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();

jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);

return jwtAuthenticationConverter;
}
}
15 changes: 15 additions & 0 deletions src/main/resources/application-production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ spring:
username: ${MONGO_DB_USERNAME:admin}
password: ${MONGO_DB_PASSWORD:admin}
authentication-database: ${MONGO_DB_AUTHENTICATION_DB:admin}
security:
oauth2:
client:
registration:
keycloak:
client-id: ${SECURITY_CLIENT_ID}
authorization-grant-type: ${SECURITY_GRANT_TYPE}
scope: ${SECURITY_SCOPE}
provider:
keycloak:
issuer-uri: ${SECURITY_ISSUER_ID}
user-name-attribute: ${SECURITY_USERNAME_ATTRIBUTE}
resourceserver:
jwt:
issuer-uri: ${SECURITY_ISSUER_ID}

server:
forward-headers-strategy: native
17 changes: 16 additions & 1 deletion src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,19 @@ spring:
port: ${MONGO_DB_PORT:27017}
username: ${MONGO_DB_USERNAME:admin}
password: ${MONGO_DB_PASSWORD:admin}
authentication-database: ${MONGO_DB_AUTHENTICATION_DB:admin}
authentication-database: ${MONGO_DB_AUTHENTICATION_DB:admin}
security:
oauth2:
client:
registration:
keycloak:
client-id: ${SECURITY_CLIENT_ID:web}
authorization-grant-type: ${SECURITY_GRANT_TYPE:authorization_code}
scope: ${SECURITY_SCOPE:profile}
provider:
keycloak:
issuer-uri: ${SECURITY_ISSUER_ID:http://localhost:8081/realms/testing}
user-name-attribute: ${SECURITY_USERNAME_ATTRIBUTE:preferred_username}
resourceserver:
jwt:
issuer-uri: ${SECURITY_ISSUER_ID:http://localhost:8081/realms/testing}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
ArticleNotFoundAdvice.class
})
@EnableWebMvc
@AutoConfigureMockMvc
@AutoConfigureMockMvc(addFilters = false)
class ArticleControllerIntegrationTest {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
ArticleNotFoundAdvice.class
})
@EnableWebMvc
@AutoConfigureMockMvc
@AutoConfigureMockMvc(addFilters = false)
class HighlightedArticlesControllerIntegrationTest {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
CategoryDeletionRejectedAdvice.class
})
@EnableWebMvc
@AutoConfigureMockMvc
@AutoConfigureMockMvc(addFilters = false)
class CategoryControllerIntegrationTest {

@Autowired
Expand Down

0 comments on commit eab7151

Please sign in to comment.