Skip to content

Commit

Permalink
Add messages-service module
Browse files Browse the repository at this point in the history
  • Loading branch information
sivaprasadreddy committed Sep 29, 2023
1 parent a6e5b00 commit cc980df
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 0 deletions.
33 changes: 33 additions & 0 deletions messages-service/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
58 changes: 58 additions & 0 deletions messages-service/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sivalabs</groupId>
<artifactId>messages-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>messages-service</name>
<description>messages-service</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sivalabs.messages;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MessagesServiceApplication {

public static void main(String[] args) {
SpringApplication.run(MessagesServiceApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.sivalabs.messages.api;

import com.sivalabs.messages.domain.Message;
import com.sivalabs.messages.domain.MessageRepository;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/messages")
class MessageController {
private static final Logger log = LoggerFactory.getLogger(MessageController.class);

private final MessageRepository messageRepository;

MessageController(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}

@GetMapping
List<Message> getMessages() {
return messageRepository.getMessages();
}

@PostMapping
Message createMessage(@RequestBody @Valid Message message) {
return messageRepository.createMessage(message);
}

@PostMapping("/archive")
Map<String,String> archiveMessages() {
log.info("Archiving all messages");
return Map.of("status", "success");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.sivalabs.messages.api;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
class UserInfoController {

@GetMapping("/api/me")
Map<String, Object> currentUserDetails() {
return getLoginUserDetails();
}

Map<String, Object> getLoginUserDetails() {
Map<String, Object> map = new HashMap<>();
JwtAuthenticationToken authentication =
(JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt) authentication.getPrincipal();

map.put("username", jwt.getClaimAsString("preferred_username"));
map.put("email", jwt.getClaimAsString("email"));
map.put("name", jwt.getClaimAsString("name"));
map.put("token", jwt.getTokenValue());
map.put("authorities", authentication.getAuthorities());
map.put("roles", getRoles(jwt));

return map;
}

List<String> getRoles(Jwt jwt) {
Map<String,Object> realm_access = (Map<String, Object>) jwt.getClaims().get("realm_access");
if(realm_access != null && !realm_access.isEmpty()) {
return (List<String>) realm_access.get("roles");
}
return List.of();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.sivalabs.messages.config;

import org.springframework.core.convert.converter.Converter;
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.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;

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

class KeycloakJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final Converter<Jwt, Collection<GrantedAuthority>> delegate = new JwtGrantedAuthoritiesConverter();

@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
List<GrantedAuthority> authorityList = extractRoles(jwt);
Collection<GrantedAuthority> authorities = delegate.convert(jwt);
if (authorities != null) {
authorityList.addAll(authorities);
}
return new JwtAuthenticationToken(jwt, authorityList);
}

private List<GrantedAuthority> extractRoles(Jwt jwt) {
Map<String,Object> realm_access = (Map<String, Object>) jwt.getClaims().get("realm_access");
if(realm_access == null || realm_access.isEmpty()) {
return List.of();
}
List<String> roles = (List<String>) realm_access.get("roles");
if (roles == null || roles.isEmpty()) {
roles = List.of("ROLE_USER");
}
return roles.stream()
.filter(role -> role.startsWith("ROLE_"))
.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.sivalabs.messages.config;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(c ->
c
.requestMatchers(HttpMethod.GET, "/api/messages").permitAll()
.requestMatchers(HttpMethod.POST, "/api/messages/archive").hasAnyRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.cors(CorsConfigurer::disable)
.csrf(CsrfConfigurer::disable)
.oauth2ResourceServer(oauth2 ->
//oauth2.jwt(Customizer.withDefaults())
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(new KeycloakJwtAuthenticationConverter()))
);
return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.sivalabs.messages.domain;

import jakarta.validation.constraints.NotEmpty;

import java.time.Instant;

public class Message {
private Long id;
@NotEmpty
private String content;
@NotEmpty
private String createdBy;
private Instant createdAt;

public Message() {
}

public Message(Long id, String content, String createdBy, Instant createdAt) {
this.id = id;
this.content = content;
this.createdBy = createdBy;
this.createdAt = createdAt;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getCreatedBy() {
return createdBy;
}

public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}

public Instant getCreatedAt() {
return createdAt;
}

public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.sivalabs.messages.domain;

import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Repository;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

@Repository
public class MessageRepository {
private static final AtomicLong ID = new AtomicLong(0L);
private static final List<Message> MESSAGES = new ArrayList<>();

@PostConstruct
void init() {
getDefaultMessages().forEach( p -> {
p.setId(ID.incrementAndGet());
MESSAGES.add(p);
});
}

public List<Message> getMessages() {
return MESSAGES;
}

public Message createMessage(Message message) {
message.setId(ID.incrementAndGet());
message.setCreatedAt(Instant.now());
MESSAGES.add(message);
return message;
}

private List<Message> getDefaultMessages() {
List<Message> messages = new ArrayList<>();
messages.add(new Message(null, "Test Message 1", "admin", Instant.now()));
messages.add(new Message(null, "Test Message 2", "admin", Instant.now()));
return messages;
}
}
4 changes: 4 additions & 0 deletions messages-service/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spring.application.name=messages-service
server.port=8181
OAUTH_SERVER=http://localhost:9191/realms/sivalabs
spring.security.oauth2.resourceserver.jwt.issuer-uri=${OAUTH_SERVER}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sivalabs.messages;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MessagesServiceApplicationTests {

@Test
void contextLoads() {
}

}
Loading

0 comments on commit cc980df

Please sign in to comment.