From 8a7e4b9ef4ad21ea180b5aeda178550c76303f1b Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Sat, 28 May 2022 14:48:24 +0100 Subject: [PATCH 01/24] Revert "[JAVA-8154] Code clean up for reactive security" --- .../reactive/security/GreetController.java | 37 ++++++++++++++++++ ...GreetingService.java => GreetService.java} | 2 +- .../reactive/security/GreetingController.java | 37 ------------------ .../reactive/security/SecurityConfig.java | 39 ++++++++++--------- .../security/SecurityIntegrationTest.java | 19 +++------ 5 files changed, 64 insertions(+), 70 deletions(-) create mode 100644 spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java rename spring-reactive/src/main/java/com/baeldung/reactive/security/{GreetingService.java => GreetService.java} (91%) delete mode 100644 spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java new file mode 100644 index 0000000000..99b79d88ea --- /dev/null +++ b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java @@ -0,0 +1,37 @@ +package com.baeldung.reactive.security; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +import java.security.Principal; + +@RestController +public class GreetController { + + private GreetService greetService; + + public GreetController(GreetService greetService) { + this.greetService = greetService; + } + + @GetMapping("/") + public Mono greet(Mono principal) { + return principal + .map(Principal::getName) + .map(name -> String.format("Hello, %s", name)); + } + + @GetMapping("/admin") + public Mono greetAdmin(Mono principal) { + return principal + .map(Principal::getName) + .map(name -> String.format("Admin access: %s", name)); + } + + @GetMapping("/greetService") + public Mono greetService() { + return greetService.greet(); + } + +} diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingService.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetService.java similarity index 91% rename from spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingService.java rename to spring-reactive/src/main/java/com/baeldung/reactive/security/GreetService.java index b512f12bae..93df64bced 100644 --- a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingService.java +++ b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetService.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Service -public class GreetingService { +public class GreetService { @PreAuthorize("hasRole('ADMIN')") public Mono greet() { diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java deleted file mode 100644 index 10d6cf4df7..0000000000 --- a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.baeldung.reactive.security; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; - -import java.security.Principal; - -@RestController -public class GreetingController { - - private final GreetingService greetingService; - - public GreetingController(GreetingService greetingService) { - this.greetingService = greetingService; - } - - @GetMapping("/") - public Mono greet(Mono principal) { - return principal - .map(Principal::getName) - .map(name -> String.format("Hello, %s", name)); - } - - @GetMapping("/admin") - public Mono greetAdmin(Mono principal) { - return principal - .map(Principal::getName) - .map(name -> String.format("Admin access: %s", name)); - } - - @GetMapping("/greetingService") - public Mono greetingService() { - return greetingService.greet(); - } - -} diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java index 67e54ad26a..bb2f2d50e1 100644 --- a/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java +++ b/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java @@ -16,37 +16,40 @@ import org.springframework.security.web.server.SecurityWebFilterChain; public class SecurityConfig { @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) { return http.authorizeExchange() - .pathMatchers("/admin").hasAuthority("ROLE_ADMIN") - .anyExchange().authenticated() - .and() - .formLogin() - .and() - .csrf().disable() - .build(); + .pathMatchers("/admin") + .hasAuthority("ROLE_ADMIN") + .anyExchange() + .authenticated() + .and() + .formLogin() + .and() + .csrf() + .disable() + .build(); } @Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User - .withUsername("user") - .password(passwordEncoder().encode("password")) - .roles("USER") - .build(); + .withUsername("user") + .password(passwordEncoder().encode("password")) + .roles("USER") + .build(); UserDetails admin = User - .withUsername("admin") - .password(passwordEncoder().encode("password")) - .roles("ADMIN") - .build(); + .withUsername("admin") + .password(passwordEncoder().encode("password")) + .roles("ADMIN") + .build(); return new MapReactiveUserDetailsService(user, admin); } - + @Bean public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + return new BCryptPasswordEncoder(); } } diff --git a/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java b/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java index 0ef828df5a..06644fbf77 100644 --- a/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java +++ b/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java @@ -15,32 +15,23 @@ import org.springframework.test.web.reactive.server.WebTestClient; public class SecurityIntegrationTest { @Autowired - private ApplicationContext context; + ApplicationContext context; - private WebTestClient webTestClient; + private WebTestClient rest; @BeforeEach public void setup() { - webTestClient = WebTestClient.bindToApplicationContext(context) - .configureClient() - .build(); + this.rest = WebTestClient.bindToApplicationContext(this.context).configureClient().build(); } @Test public void whenNoCredentials_thenRedirectToLogin() { - webTestClient.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection(); + this.rest.get().uri("/").exchange().expectStatus().is3xxRedirection(); } @Test @WithMockUser public void whenHasCredentials_thenSeesGreeting() { - webTestClient.get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello, user"); + this.rest.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello, user"); } } From 24cdf2b67221252c7aaf5930a8d283fe7d101394 Mon Sep 17 00:00:00 2001 From: Bhaskara Navuluri Date: Tue, 31 May 2022 20:24:47 +0530 Subject: [PATCH 02/24] BAEL-5534 Configure JWT Authentication for OpenAPI --- .../spring-boot-springdoc/pom.xml | 17 ++- .../com/baeldung/jwt/AuthenticationApi.java | 86 ++++++++++++ .../baeldung/jwt/OpenAPI30Configuration.java | 52 ++++++++ .../baeldung/jwt/SecurityConfiguration.java | 124 ++++++++++++++++++ .../jwt/SecurityTokenApplication.java | 17 +++ .../src/main/java/com/baeldung/jwt/User.java | 56 ++++++++ .../main/java/com/baeldung/jwt/UserApi.java | 50 +++++++ .../src/main/resources/app.key | 28 ++++ .../src/main/resources/app.pub | 9 ++ .../src/main/resources/application.properties | 15 ++- .../jwt/OpenApiJwtIntegrationTest.java | 89 +++++++++++++ 11 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/AuthenticationApi.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/OpenAPI30Configuration.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/User.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/UserApi.java create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/resources/app.key create mode 100644 spring-boot-modules/spring-boot-springdoc/src/main/resources/app.pub create mode 100644 spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java diff --git a/spring-boot-modules/spring-boot-springdoc/pom.xml b/spring-boot-modules/spring-boot-springdoc/pom.xml index e7d4a35d97..88e616119d 100644 --- a/spring-boot-modules/spring-boot-springdoc/pom.xml +++ b/spring-boot-modules/spring-boot-springdoc/pom.xml @@ -32,6 +32,14 @@ com.h2database h2 + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + org.springframework.boot spring-boot-starter-test @@ -48,6 +56,11 @@ springdoc-openapi-data-rest ${springdoc.version} + + org.springdoc + springdoc-openapi-security + ${springdoc.version} + org.springframework.restdocs @@ -131,7 +144,7 @@ org.springdoc springdoc-openapi-maven-plugin - 0.2 + 1.4 integration-test @@ -152,7 +165,7 @@ - 1.6.4 + 1.6.8 1.5.6 ${project.build.directory}/generated-snippets diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/AuthenticationApi.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/AuthenticationApi.java new file mode 100644 index 0000000000..c53774129a --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/AuthenticationApi.java @@ -0,0 +1,86 @@ +package com.baeldung.jwt; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +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.time.Instant; +import java.util.stream.Collectors; + +/** + * REST APIs that contains all the operations that can be performed for authentication like login, registration etc + */ +@RequestMapping("/api/auth") +@RestController +@Tag(name = "Authentication", description = "The Authentication API. Contains operations like change password, forgot password, login, logout, etc.") +public class AuthenticationApi { + + private final UserDetailsService userDetailsService; + private final JwtEncoder encoder; + + public AuthenticationApi(UserDetailsService userDetailsService, JwtEncoder encoder) { + this.userDetailsService = userDetailsService; + this.encoder = encoder; + } + + /** + * API to Login + * + * @param user The login entity that contains username and password + * @return Returns the JWT token + * @see com.baeldung.jwt.User + */ + @Operation(summary = "User Authentication", description = "Authenticate the user and return a JWT token if the user is valid.") + @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", examples = @io.swagger.v3.oas.annotations.media.ExampleObject(value = "{\n" + " \"username\": \"jane\",\n" + + " \"password\": \"password\"\n" + "}", summary = "User Authentication Example"))) + @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity login(@RequestBody User user) { + UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername()); + if (user.getPassword().equalsIgnoreCase(userDetails.getPassword())) { + String token = generateToken(userDetails); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set("X-AUTH-TOKEN", token); + return ResponseEntity.ok().headers(httpHeaders).contentType(MediaType.APPLICATION_JSON).body("{\"token\":\"" + token + "\"}"); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(MediaType.APPLICATION_JSON).body("Invalid username or password"); + } + } + + /** + * Generates the JWT token with claims + * + * @param userDetails The user details + * @return Returns the JWT token + */ + private String generateToken(UserDetails userDetails) { + Instant now = Instant.now(); + long expiry = 36000L; + // @formatter:off + String scope = userDetails.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(" ")); + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuer("self") + .issuedAt(now) + .expiresAt(now.plusSeconds(expiry)) + .subject(userDetails.getUsername()) + .claim("scope", scope) + .build(); + // @formatter:on + return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } + +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/OpenAPI30Configuration.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/OpenAPI30Configuration.java new file mode 100644 index 0000000000..53f0b735fe --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/OpenAPI30Configuration.java @@ -0,0 +1,52 @@ +package com.baeldung.jwt; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Contact; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.info.License; +import io.swagger.v3.oas.annotations.servers.Server; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +//@formatter:off +@OpenAPIDefinition( + info = @Info(title = "User API", version = "${api.version}", + contact = @Contact(name = "Baeldung", email = "user-apis@baeldung.com", url = "https://www.baeldung.com"), + license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0"), termsOfService = "${tos.uri}", + description = "${api.description}"), + servers = { + @Server(url = "http://localhost:8080", description = "Development"), + @Server(url = "${api.server.url}", description = "Production")}) +//@formatter:on +public class OpenAPI30Configuration { + + /** + * Configure the OpenAPI components. + * + * @return Returns fully configure OpenAPI object + * @see OpenAPI + */ + @Bean + public OpenAPI customizeOpenAPI() { + //@formatter:off + final String securitySchemeName = "bearerAuth"; + return new OpenAPI() + .addSecurityItem(new SecurityRequirement() + .addList(securitySchemeName)) + .components(new Components() + .addSecuritySchemes(securitySchemeName, new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .description( + "Provide the JWT token. JWT token can be obtained from the Login API. For testing, use the credentials john/password") + .bearerFormat("JWT"))); + //@formatter:on + + } +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java new file mode 100644 index 0000000000..6b42a8f1bb --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java @@ -0,0 +1,124 @@ +package com.baeldung.jwt; + +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; +import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +import org.springframework.security.core.userdetails.User; + +/** + * This class is inspired from + * https://github.com/spring-projects/spring-security-samples/blob/5.7.x/servlet/spring-boot/java/jwt/login/src/main/java/example/RestConfig.java + */ +@EnableWebSecurity +@Configuration +public class SecurityConfiguration { + + @Value("${jwt.public.key}") + RSAPublicKey publicKey; + + @Value("${jwt.private.key}") + RSAPrivateKey privateKey; + + /** + * This bean is used to configure the JWT token. Configure the URLs that should not be protected by the JWT token. + * + * @param http the HttpSecurity object + * @return the HttpSecurity object + * @throws Exception if an error occurs + */ + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + //@formatter:off + return http + .authorizeHttpRequests(authorizeRequests -> authorizeRequests + .antMatchers("/api/auth/**", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", + "/swagger-ui/index.html") + .permitAll() + .anyRequest() + .authenticated()) + .cors().disable() + .csrf().disable() + .formLogin().disable() + .httpBasic().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) + .exceptionHandling(exceptions -> exceptions + .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) + .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) + .and()) + .build(); + //@formatter:on + } + + /** + * For demonstration/example, we use the InMemoryUserDetailsManager. + * + * @return Returns the UserDetailsService with pre-configure credentials. + * @see InMemoryUserDetailsManager + */ + @Bean + UserDetailsService allUsers() { + // @formatter:off + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager + .createUser(User.builder() + .passwordEncoder(password -> password) + .username("john") + .password("password") + .authorities("USER") + .roles("USER").build()); + manager + .createUser(User.builder() + .passwordEncoder(password -> password) + .username("jane") + .password("password") + .authorities("USER") + .roles("USER").build()); + return manager; + // @formatter:on + } + + /** + * This bean is used to decode the JWT token. + * + * @return Returns the JwtDecoder bean to decode JWT tokens. + */ + @Bean + JwtDecoder jwtDecoder() { + return NimbusJwtDecoder.withPublicKey(this.publicKey).build(); + } + + /** + * This bean is used to encode the JWT token. + * + * @return Returns the JwtEncoder bean to encode JWT tokens. + */ + @Bean + JwtEncoder jwtEncoder() { + JWK jwk = new RSAKey.Builder(this.publicKey).privateKey(this.privateKey).build(); + return new NimbusJwtEncoder(new ImmutableJWKSet<>(new JWKSet(jwk))); + } +} + diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java new file mode 100644 index 0000000000..4c0c4f01d8 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java @@ -0,0 +1,17 @@ +package com.baeldung.jwt; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SecurityTokenApplication { + + /** + * The bootstrap method + * @param args + */ + public static void main(String[] args) { + SpringApplication.run(SecurityTokenApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/User.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/User.java new file mode 100644 index 0000000000..43427c609f --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/User.java @@ -0,0 +1,56 @@ +package com.baeldung.jwt; + + +import java.io.Serializable; + +public class User implements Serializable { + + private static final long serialVersionUID = 3317686311392412458L; + private String username; + private String password; + private String role; + private String email; + + public User(String username, String password, String role) { + this.username = username; + this.password = password; + this.role = role; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public String toString() { + return "User [username=" + username + ", password=" + password + ", role=" + role + "]"; + } +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/UserApi.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/UserApi.java new file mode 100644 index 0000000000..d2d17978ba --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/UserApi.java @@ -0,0 +1,50 @@ +package com.baeldung.jwt; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.text.MessageFormat; + +/** + * REST APIs that contain all the operations that can be performed on a User + */ +@RequestMapping("/api/user") +@RestController +@Tag(name = "User", description = "The User API. Contains all the operations that can be performed on a user.") +public class UserApi { + + private final UserDetailsService userDetailsService; + + public UserApi(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + /** + * API to get the current user. Returns the user details for the provided JWT token + * @param authentication The authentication object that contains the JWT token + * @return Returns the user details for the provided JWT token + */ + @Operation(summary = "Get user details", description = "Get the user details. The operation returns the details of the user that is associated " + "with the provided JWT token.") + @GetMapping + public UserDetails getUser(Authentication authentication) { + return userDetailsService.loadUserByUsername(authentication.getName()); + } + + /** + * API to delete the current user. + * @param authentication The authentication object that contains the JWT token + * @return Returns a success message on deletion of the user + */ + @Operation(summary = "Delete user details", description = "Delete user details. The operation deletes the details of the user that is " + "associated with the provided JWT token.") + @DeleteMapping + public String deleteUser(Authentication authentication) { + return MessageFormat.format("User {0} deleted successfully", authentication.getName()); + } +} diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/resources/app.key b/spring-boot-modules/spring-boot-springdoc/src/main/resources/app.key new file mode 100644 index 0000000000..53510079ac --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/resources/app.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA +iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM +g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK +LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF +oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc +3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn ++jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE +E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek +lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG +mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7 +62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0 +bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA ++Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH +Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA +8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd +I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY +QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d +rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk +HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA +Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN +HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a +FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF +snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H +c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM +TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR +47jndeyIaMTNETEmOnms+as17g== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/resources/app.pub b/spring-boot-modules/spring-boot-springdoc/src/main/resources/app.pub new file mode 100644 index 0000000000..0b2ee7b336 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/main/resources/app.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd +7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv +c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6 +iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2 +kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o +RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj +KwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties b/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties index 733e716e76..a668601a7d 100644 --- a/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties +++ b/spring-boot-modules/spring-boot-springdoc/src/main/resources/application.properties @@ -1,6 +1,5 @@ # custom path for swagger-ui springdoc.swagger-ui.path=/swagger-ui-custom.html -springdoc.swagger-ui.operationsSorter=method # custom path for api docs springdoc.api-docs.path=/api-docs @@ -10,4 +9,16 @@ spring.datasource.url=jdbc:h2:mem:springdoc ## for com.baeldung.restdocopenapi ## springdoc.version=@springdoc.version@ -spring.jpa.hibernate.ddl-auto=none \ No newline at end of file +spring.jpa.hibernate.ddl-auto=none + +## for com.baeldung.jwt ## +jwt.private.key=classpath:app.key +jwt.public.key=classpath:app.pub + + +api.version=1.0-SNAPSHOT +tos.uri=terms-of-service +api.server.url=https://www.baeldung.com +api.description=The User API is used to create, update, and delete users. Users can be created with or without an associated account. If an account is created, the user will be granted the ROLE_USER role. If an account is not created, the user will be granted the ROLE_USER role. +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.tagsSorter=alpha \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java b/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java new file mode 100644 index 0000000000..1ea88d14fa --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java @@ -0,0 +1,89 @@ +package com.baeldung.jwt; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DisplayName("OpenAPI JWT Live Tests") +class OpenApiJwtIntegrationTest +{ + @LocalServerPort + private int port; + @Autowired + private AuthenticationApi authenticationApi; + @Autowired + private TestRestTemplate restTemplate; + + @Test + @DisplayName("LiveTest - Render Swagger UI") + void whenInvokeSwagger_thenRenderIndexPage() + { + assertNotNull(authenticationApi); + + String response = this.restTemplate.getForObject("http://localhost:" + port + "/swagger-ui.html", String.class); + + assertNotNull(response); + assertTrue(response.contains("Swagger UI")); + assertTrue(response.contains("
")); + } + + @Test + @DisplayName("LiveTest - Check Headers") + void whenInvokeOpenApi_thenCheckHeaders() + { + assertNotNull(authenticationApi); + + ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", + String.class); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getHeaders().get("Content-Type")); + assertEquals(1, response.getHeaders().get("Content-Type").size()); + assertEquals("application/json", response.getHeaders().get("Content-Type").get(0)); + } + + @Test + @DisplayName("LiveTest - Verify OpenAPI Document") + void whenInvokeOpenApi_thenVerifyOpenApiDoc() + { + assertNotNull(authenticationApi); + + ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", + String.class); + + assertNotNull(response); + assertNotNull(response.getBody()); + assertTrue(response.getBody().contains("\"openapi\":")); + assertTrue(response.getBody().contains("User API")); + assertTrue(response.getBody().contains("\"post\"")); + } + + @Test + @DisplayName("LiveTest - Verify OpenAPI Security Section") + void whenInvokeOpenApi_thenCheckSecurityConfig() + { + assertNotNull(authenticationApi); + + ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", + String.class); + + assertNotNull(response); + assertNotNull(response.getBody()); + assertTrue(response.getBody().contains("\"securitySchemes\"")); + assertTrue(response.getBody().contains("\"bearerFormat\":\"JWT\"")); + assertTrue(response.getBody().contains("\"scheme\":\"bearer\"")); + } + +} + From 7f97acf36116b21c6b1077aae0f768f9257d795c Mon Sep 17 00:00:00 2001 From: Azhwani <13301425+azhwani@users.noreply.github.com> Date: Sun, 5 Jun 2022 17:26:59 +0200 Subject: [PATCH 03/24] BAEL-4469: Update Introduction to Mockito AdditionalAnswers (#12267) * init commit * improve test cases --- .../BookServiceUnitTest.java | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java index ee32bcf70c..b2a998b8b9 100644 --- a/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java +++ b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/additionalanswers/BookServiceUnitTest.java @@ -1,5 +1,15 @@ package com.baeldung.mockito.additionalanswers; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.AdditionalAnswers.answer; +import static org.mockito.AdditionalAnswers.answerVoid; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.AdditionalAnswers; @@ -7,8 +17,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; @RunWith(MockitoJUnitRunner.class) public class BookServiceUnitTest { @@ -65,4 +73,32 @@ public class BookServiceUnitTest { assertEquals(bookOnIndex, book2); } + + @Test + public void givenMockedMethod_whenMethodInvoked_thenReturnBook() { + Long id = 1L; + when(bookRepository.getByBookId(anyLong())).thenAnswer(answer(BookServiceUnitTest::buildBook)); + + assertNotNull(bookService.getByBookId(id)); + assertEquals("The Stranger", bookService.getByBookId(id).getTitle()); + } + + @Test + public void givenMockedMethod_whenMethodInvoked_thenReturnVoid() { + Long id = 2L; + when(bookRepository.getByBookId(anyLong())).thenAnswer(answerVoid(BookServiceUnitTest::printBookId)); + + bookService.getByBookId(id); + + verify(bookRepository, times(1)).getByBookId(id); + } + + private static Book buildBook(Long bookId) { + return new Book(bookId, "The Stranger", "Albert Camus", 456); + } + + private static void printBookId(Long bookId) { + System.out.println(bookId); + } + } From 4c8b4485a4933f1a1ee9139e00a65239a90c5455 Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Mon, 6 Jun 2022 10:58:48 +0500 Subject: [PATCH 04/24] Updated README.md added link back to the article: https://www.baeldung.com/java-sort-list-alphabetically --- core-java-modules/core-java-collections-list-4/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/core-java-modules/core-java-collections-list-4/README.md b/core-java-modules/core-java-collections-list-4/README.md index 09b61fa9b2..4c020969e3 100644 --- a/core-java-modules/core-java-collections-list-4/README.md +++ b/core-java-modules/core-java-collections-list-4/README.md @@ -5,4 +5,5 @@ This module contains articles about the Java List collection ### Relevant Articles: - [Working With a List of Lists in Java](https://www.baeldung.com/java-list-of-lists) - [Reverse an ArrayList in Java](https://www.baeldung.com/java-reverse-arraylist) +- [Sort a List Alphabetically in Java](https://www.baeldung.com/java-sort-list-alphabetically) - [[<-- Prev]](/core-java-modules/core-java-collections-list-3) From be9d239af268781ae7dca7f1198ce256b7cbbd0f Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:30:14 +0500 Subject: [PATCH 05/24] Updated README.md added link back to the article: https://www.baeldung.com/ops/docker-compose-restart-policies --- spring-cloud-modules/spring-cloud-docker/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-cloud-modules/spring-cloud-docker/README.md b/spring-cloud-modules/spring-cloud-docker/README.md index 018d4ab1eb..9c7eac5105 100644 --- a/spring-cloud-modules/spring-cloud-docker/README.md +++ b/spring-cloud-modules/spring-cloud-docker/README.md @@ -1,3 +1,4 @@ ## Relevant Articles: - [Dockerizing a Spring Boot Application](https://www.baeldung.com/dockerizing-spring-boot-application) +- [Docker Compose Restart Policies](https://www.baeldung.com/ops/docker-compose-restart-policies) From 29a83bcc4a00b22504b8bbea3a45379eb32366af Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:40:36 +0500 Subject: [PATCH 06/24] Updated README.md added link back to the article: https://www.baeldung.com/java-bigdecimal-zero --- java-numbers-4/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/java-numbers-4/README.md b/java-numbers-4/README.md index 2a77992c8f..12f9890ee3 100644 --- a/java-numbers-4/README.md +++ b/java-numbers-4/README.md @@ -9,3 +9,4 @@ - [Convert boolean to int in Java](https://www.baeldung.com/java-boolean-to-int) - [Generate a Random Value From an Enum](https://www.baeldung.com/java-enum-random-value) - [Reverse a Number in Java](https://www.baeldung.com/java-reverse-number) +- [Check if BigDecimal Value Is Zero](https://www.baeldung.com/java-bigdecimal-zero) From 164cfa7630242b96c3d7ec9ac16fc517d806b759 Mon Sep 17 00:00:00 2001 From: Asjad J <97493880+Asjad-J@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:45:42 +0500 Subject: [PATCH 07/24] Updated README.md added link back to the article: https://www.baeldung.com/java-httpservletrequest-mock --- javax-servlets-2/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/javax-servlets-2/README.md b/javax-servlets-2/README.md index 9a7ad02d39..cd599890ad 100644 --- a/javax-servlets-2/README.md +++ b/javax-servlets-2/README.md @@ -4,3 +4,4 @@ This module contains articles about Servlets. ### Relevant Articles: - [Check if a User Is Logged-in With Servlets and JSP](https://www.baeldung.com/servlets-jsp-check-user-login) +- [How to Mock HttpServletRequest](https://www.baeldung.com/java-httpservletrequest-mock) From a26d45e5c0d481a38d533cb056b7ea274bb0febd Mon Sep 17 00:00:00 2001 From: Bhaskara Navuluri Date: Mon, 6 Jun 2022 20:35:38 +0530 Subject: [PATCH 08/24] Fixed the Integration Build Pipeline --- .../spring-boot-springdoc/pom.xml | 6 +- .../baeldung/jwt/SecurityConfiguration.java | 4 +- .../jwt/SecurityTokenApplication.java | 4 +- .../jwt/OpenApiJwtIntegrationTest.java | 8 +- .../MongoAuthApplicationIntegrationTest.java | 118 ------------------ 5 files changed, 11 insertions(+), 129 deletions(-) delete mode 100644 spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java diff --git a/spring-boot-modules/spring-boot-springdoc/pom.xml b/spring-boot-modules/spring-boot-springdoc/pom.xml index 88e616119d..18b1774920 100644 --- a/spring-boot-modules/spring-boot-springdoc/pom.xml +++ b/spring-boot-modules/spring-boot-springdoc/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 spring-boot-springdoc 0.0.1-SNAPSHOT @@ -112,6 +110,8 @@ application.properties data.sql schema.sql + app.key + app.pub diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java index 6b42a8f1bb..216740f979 100644 --- a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityConfiguration.java @@ -52,8 +52,8 @@ public class SecurityConfiguration { //@formatter:off return http .authorizeHttpRequests(authorizeRequests -> authorizeRequests - .antMatchers("/api/auth/**", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", - "/swagger-ui/index.html") + .antMatchers("/api/auth/**", "/swagger-ui-custom.html" ,"/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", + "/swagger-ui/index.html","/api-docs/**") .permitAll() .anyRequest() .authenticated()) diff --git a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java index 4c0c4f01d8..ba396e1703 100644 --- a/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java +++ b/spring-boot-modules/spring-boot-springdoc/src/main/java/com/baeldung/jwt/SecurityTokenApplication.java @@ -11,7 +11,7 @@ public class SecurityTokenApplication { * @param args */ public static void main(String[] args) { - SpringApplication.run(SecurityTokenApplication.class, args); + SpringApplication.run(SecurityTokenApplication.class); } - } + diff --git a/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java b/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java index 1ea88d14fa..fecd4101eb 100644 --- a/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java +++ b/spring-boot-modules/spring-boot-springdoc/src/test/java/com/baeldung/jwt/OpenApiJwtIntegrationTest.java @@ -30,7 +30,7 @@ class OpenApiJwtIntegrationTest { assertNotNull(authenticationApi); - String response = this.restTemplate.getForObject("http://localhost:" + port + "/swagger-ui.html", String.class); + String response = this.restTemplate.getForObject("http://localhost:" + port + "/swagger-ui/index.html", String.class); assertNotNull(response); assertTrue(response.contains("Swagger UI")); @@ -43,7 +43,7 @@ class OpenApiJwtIntegrationTest { assertNotNull(authenticationApi); - ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", + ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/api-docs", String.class); assertNotNull(response); @@ -59,7 +59,7 @@ class OpenApiJwtIntegrationTest { assertNotNull(authenticationApi); - ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", + ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/api-docs", String.class); assertNotNull(response); @@ -75,7 +75,7 @@ class OpenApiJwtIntegrationTest { assertNotNull(authenticationApi); - ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", + ResponseEntity response = this.restTemplate.getForEntity("http://localhost:" + port + "/api-docs", String.class); assertNotNull(response); diff --git a/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java b/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java deleted file mode 100644 index b7994cad9e..0000000000 --- a/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.baeldung.mongoauth; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.Collections; -import java.util.HashSet; - -import org.junit.jupiter.api.BeforeEach; -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.boot.test.context.SpringBootTest; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import com.baeldung.mongoauth.domain.Role; -import com.baeldung.mongoauth.domain.User; -import com.baeldung.mongoauth.domain.UserRole; - -@SpringBootTest(classes = { MongoAuthApplication.class }) -@AutoConfigureMockMvc -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -class MongoAuthApplicationIntegrationTest { - - @Autowired - private WebApplicationContext context; - - @Autowired - private MongoTemplate mongoTemplate; - - @Autowired - private BCryptPasswordEncoder bCryptPasswordEncoder; - - private MockMvc mvc; - - private static final String USER_NAME = "user@gmail.com"; - private static final String ADMIN_NAME = "admin@gmail.com"; - private static final String PASSWORD = "password"; - - @BeforeEach - public void setup() { - - setUp(); - - mvc = MockMvcBuilders.webAppContextSetup(context) - .apply(springSecurity()) - .build(); - } - - private void setUp() { - Role roleUser = new Role(); - roleUser.setName("ROLE_USER"); - mongoTemplate.save(roleUser); - - User user = new User(); - user.setUsername(USER_NAME); - user.setPassword(bCryptPasswordEncoder.encode(PASSWORD)); - - UserRole userRole = new UserRole(); - userRole.setRole(roleUser); - user.setUserRoles(new HashSet<>(Collections.singletonList(userRole))); - mongoTemplate.save(user); - - User admin = new User(); - admin.setUsername(ADMIN_NAME); - admin.setPassword(bCryptPasswordEncoder.encode(PASSWORD)); - - Role roleAdmin = new Role(); - roleAdmin.setName("ROLE_ADMIN"); - mongoTemplate.save(roleAdmin); - - UserRole adminRole = new UserRole(); - adminRole.setRole(roleAdmin); - admin.setUserRoles(new HashSet<>(Collections.singletonList(adminRole))); - mongoTemplate.save(admin); - } - - @Test - void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception { - mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD))) - .andExpect(status().isOk()); - } - - @Test - void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception { - mvc.perform(get("/user").with(httpBasic("not_existing_user", "password"))) - .andExpect(status().isUnauthorized()); - } - - @Test - void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception { - mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password"))) - .andExpect(status().isUnauthorized()); - } - - @Test - void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception { - mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD))) - .andExpect(status().isForbidden()); - } - - @Test - void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception { - mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD))) - .andExpect(status().isOk()); - - mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD))) - .andExpect(status().isOk()); - } - -} From 29db405d5ded5710f087d8f59ab801e0eed08c0b Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 00:34:07 +0800 Subject: [PATCH 09/24] Update README.md --- apache-cxf-modules/cxf-introduction/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apache-cxf-modules/cxf-introduction/README.md b/apache-cxf-modules/cxf-introduction/README.md index 9a076524b7..3eef167785 100644 --- a/apache-cxf-modules/cxf-introduction/README.md +++ b/apache-cxf-modules/cxf-introduction/README.md @@ -1,2 +1,2 @@ ### Relevant Articles: -- [Introduction to Apache CXF](http://www.baeldung.com/introduction-to-apache-cxf) +- [Introduction to Apache CXF](https://www.baeldung.com/introduction-to-apache-cxf) From 8813f1c809e5f01e81ae37f4d5ea243cd02f286c Mon Sep 17 00:00:00 2001 From: anuragkumawat Date: Mon, 6 Jun 2022 22:07:37 +0530 Subject: [PATCH 10/24] JAVA-11558 Update article - Hibernate 5 Naming Strategy Configuration --- .../hibernate/namingstrategy/CustomPhysicalNamingStrategy.java | 2 +- .../src/test/resources/hibernate-namingstrategy.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/namingstrategy/CustomPhysicalNamingStrategy.java b/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/namingstrategy/CustomPhysicalNamingStrategy.java index 74bcb9e411..d0cc6e8be6 100644 --- a/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/namingstrategy/CustomPhysicalNamingStrategy.java +++ b/persistence-modules/hibernate5/src/main/java/com/baeldung/hibernate/namingstrategy/CustomPhysicalNamingStrategy.java @@ -41,7 +41,7 @@ public class CustomPhysicalNamingStrategy implements PhysicalNamingStrategy { final String newName = identifier.getText() .replaceAll(regex, replacement) .toLowerCase(); - return Identifier.toIdentifier(newName); + return Identifier.toIdentifier(newName, identifier.isQuoted()); } } diff --git a/persistence-modules/hibernate5/src/test/resources/hibernate-namingstrategy.properties b/persistence-modules/hibernate5/src/test/resources/hibernate-namingstrategy.properties index 263033823c..d0e068d13f 100644 --- a/persistence-modules/hibernate5/src/test/resources/hibernate-namingstrategy.properties +++ b/persistence-modules/hibernate5/src/test/resources/hibernate-namingstrategy.properties @@ -5,6 +5,7 @@ hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.show_sql=false hibernate.hbm2ddl.auto=create-drop +hibernate.globally_quoted_identifiers=true hibernate.physical_naming_strategy=com.baeldung.hibernate.namingstrategy.CustomPhysicalNamingStrategy hibernate.implicit_naming_strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl \ No newline at end of file From 33290fd7e3b9c8e3ea26ee66d381c957a42cd6c9 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 00:53:35 +0800 Subject: [PATCH 11/24] Update README.md --- spring-boot-modules/spring-boot-libraries-2/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-modules/spring-boot-libraries-2/README.md b/spring-boot-modules/spring-boot-libraries-2/README.md index 96f82cdf59..2031b76661 100644 --- a/spring-boot-modules/spring-boot-libraries-2/README.md +++ b/spring-boot-modules/spring-boot-libraries-2/README.md @@ -8,5 +8,6 @@ This module contains articles about various Spring Boot libraries - [Open API Server Implementation Using OpenAPI Generator](https://www.baeldung.com/java-openapi-generator-server) - [An Introduction to Kong](https://www.baeldung.com/kong) - [Getting Started With GraphQL SPQR and Spring Boot](https://www.baeldung.com/spring-boot-graphql-spqr) +- [How to Test GraphQL Using Postman](https://www.baeldung.com/graphql-postman) More articles: [[prev -->]](/spring-boot-modules/spring-boot-libraries) From 054afeea49e6aff91668244955657ba73b1b9f2b Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 01:01:21 +0800 Subject: [PATCH 12/24] Create README.md --- docker-modules/docker-spring-boot-postgres/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docker-modules/docker-spring-boot-postgres/README.md diff --git a/docker-modules/docker-spring-boot-postgres/README.md b/docker-modules/docker-spring-boot-postgres/README.md new file mode 100644 index 0000000000..c7e83a2e7c --- /dev/null +++ b/docker-modules/docker-spring-boot-postgres/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Running Spring Boot with PostgreSQL in Docker Compose](https://www.baeldung.com/spring-boot-postgresql-docker) From 8422a655ac9a3fec52fb7953cfdaf13ec1537f83 Mon Sep 17 00:00:00 2001 From: Christian German <64325154+christian-german@users.noreply.github.com> Date: Tue, 7 Jun 2022 06:12:52 +0200 Subject: [PATCH 13/24] BAEL-5252-Disable-Keycloak-Security-in-Spring-Boot (#12218) --- .../com/baeldung/disablingkeycloak/App.java | 13 ++++++ .../DisableSecurityConfiguration.java | 20 ++++++++++ .../KeycloakConfiguration.java | 14 +++++++ .../KeycloakSecurityConfig.java | 38 ++++++++++++++++++ .../com/baeldung/disablingkeycloak/User.java | 40 +++++++++++++++++++ .../disablingkeycloak/UserController.java | 17 ++++++++ .../application-disabling-keycloak.properties | 7 ++++ .../DisablingKeycloakIntegrationTest.java | 33 +++++++++++++++ .../application-disablingkeycloak.properties | 1 + 9 files changed, 183 insertions(+) create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/App.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/DisableSecurityConfiguration.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakConfiguration.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakSecurityConfig.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/User.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/UserController.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/main/resources/application-disabling-keycloak.properties create mode 100644 spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/disablingkeycloak/DisablingKeycloakIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-keycloak/src/test/resources/application-disablingkeycloak.properties diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/App.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/App.java new file mode 100644 index 0000000000..9655c80cc0 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/App.java @@ -0,0 +1,13 @@ +package com.baeldung.disablingkeycloak; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = { "com.baeldung.disablingkeycloak" }) +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/DisableSecurityConfiguration.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/DisableSecurityConfiguration.java new file mode 100644 index 0000000000..619fd63662 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/DisableSecurityConfiguration.java @@ -0,0 +1,20 @@ +package com.baeldung.disablingkeycloak; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "false") +public class DisableSecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(final HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeRequests() + .anyRequest() + .permitAll(); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakConfiguration.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakConfiguration.java new file mode 100644 index 0000000000..a9a2ea6a18 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakConfiguration.java @@ -0,0 +1,14 @@ +package com.baeldung.disablingkeycloak; + +import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class KeycloakConfiguration { + + @Bean + public KeycloakSpringBootConfigResolver keycloakConfigResolver() { + return new KeycloakSpringBootConfigResolver(); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakSecurityConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakSecurityConfig.java new file mode 100644 index 0000000000..d48c99d8fd --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/KeycloakSecurityConfig.java @@ -0,0 +1,38 @@ +package com.baeldung.disablingkeycloak; + +import org.keycloak.adapters.springsecurity.KeycloakConfiguration; +import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; + +@KeycloakConfiguration +@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true) +public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + auth.authenticationProvider(keycloakAuthenticationProvider()); + } + + @Bean + @Override + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new NullAuthenticatedSessionStrategy(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + + http.csrf() + .disable() + .authorizeRequests() + .anyRequest() + .authenticated(); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/User.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/User.java new file mode 100644 index 0000000000..78d4a9913a --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/User.java @@ -0,0 +1,40 @@ +package com.baeldung.disablingkeycloak; + +public class User { + private Long id; + private String firstname; + private String lastname; + + public User() { + } + + public User(Long id, String firstname, String lastname) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/UserController.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/UserController.java new file mode 100644 index 0000000000..19b429a78d --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/disablingkeycloak/UserController.java @@ -0,0 +1,17 @@ +package com.baeldung.disablingkeycloak; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/users") +public class UserController { + + @GetMapping("/{userId}") + public User getCustomer(@PathVariable Long userId) { + return new User(userId, "John", "Doe"); + } + +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-disabling-keycloak.properties b/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-disabling-keycloak.properties new file mode 100644 index 0000000000..21263cf725 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-disabling-keycloak.properties @@ -0,0 +1,7 @@ +# Keycloak authentication is enabled for production. +keycloak.enabled=true +keycloak.realm=SpringBootKeycloak +keycloak.auth-server-url=http://localhost:8180/auth +keycloak.resource=login-app +keycloak.bearer-only=true +keycloak.ssl-required=external diff --git a/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/disablingkeycloak/DisablingKeycloakIntegrationTest.java b/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/disablingkeycloak/DisablingKeycloakIntegrationTest.java new file mode 100644 index 0000000000..cf70f7e7c3 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/disablingkeycloak/DisablingKeycloakIntegrationTest.java @@ -0,0 +1,33 @@ +package com.baeldung.disablingkeycloak; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.apache.http.HttpStatus; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +@SpringBootTest(classes = App.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@RunWith(SpringRunner.class) +@ActiveProfiles("disablingkeycloak") +public class DisablingKeycloakIntegrationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void givenUnauthenticated_whenGettingUser_shouldReturnUser() { + ResponseEntity responseEntity = restTemplate.getForEntity("/users/1", User.class); + + assertEquals(HttpStatus.SC_OK, responseEntity.getStatusCodeValue()); + assertNotNull(responseEntity.getBody() + .getFirstname()); + } + +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-disablingkeycloak.properties b/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-disablingkeycloak.properties new file mode 100644 index 0000000000..db2c8fc59a --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-disablingkeycloak.properties @@ -0,0 +1 @@ +keycloak.enabled=false From 6d490047ff80aa6cd862c30e6725549016572b3e Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 13:40:26 +0800 Subject: [PATCH 14/24] Update README.md --- apache-cxf-modules/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apache-cxf-modules/README.md b/apache-cxf-modules/README.md index 88edaf4e13..4cc174ac13 100644 --- a/apache-cxf-modules/README.md +++ b/apache-cxf-modules/README.md @@ -7,3 +7,4 @@ This module contains articles about Apache CXF - [Apache CXF Support for RESTful Web Services](https://www.baeldung.com/apache-cxf-rest-api) - [A Guide to Apache CXF with Spring](https://www.baeldung.com/apache-cxf-with-spring) - [Introduction to Apache CXF](https://www.baeldung.com/introduction-to-apache-cxf) +- [Introduction to Apache CXF Aegis Data Binding](https://www.baeldung.com/aegis-data-binding-in-apache-cxf) From 502f947cdb05e7e707554e33c6d320fddd88affb Mon Sep 17 00:00:00 2001 From: Bhaskara Navuluri Date: Tue, 7 Jun 2022 12:16:50 +0530 Subject: [PATCH 15/24] Restored the deleted test file --- .../MongoAuthApplicationIntegrationTest.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java diff --git a/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java b/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java new file mode 100644 index 0000000000..fc9fd1ef30 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-3/src/test/java/com/baeldung/mongoauth/MongoAuthApplicationIntegrationTest.java @@ -0,0 +1,118 @@ +package com.baeldung.mongoauth; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Collections; +import java.util.HashSet; + +import org.junit.jupiter.api.BeforeEach; +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.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.baeldung.mongoauth.domain.Role; +import com.baeldung.mongoauth.domain.User; +import com.baeldung.mongoauth.domain.UserRole; + +@SpringBootTest(classes = { MongoAuthApplication.class }) +@AutoConfigureMockMvc +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class MongoAuthApplicationIntegrationTest { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private BCryptPasswordEncoder bCryptPasswordEncoder; + + private MockMvc mvc; + + private static final String USER_NAME = "user@gmail.com"; + private static final String ADMIN_NAME = "admin@gmail.com"; + private static final String PASSWORD = "password"; + + @BeforeEach + public void setup() { + + setUp(); + + mvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + private void setUp() { + Role roleUser = new Role(); + roleUser.setName("ROLE_USER"); + mongoTemplate.save(roleUser); + + User user = new User(); + user.setUsername(USER_NAME); + user.setPassword(bCryptPasswordEncoder.encode(PASSWORD)); + + UserRole userRole = new UserRole(); + userRole.setRole(roleUser); + user.setUserRoles(new HashSet<>(Collections.singletonList(userRole))); + mongoTemplate.save(user); + + User admin = new User(); + admin.setUsername(ADMIN_NAME); + admin.setPassword(bCryptPasswordEncoder.encode(PASSWORD)); + + Role roleAdmin = new Role(); + roleAdmin.setName("ROLE_ADMIN"); + mongoTemplate.save(roleAdmin); + + UserRole adminRole = new UserRole(); + adminRole.setRole(roleAdmin); + admin.setUserRoles(new HashSet<>(Collections.singletonList(adminRole))); + mongoTemplate.save(admin); + } + + @Test + void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception { + mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD))) + .andExpect(status().isOk()); + } + + @Test + void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception { + mvc.perform(get("/user").with(httpBasic("not_existing_user", "password"))) + .andExpect(status().isUnauthorized()); + } + + @Test + void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception { + mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password"))) + .andExpect(status().isUnauthorized()); + } + + @Test + void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception { + mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD))) + .andExpect(status().isForbidden()); + } + + @Test + void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception { + mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD))) + .andExpect(status().isOk()); + + mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD))) + .andExpect(status().isOk()); + } + +} From 2fd1d693554432597cde5d3f12a771faf7a2598f Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 15:02:34 +0800 Subject: [PATCH 16/24] Update README.md --- apache-cxf-modules/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apache-cxf-modules/README.md b/apache-cxf-modules/README.md index 4cc174ac13..90d0fc884b 100644 --- a/apache-cxf-modules/README.md +++ b/apache-cxf-modules/README.md @@ -4,7 +4,6 @@ This module contains articles about Apache CXF ## Relevant Articles: -- [Apache CXF Support for RESTful Web Services](https://www.baeldung.com/apache-cxf-rest-api) - [A Guide to Apache CXF with Spring](https://www.baeldung.com/apache-cxf-with-spring) - [Introduction to Apache CXF](https://www.baeldung.com/introduction-to-apache-cxf) - [Introduction to Apache CXF Aegis Data Binding](https://www.baeldung.com/aegis-data-binding-in-apache-cxf) From 5454b3518794c43589fc34252d84e7db074f8570 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 15:05:09 +0800 Subject: [PATCH 17/24] Update README.md --- apache-cxf-modules/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apache-cxf-modules/README.md b/apache-cxf-modules/README.md index 90d0fc884b..060d16af3f 100644 --- a/apache-cxf-modules/README.md +++ b/apache-cxf-modules/README.md @@ -4,6 +4,5 @@ This module contains articles about Apache CXF ## Relevant Articles: -- [A Guide to Apache CXF with Spring](https://www.baeldung.com/apache-cxf-with-spring) - [Introduction to Apache CXF](https://www.baeldung.com/introduction-to-apache-cxf) - [Introduction to Apache CXF Aegis Data Binding](https://www.baeldung.com/aegis-data-binding-in-apache-cxf) From ed2f49a894ea779bdfa3d37e6ed60d99f1ba6828 Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Tue, 7 Jun 2022 09:33:06 +0100 Subject: [PATCH 18/24] Revert "Revert "[JAVA-8154] Code clean up for reactive security"" --- .../reactive/security/GreetController.java | 37 ------------------ .../reactive/security/GreetingController.java | 37 ++++++++++++++++++ ...GreetService.java => GreetingService.java} | 2 +- .../reactive/security/SecurityConfig.java | 39 +++++++++---------- .../security/SecurityIntegrationTest.java | 19 ++++++--- 5 files changed, 70 insertions(+), 64 deletions(-) delete mode 100644 spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java create mode 100644 spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java rename spring-reactive/src/main/java/com/baeldung/reactive/security/{GreetService.java => GreetingService.java} (91%) diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java deleted file mode 100644 index 99b79d88ea..0000000000 --- a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetController.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.baeldung.reactive.security; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; - -import java.security.Principal; - -@RestController -public class GreetController { - - private GreetService greetService; - - public GreetController(GreetService greetService) { - this.greetService = greetService; - } - - @GetMapping("/") - public Mono greet(Mono principal) { - return principal - .map(Principal::getName) - .map(name -> String.format("Hello, %s", name)); - } - - @GetMapping("/admin") - public Mono greetAdmin(Mono principal) { - return principal - .map(Principal::getName) - .map(name -> String.format("Admin access: %s", name)); - } - - @GetMapping("/greetService") - public Mono greetService() { - return greetService.greet(); - } - -} diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java new file mode 100644 index 0000000000..10d6cf4df7 --- /dev/null +++ b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingController.java @@ -0,0 +1,37 @@ +package com.baeldung.reactive.security; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +import java.security.Principal; + +@RestController +public class GreetingController { + + private final GreetingService greetingService; + + public GreetingController(GreetingService greetingService) { + this.greetingService = greetingService; + } + + @GetMapping("/") + public Mono greet(Mono principal) { + return principal + .map(Principal::getName) + .map(name -> String.format("Hello, %s", name)); + } + + @GetMapping("/admin") + public Mono greetAdmin(Mono principal) { + return principal + .map(Principal::getName) + .map(name -> String.format("Admin access: %s", name)); + } + + @GetMapping("/greetingService") + public Mono greetingService() { + return greetingService.greet(); + } + +} diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetService.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingService.java similarity index 91% rename from spring-reactive/src/main/java/com/baeldung/reactive/security/GreetService.java rename to spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingService.java index 93df64bced..b512f12bae 100644 --- a/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetService.java +++ b/spring-reactive/src/main/java/com/baeldung/reactive/security/GreetingService.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Service -public class GreetService { +public class GreetingService { @PreAuthorize("hasRole('ADMIN')") public Mono greet() { diff --git a/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java b/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java index bb2f2d50e1..67e54ad26a 100644 --- a/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java +++ b/spring-reactive/src/main/java/com/baeldung/reactive/security/SecurityConfig.java @@ -16,40 +16,37 @@ import org.springframework.security.web.server.SecurityWebFilterChain; public class SecurityConfig { @Bean - public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) { + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http.authorizeExchange() - .pathMatchers("/admin") - .hasAuthority("ROLE_ADMIN") - .anyExchange() - .authenticated() - .and() - .formLogin() - .and() - .csrf() - .disable() - .build(); + .pathMatchers("/admin").hasAuthority("ROLE_ADMIN") + .anyExchange().authenticated() + .and() + .formLogin() + .and() + .csrf().disable() + .build(); } @Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User - .withUsername("user") - .password(passwordEncoder().encode("password")) - .roles("USER") - .build(); + .withUsername("user") + .password(passwordEncoder().encode("password")) + .roles("USER") + .build(); UserDetails admin = User - .withUsername("admin") - .password(passwordEncoder().encode("password")) - .roles("ADMIN") - .build(); + .withUsername("admin") + .password(passwordEncoder().encode("password")) + .roles("ADMIN") + .build(); return new MapReactiveUserDetailsService(user, admin); } - + @Bean public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + return new BCryptPasswordEncoder(); } } diff --git a/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java b/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java index 06644fbf77..0ef828df5a 100644 --- a/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java +++ b/spring-reactive/src/test/java/com/baeldung/reactive/security/SecurityIntegrationTest.java @@ -15,23 +15,32 @@ import org.springframework.test.web.reactive.server.WebTestClient; public class SecurityIntegrationTest { @Autowired - ApplicationContext context; + private ApplicationContext context; - private WebTestClient rest; + private WebTestClient webTestClient; @BeforeEach public void setup() { - this.rest = WebTestClient.bindToApplicationContext(this.context).configureClient().build(); + webTestClient = WebTestClient.bindToApplicationContext(context) + .configureClient() + .build(); } @Test public void whenNoCredentials_thenRedirectToLogin() { - this.rest.get().uri("/").exchange().expectStatus().is3xxRedirection(); + webTestClient.get() + .uri("/") + .exchange() + .expectStatus().is3xxRedirection(); } @Test @WithMockUser public void whenHasCredentials_thenSeesGreeting() { - this.rest.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello, user"); + webTestClient.get() + .uri("/") + .exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo("Hello, user"); } } From 4db9e493d827a9d7cff36c6581a778f751e64693 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 16:47:23 +0800 Subject: [PATCH 19/24] Update README.md --- apache-cxf-modules/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apache-cxf-modules/README.md b/apache-cxf-modules/README.md index 060d16af3f..cd45c371fe 100644 --- a/apache-cxf-modules/README.md +++ b/apache-cxf-modules/README.md @@ -4,5 +4,4 @@ This module contains articles about Apache CXF ## Relevant Articles: -- [Introduction to Apache CXF](https://www.baeldung.com/introduction-to-apache-cxf) - [Introduction to Apache CXF Aegis Data Binding](https://www.baeldung.com/aegis-data-binding-in-apache-cxf) From e506064ce2a657bf193599ff1994eaedadf23df6 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 16:56:15 +0800 Subject: [PATCH 20/24] Update README.md --- docker-modules/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-modules/README.md b/docker-modules/README.md index b2ae5d56c9..f5daa19b08 100644 --- a/docker-modules/README.md +++ b/docker-modules/README.md @@ -2,7 +2,6 @@ - [Introduction to Docker Compose](https://www.baeldung.com/ops/docker-compose) - [Reusing Docker Layers with Spring Boot](https://www.baeldung.com/docker-layers-spring-boot) -- [Running Spring Boot with PostgreSQL in Docker Compose](https://www.baeldung.com/spring-boot-postgresql-docker) - [How To Configure Java Heap Size Inside a Docker Container](https://www.baeldung.com/ops/docker-jvm-heap-size) - [Dockerfile Strategies for Git](https://www.baeldung.com/ops/dockerfile-git-strategies) - [How to Get Docker-Compose to Always Use the Latest Image](https://www.baeldung.com/ops/docker-compose-latest-image) From 5effbad6110cd6c6d475cb3200c678ce8c711c40 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:18:21 +0800 Subject: [PATCH 21/24] Update README.md --- core-java-modules/core-java-collections-list-3/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/core-java-modules/core-java-collections-list-3/README.md b/core-java-modules/core-java-collections-list-3/README.md index 6d0a3c7037..ecae0dda7d 100644 --- a/core-java-modules/core-java-collections-list-3/README.md +++ b/core-java-modules/core-java-collections-list-3/README.md @@ -13,5 +13,4 @@ This module contains articles about the Java List collection - [Finding the Differences Between Two Lists in Java](https://www.baeldung.com/java-lists-difference) - [List vs. ArrayList in Java](https://www.baeldung.com/java-list-vs-arraylist) - [How to Store HashMap Inside a List](https://www.baeldung.com/java-hashmap-inside-list) -- [Working With a List of Lists in Java](https://www.baeldung.com/java-list-of-lists) - [[<-- Prev]](/core-java-modules/core-java-collections-list-2) From 6b5909f5ce5623d338d7d7ec3d8c970670fbfbe9 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:30:19 +0800 Subject: [PATCH 22/24] Delete README.md --- docker-modules/docker-sample-app/README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docker-modules/docker-sample-app/README.md diff --git a/docker-modules/docker-sample-app/README.md b/docker-modules/docker-sample-app/README.md deleted file mode 100644 index 6aeaa1d2a3..0000000000 --- a/docker-modules/docker-sample-app/README.md +++ /dev/null @@ -1,3 +0,0 @@ -### Relevant Articles: - -- How to Get Docker-Compose to Always Use the Latest Image From 1d1830c8680443b6d75636e6bd8b02be6d0c5be2 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Tue, 7 Jun 2022 19:24:05 +0800 Subject: [PATCH 23/24] Create README.md --- docker-modules/heap-sizing/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docker-modules/heap-sizing/README.md diff --git a/docker-modules/heap-sizing/README.md b/docker-modules/heap-sizing/README.md new file mode 100644 index 0000000000..aa5167cc16 --- /dev/null +++ b/docker-modules/heap-sizing/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [How To Configure Java Heap Size Inside a Docker Container](https://www.baeldung.com/ops/docker-jvm-heap-size) From 74dbf0d0c9cb5535a22210ca60e653e40957076f Mon Sep 17 00:00:00 2001 From: Avin Buricha Date: Tue, 7 Jun 2022 21:02:10 +0530 Subject: [PATCH 24/24] BAEL-5486 Adding Parameters to Java HttpClient Requests (#11996) * BAEL-5486 | Article Code * BAEL-5486 | Remove comments and format fix * BAEL-5486 | Add code sample * BAEL-5486 | Common code extracted to a method * BAEL-5486 | Use static import for Assertions * BAEL-5486 | Removed external library * BAEL-5486 | Removed BodyPublisher examples * BAEL-5486 | Code examples added * BAEL-5486 | Removed extra Class Co-authored-by: Avin Buricha --- .../HttpClientParametersLiveTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 core-java-modules/core-java-11-2/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java diff --git a/core-java-modules/core-java-11-2/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java b/core-java-modules/core-java-11-2/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java new file mode 100644 index 0000000000..429825c550 --- /dev/null +++ b/core-java-modules/core-java-11-2/src/test/java/com/baeldung/httpclient/parameters/HttpClientParametersLiveTest.java @@ -0,0 +1,45 @@ +package com.baeldung.httpclient.parameters; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HttpClientParametersLiveTest { + + private static HttpClient client; + + @BeforeAll + public static void setUp() { + client = HttpClient.newHttpClient(); + } + + @Test + public void givenQueryParams_whenGetRequest_thenResponseOk() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .version(HttpClient.Version.HTTP_2) + .uri(URI.create("https://postman-echo.com/get?param1=value1¶m2=value2")) + .GET() + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(response.statusCode(), 200); + } + + @Test + public void givenQueryParams_whenGetRequestWithDefaultConfiguration_thenResponseOk() throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://postman-echo.com/get?param1=value1¶m2=value2")) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + assertEquals(response.statusCode(), 200); + } + +}