Skip to content

Commit

Permalink
Add Spring Boot Security support with JWT + Basic Auth
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalgrimaud committed Dec 1, 2021
1 parent 2c4b1d0 commit 3033d27
Show file tree
Hide file tree
Showing 34 changed files with 1,859 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tech.jhipster.light.generator.server.springboot.mvc.security.jwt.application;

import org.springframework.stereotype.Service;
import tech.jhipster.light.generator.project.domain.Project;
import tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain.JwtSecurityService;

@Service
public class JwtSecurityApplicationService {

private final JwtSecurityService jwtSecurityService;

public JwtSecurityApplicationService(JwtSecurityService jwtSecurityService) {
this.jwtSecurityService = jwtSecurityService;
}

public void initBasicAuth(Project project) {
jwtSecurityService.initBasicAuth(project);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain;

import java.util.HashMap;
import java.util.Map;
import tech.jhipster.light.generator.buildtool.generic.domain.Dependency;

public class JwtSecurity {

private static final String JJWT_VERSION = "0.11.2";
private static final String infrastructureConfig = "infrastructure/config";
private static final String infrastructureRest = "infrastructure/primary/rest";

public static final String annotationProcessorPatch = "jwt-security-annotation-processor.patch";
public static final String exceptionTranslatorPatch = "jwt-security-exception-translator.patch";

private JwtSecurity() {}

public static String jjwtVersion() {
return JJWT_VERSION;
}

public static Dependency springBootStarterSecurityDependency() {
return Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-security").build();
}

public static Dependency springBootConfigurationProcessor() {
return Dependency
.builder()
.groupId("org.springframework.boot")
.artifactId("spring-boot-configuration-processor")
.scope("provided")
.build();
}

public static Dependency jjwtApiDependency() {
return Dependency.builder().groupId("io.jsonwebtoken").artifactId("jjwt-api").version("\\${jjwt.version}").build();
}

public static Dependency jjwtImplDependency() {
return Dependency.builder().groupId("io.jsonwebtoken").artifactId("jjwt-impl").version("\\${jjwt.version}").scope("runtime").build();
}

public static Dependency jjwtJacksonDependency() {
return Dependency.builder().groupId("io.jsonwebtoken").artifactId("jjwt-jackson").version("\\${jjwt.version}").scope("runtime").build();
}

public static Dependency springSecurityTestDependency() {
return Dependency.builder().groupId("org.springframework.security").artifactId("spring-security-test").scope("test").build();
}

public static Map<String, String> jwtSecurityFiles() {
Map<String, String> map = new HashMap<>();

map.put("AuthoritiesConstants.java", "domain");

map.put("ApplicationSecurityDefaults.java", infrastructureConfig);
map.put("ApplicationSecurityProperties.java", infrastructureConfig);
map.put("CorsFilterConfiguration.java", infrastructureConfig);
map.put("CorsProperties.java", infrastructureConfig);
map.put("JWTConfigurer.java", infrastructureConfig);
map.put("JWTFilter.java", infrastructureConfig);
map.put("SecurityConfiguration.java", infrastructureConfig);
map.put("SecurityExceptionTranslator.java", infrastructureConfig);
map.put("TokenProvider.java", infrastructureConfig);

map.put("AuthenticationResource.java", infrastructureRest);
map.put("LoginDTO.java", infrastructureRest);

return map;
}

public static Map<String, String> jwtTestSecurityFiles() {
Map<String, String> map = new HashMap<>();

map.put("ApplicationSecurityPropertiesTest.java", infrastructureConfig);
map.put("CorsFilterConfigurationIT.java", infrastructureConfig);
map.put("JWTFilterTest.java", infrastructureConfig);
map.put("TokenProviderTest.java", infrastructureConfig);

map.put("AuthenticationResourceIT.java", infrastructureRest);
map.put("LoginDTOTest.java", infrastructureRest);

return map;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain;

import static tech.jhipster.light.common.domain.FileUtils.getPath;
import static tech.jhipster.light.generator.project.domain.Constants.MAIN_JAVA;
import static tech.jhipster.light.generator.project.domain.Constants.TEST_JAVA;
import static tech.jhipster.light.generator.project.domain.DefaultConfig.PACKAGE_NAME;
import static tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain.JwtSecurity.*;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.jgit.api.errors.GitAPIException;
import tech.jhipster.light.error.domain.GeneratorException;
import tech.jhipster.light.generator.buildtool.generic.domain.BuildToolService;
import tech.jhipster.light.generator.project.domain.Project;
import tech.jhipster.light.generator.project.domain.ProjectRepository;
import tech.jhipster.light.generator.project.infrastructure.secondary.GitUtils;
import tech.jhipster.light.generator.server.springboot.properties.domain.SpringBootPropertiesService;

public class JwtSecurityDomainService implements JwtSecurityService {

public static final String SOURCE = "server/springboot/mvc/security/jwt";
public static final String SECURITY_JWT_PATH = "security/jwt";

private final ProjectRepository projectRepository;
private final BuildToolService buildToolService;
private final SpringBootPropertiesService springBootPropertiesService;

public JwtSecurityDomainService(
ProjectRepository projectRepository,
BuildToolService buildToolService,
SpringBootPropertiesService springBootPropertiesService
) {
this.projectRepository = projectRepository;
this.buildToolService = buildToolService;
this.springBootPropertiesService = springBootPropertiesService;
}

@Override
public void initBasicAuth(Project project) {
projectRepository.gitInit(project);

addPropertyAndDependency(project);
addJavaFiles(project);
addProperties(project);
addGitPatch(project);
applyGitPatchAnnotationProcessor(project);
applyGitPatchExceptionTranslator(project);
}

private void applyGitPatchAnnotationProcessor(Project project) {
projectRepository.gitApplyPatch(project, getPath(project.getFolder(), ".jhipster", annotationProcessorPatch));
}

private void applyGitPatchExceptionTranslator(Project project) {
projectRepository.gitApplyPatch(project, getPath(project.getFolder(), ".jhipster", exceptionTranslatorPatch));
}

private void addGitPatch(Project project) {
project.addDefaultConfig(PACKAGE_NAME);
String packageNamePath = project.getPackageNamePath().orElse(getPath("com/mycompany/myapp"));
project.addConfig("packageNamePath", packageNamePath);

projectRepository.add(project, SOURCE, annotationProcessorPatch, ".jhipster");
projectRepository.template(project, SOURCE, exceptionTranslatorPatch, ".jhipster");
}

private void addPropertyAndDependency(Project project) {
buildToolService.addProperty(project, "jjwt", jjwtVersion());

buildToolService.addDependency(project, springBootStarterSecurityDependency());
buildToolService.addDependency(project, springBootConfigurationProcessor());
buildToolService.addDependency(project, jjwtApiDependency());
buildToolService.addDependency(project, jjwtImplDependency());
buildToolService.addDependency(project, jjwtJacksonDependency());

buildToolService.addDependency(project, springSecurityTestDependency());
}

private void addJavaFiles(Project project) {
project.addDefaultConfig(PACKAGE_NAME);

String sourceSrc = getPath(SOURCE, "src");
String sourceTest = getPath(SOURCE, "test");
String packageNamePath = project.getPackageNamePath().orElse(getPath("com/mycompany/myapp"));
String destinationSrc = getPath(MAIN_JAVA, packageNamePath, SECURITY_JWT_PATH);
String destinationTest = getPath(TEST_JAVA, packageNamePath, SECURITY_JWT_PATH);

jwtSecurityFiles()
.forEach((javaFile, destination) -> projectRepository.template(project, sourceSrc, javaFile, getPath(destinationSrc, destination)));

jwtTestSecurityFiles()
.forEach((javaFile, destination) -> projectRepository.template(project, sourceTest, javaFile, getPath(destinationTest, destination)));
}

private void addProperties(Project project) {
String baseName = project.getBaseName().orElse("jhipster");

jwtProperties(baseName)
.forEach((k, v) -> {
springBootPropertiesService.addProperties(project, k, v);
springBootPropertiesService.addPropertiesTest(project, k, v);
});
}

private Map<String, Object> jwtProperties(String baseName) {
Map<String, Object> result = new LinkedHashMap<>();
result.put("spring.security.user.name", "admin");
result.put("spring.security.user.password", "\\$2a\\$12\\$cRKS9ZURbdJIaRsKDTDUmOrH4.B.2rokv8rrkrQXr2IR2Hkna484O");
result.put(
"application.security.authentication.jwt.base64-secret",
"bXktc2VjcmV0LWtleS13aGljaC1zaG91bGQtYmUtY2hhbmdlZC1pbi1wcm9kdWN0aW9uLWFuZC1iZS1iYXNlNjQtZW5jb2RlZAo="
);
result.put("application.security.authentication.jwt.token-validity-in-seconds", "86400");
result.put("application.security.authentication.jwt.token-validity-in-seconds-for-remember-me", "2592000");
result.put("application.cors.allowed-origins", "http://localhost:8100,http://localhost:9000");
result.put("application.cors.allowed-methods", "*");
result.put("application.cors.allowed-headers", "*");
result.put(
"application.cors.exposed-headers",
"Authorization,Link,X-Total-Count,X-" + baseName + "-alert,X-" + baseName + "-error,X-" + baseName + "-params"
);
result.put("application.cors.allow-credentials", "true");
result.put("application.cors.max-age", "1800");
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain;

import tech.jhipster.light.generator.project.domain.Project;

public interface JwtSecurityService {
void initBasicAuth(Project project);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package tech.jhipster.light.generator.server.springboot.mvc.security.jwt.infrastructure.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tech.jhipster.light.generator.buildtool.generic.domain.BuildToolService;
import tech.jhipster.light.generator.project.domain.ProjectRepository;
import tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain.JwtSecurityDomainService;
import tech.jhipster.light.generator.server.springboot.mvc.security.jwt.domain.JwtSecurityService;
import tech.jhipster.light.generator.server.springboot.properties.domain.SpringBootPropertiesService;

@Configuration
public class JwtSecurityBeanConfiguration {

private final ProjectRepository projectRepository;
private final BuildToolService buildToolService;
private final SpringBootPropertiesService springBootPropertiesService;

public JwtSecurityBeanConfiguration(
ProjectRepository projectRepository,
BuildToolService buildToolService,
SpringBootPropertiesService springBootPropertiesService
) {
this.projectRepository = projectRepository;
this.buildToolService = buildToolService;
this.springBootPropertiesService = springBootPropertiesService;
}

@Bean
public JwtSecurityService jwtSecurityService() {
return new JwtSecurityDomainService(projectRepository, buildToolService, springBootPropertiesService);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package tech.jhipster.light.generator.server.springboot.mvc.security.jwt.infrastructure.rest;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import tech.jhipster.light.generator.project.domain.Project;
import tech.jhipster.light.generator.project.infrastructure.primary.dto.ProjectDTO;
import tech.jhipster.light.generator.server.springboot.mvc.security.jwt.application.JwtSecurityApplicationService;

@RestController
@RequestMapping("/api/servers/spring-boot/mvc/security")
@Tag(name = "Spring Boot - MVC")
class JwtSecurityResource {

private final JwtSecurityApplicationService jwtSecurityApplicationService;

public JwtSecurityResource(JwtSecurityApplicationService jwtSecurityApplicationService) {
this.jwtSecurityApplicationService = jwtSecurityApplicationService;
}

@Operation(summary = "Add Spring Security JWT with Basic Auth")
@ApiResponses({ @ApiResponse(responseCode = "500", description = "An error occurred while adding Spring Security JWT with Basic Auth") })
@PostMapping("/jwt")
public void initBasicAuth(@RequestBody ProjectDTO projectDTO) {
Project project = ProjectDTO.toProject(projectDTO);
jwtSecurityApplicationService.initBasicAuth(project);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
diff --git a/pom.xml b/pom.xml
index c9a8342..387cd3c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,6 +146,13 @@
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <version>${spring-boot.version}</version>
+ </path>
+ </annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/src/test/java/{{packageNamePath}}/technical/infrastructure/primary/exception/ExceptionTranslatorIT.java b/src/test/java/{{packageNamePath}}/technical/infrastructure/primary/exception/ExceptionTranslatorIT.java
index 42f0c99..426c3e1 100644
--- a/src/test/java/{{packageNamePath}}/technical/infrastructure/primary/exception/ExceptionTranslatorIT.java
+++ b/src/test/java/{{packageNamePath}}/technical/infrastructure/primary/exception/ExceptionTranslatorIT.java
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import {{packageName}}.IntegrationTest;
@@ -18,6 +19,7 @@ import {{packageName}}.IntegrationTest;
*/
@IntegrationTest
@AutoConfigureMockMvc
+@WithMockUser
class ExceptionTranslatorIT {
@Autowired
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package {{packageName}}.security.jwt.infrastructure.config;

@SuppressWarnings("java:S2386")
public interface ApplicationSecurityDefaults {
interface Security {
String contentSecurityPolicy =
"default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:";
interface Authentication {
interface Jwt {
String secret = null;
String base64Secret = null;
long tokenValidityInSeconds = 1800; // 30 minutes
long tokenValidityInSecondsForRememberMe = 2592000; // 30 days
}
}
}
}
Loading

0 comments on commit 3033d27

Please sign in to comment.