Custom JWT Auth with Spring Webflux - spring

I'm trying to setup authentication with JWT with Spring Security inside a Spring WebFlux application. I'm using a custom authorization scheme based on custom JWT claims. The problem I'm having is that when I try to invoke a secured endpoint authentication fails with Authentication failed: An Authentication object was not found in the SecurityContext.
Here is the SecurityWebFilterChain I'm using:
#Configuration
#EnableWebFluxSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfiguration {
#Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
jwt {
jwtAuthenticationConverter = grantedAuthoritiesExtractor()
}
}
}
}
fun grantedAuthoritiesExtractor(): Converter<Jwt, Mono<AbstractAuthenticationToken>> {
val jwtAuthenticationConverter = JwtAuthenticationConverter()
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(MappingJwtAuthoritiesConverter())
// custom JWT -> Collection<GrantedAuthority> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter)
}
#Bean
fun jwtDecoder(): ReactiveJwtDecoder {
val secretKey: SecretKey = SecretKeySpec("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".toByteArray(), "HMACSHA256")
return NimbusReactiveJwtDecoder.withSecretKey(secretKey).macAlgorithm(MacAlgorithm.HS256).build()
}
#Bean
fun jwtValidator(): OAuth2TokenValidator<Jwt> {
return OAuth2TokenValidator<Jwt> { OAuth2TokenValidatorResult.success() }
}
fun jwtAuthenticationManager(jwtDecoder: ReactiveJwtDecoder): JwtReactiveAuthenticationManager {
return JwtReactiveAuthenticationManager(jwtDecoder).apply {
this.setJwtAuthenticationConverter(grantedAuthoritiesExtractor())
}
}
}
Here is MappingJwtAuthoritiesConverter:
class MappingJwtAuthoritiesConverter : Converter<Jwt, Collection<GrantedAuthority>> {
companion object {
private val WELL_KNOWN_CLAIMS: Set<String> = setOf("myCustomClaim")
}
override fun convert(jwt: Jwt): Collection<GrantedAuthority> {
val authorities = jwt.claims.entries
.filter { (key, _) -> key in WELL_KNOWN_CLAIMS }
.map { (key, value) ->
return#map SimpleGrantedAuthority("$key:$value")
}
return authorities
}
}
I searched online but many JWT/Spring Webflux implementation hand-roll JWT validation and handling, and I'd rather use what's already offered by Spring under OAuth integration. Right now the only custom piece I'm using is the converter from JWT to GrantedAuthority, but I still can't get authentication to work.
With the following JWT:
header:
{
"typ": "JWT",
"alg": "HS256"
}
payload:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1658474926,
"exp": 1668478526,
"myCustomClaim": "READ"
}
Encoded:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNjU4NDc0OTI2LCJleHAiOjE2Njg0Nzg1MjYsIm15Q3VzdG9tQ2xhaW0iOiJSRUFEIn0.lh66pHMd_xvXAF2itblHeHbZReJQA5xkGLKqXZV6MjI
The endpoint I'm trying to secure is defined as:
#RestController
class FooController {
#PreAuthorize("hasAuthority('myCustomClaim:READ')")
#RequestMapping(
method = [RequestMethod.GET],
value = ["/foo"],
)
override suspend fun getFoo(): ResponseEntity<String> {
return ResponseEntity.ok("Got foo")
}
}
Spring logs:
2022-07-22 09:43:35.138 DEBUG 508191 --- [ctor-http-nio-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [b7fe0c9e-1] HTTP GET "/foo"
2022-07-22 09:43:35.313 DEBUG 508191 --- [ parallel-2] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
2022-07-22 09:43:35.319 DEBUG 508191 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers$1#3602a026
2022-07-22 09:43:35.319 DEBUG 508191 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : matched
2022-07-22 09:43:35.319 DEBUG 508191 --- [ parallel-2] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/foo' using org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager#2668fdab
2022-07-22 09:43:35.320 DEBUG 508191 --- [ parallel-2] o.s.s.w.s.a.AuthorizationWebFilter : Authorization successful
2022-07-22 09:43:35.325 DEBUG 508191 --- [ parallel-2] s.w.r.r.m.a.RequestMappingHandlerMapping : [b7fe0c9e-1] Mapped to it.project.backend.controllers.FooController#getFoo(Continuation)
2022-07-22 09:43:35.665 DEBUG 508191 --- [ parallel-2] o.s.s.w.s.a.AuthenticationWebFilter : Authentication failed: An Authentication object was not found in the SecurityContext
2022-07-22 09:43:35.694 DEBUG 508191 --- [ parallel-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [b7fe0c9e-1] Completed 401 UNAUTHORIZED
Any ideas?

The problem was using #EnableGlobalMethodSecurity instead of #EnableReactiveMethodSecurity in the configuration bean. After fixing that everything started to work.

Looks like the secret key you constructed your jwtDecoder with does not correspond to the signature of the token you posted and thus the token validation fails.
You can use https://jwt.io/ to check that.

Related

Kotlin Spring Boot CORS "No mapping for OPTIONS /users"

I have tried every solution I could find on stack overflow for my problem but nothing worked.
I have a CORS configuration in my kotlin app.
#Configuration
#EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedOrigins("*")
.allowedHeaders("*");
}
}
And I have this controller:
#RestController
#RequestMapping("users")
class UserController(
val userService: UserService
) {
#CrossOrigin
#PostMapping("/", produces = ["application/json"])
fun createUser(#RequestBody user: User): User {
return userService.createUser(user)
}
}
When I call that endpoint from my Angular application I get the following error:
WARN 19441 --- [nio-8080-exec-2] o.s.web.servlet.PageNotFound : No mapping for OPTIONS /users
I can't seem to have the Global CORS working. How have you done this in Kotlin?
Thank you!
The URL you are calling is incorrect.
In your #PostMapping you registered /users/ and not /users.
To fix this change your post mapping to #PostMapping(produces = ["application/json"]) and it should be working.

Spring Webflux Security blocking Kafka GET /connectors calls

I have a spring boot Webflux application. I am using Kafka with Apache camel and also using websockets.
I recently added Spring security. For I do not need Authentication therefore I am not authenticating but only authorizing the incoming requests.
Since adding the security it is blocking Kafka Connect's REST API endpoints eg. GET /connectors call. IMO, it should not be blocking them as they would be raised from inside the applications.
Error Logs
2021-07-29 22:20:51.654 DEBUG 68377 --- [ctor-http-nio-4] o.s.w.s.adapter.HttpWebHandlerAdapter : [adf2f930-1, L:/127.0.0.1:8083 - R:/127.0.0.1:60653] HTTP GET "/connectors"
2021-07-29 22:20:51.659 DEBUG 68377 --- [ parallel-3] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/weather-data/api/**', method=GET}
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] athPatternParserServerWebExchangeMatcher : Request 'GET /connectors' doesn't match 'GET /weather-data/api/**'
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/weather-data/api/**', method=POST}
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] athPatternParserServerWebExchangeMatcher : Request 'GET /connectors' doesn't match 'POST /weather-data/api/**'
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/weather-data/api/events', method=GET}
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] athPatternParserServerWebExchangeMatcher : Request 'GET /connectors' doesn't match 'GET /weather-data/api/events'
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
2021-07-29 22:20:51.660 DEBUG 68377 --- [ parallel-3] o.s.s.w.s.a.AuthorizationWebFilter : Authorization failed: Access Denied
2021-07-29 22:20:51.669 INFO 68377 --- [ parallel-3] c.s.M.w.WebFluxSecurityConfiguration : Authentication exception
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: Not Authenticated
at org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter.commenceAuthentication(ExceptionTranslationWebFilter.java:70) ~[spring-security-web-5.5.0.jar:5.5.0]
at org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter.lambda$filter$1(ExceptionTranslationWebFilter.java:45) ~[spring-security-web-5.5.0.jar:5.5.0]
1. Webflux Security configuration
package com.reactive.sse.security
import mu.KotlinLogging
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.AuthenticationWebFilter
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
#Configuration
#EnableWebFluxSecurity
class SecurityConfiguration {
private val logger = KotlinLogging.logger {}
val READ_ROLE = "DATA_READ"
#Bean
fun securityWebFilterChain(
http: ServerHttpSecurity,
weatherAuthenticatedRequestManager: WeatherAuthenticatedRequestManager,
authenticationConverter: ServerAuthenticationConverter
): SecurityWebFilterChain {
val authenticationWebFilter = AuthenticationWebFilter(weatherAuthenticatedRequestManager)
authenticationWebFilter.setServerAuthenticationConverter(authenticationConverter)
return http
.exceptionHandling()
.authenticationEntryPoint { swe: ServerWebExchange, ex: AuthenticationException? ->
logger.error(ex) { "Authentication exception" }
Mono.fromRunnable { swe.response.statusCode = HttpStatus.UNAUTHORIZED }
}.accessDeniedHandler { swe: ServerWebExchange, ex: AccessDeniedException? ->
logger.error(ex) { "Authorization exception" }
Mono.fromRunnable { swe.response.statusCode = HttpStatus.FORBIDDEN }
}.and()
.authorizeExchange()
.pathMatchers(HttpMethod.GET,"/weather-data/api/**").hasRole(READ_ROLE)
.and()
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.logout().disable()
.build()
}
}
2. WeatherAuthenticationConverter
package com.reactive.sse.security
import org.springframework.http.HttpCookie
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
#Component
class WeatherAuthenticationConverter : ServerAuthenticationConverter {
override fun convert(exchange: ServerWebExchange?): Mono<Authentication> {
return Mono.justOrEmpty(exchange)
.flatMap { serverWebExchange: ServerWebExchange ->
Mono.justOrEmpty(serverWebExchange.request.cookies["X-Auth"])
}
.filter { cookies: List<HttpCookie> -> cookies.isNotEmpty() }
.map { cookies: List<HttpCookie> -> cookies[0].value }
.map { authenticationStr: String ->
UsernamePasswordAuthenticationToken(
authenticationStr,
authenticationStr
)
}
}
}
3. WeatherAuthenticatedRequestManager
package com.reactive.sse.security
import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
#Component
class WeatherAuthenticatedRequestManager : ReactiveAuthenticationManager {
/**
* Authenticate based on the Jwt token
*
* #property authentication
* #return Authentication Publisher
*/
override fun authenticate(authentication: Authentication): Mono<Authentication> {
return Mono.just(authentication)
.map { authenticationObj: Authentication ->
val roles: List<String> = (authenticationObj.credentials as List<*>).filterIsInstance<String>()
val rolesAuthority = roles.map { value -> SimpleGrantedAuthority(value) }
UsernamePasswordAuthenticationToken(
roles,
roles,
rolesAuthority
)
}
}
}
How should I configure by spring security so that it does not block the internal calls like this.
After doing google I found /connectors API call initiated by Kafka. It runs on port 8083. My spring boot application unfortunately running on 8083. Therefore It was blocked. I am still confused why it was blocking the internal calls.
As soon as I changed the port to 8089 or something else, error goes away

How to mock custom JWT claims in #WebMvcTest

I am using Spring Boot 2.2.0.RELEASE and my Spring-based backend acts as an OAuth2 Resource server which runs fine in production.
All my REST endpoints are protected :
public class BookingController {
#PreAuthorize("hasAuthority('booking:WRITE')")
#PostMapping(value = "/book")
public ResponseEntity<Void> createBooking(#RequestBody BookModel bookModel, JwtAuthenticationToken jwt) {..}
I wanted to write a unit test against my REST APIs and I would like to mock the JWT token.
I tried the following but I always get the "Access denied message"
My Unit test looks like the following:
#WebMvcTest(controllers = BookingController.class)
public class BookingControllerTests {
#Autowired
private ObjectMapper objectMapper;
#Autowired
MockMvc mockMvc;
#MockBean
JwtDecoder jwtDecoder;
#Test
public void when_valid_booking_then_return_200() {
BookModel bookModel = new BookModel();
mockMvc
.perform(post("/book")
.with(jwt(jwt ->jwt().authorities(new SimpleGrantedAuthority("booking:WRITE"))))
.contentType("application/json")
.content(objectMapper.writeValueAsBytes(bookModel)))
.andExpect(status().isCreated());
}
Somehow the claims which are defined in mockMvc are ignored. See the debug output :
PrePostAnnotationSecurityMetadataSource : #org.springframework.security.access.prepost.PreAuthorize(value=hasAuthority('booking:WRITE')) found on specific method: public org.springframework.http.ResponseEntity BookingController.createBooking(BookModel ,org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken)
o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /book; Attributes: [permitAll]
o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken#eca97305: Principal: org.springframework.security.oauth2.jwt.Jwt#bbd01fb9; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: SCOPE_read
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#18907af2, returned: 1
o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
o.s.s.w.FilterChainProxy : /book reached end of additional filter chain; proceeding with original chain
o.s.s.a.i.a.MethodSecurityInterceptor : Secure object: ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity BookingController.createBooking(BookModel,org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken); target is of class [BookModel]; Attributes: [[authorize: 'hasAuthority('booking:WRITE')', filter: 'null', filterTarget: 'null']]
o.s.s.a.i.a.MethodSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken#eca97305: Principal: org.springframework.security.oauth2.jwt.Jwt#bbd01fb9; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: SCOPE_read
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#67afe909, returned: -1
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter#79f1e22e, returned: 0
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter#6903ed0e, returned: 0
c.s.d.r.e.GlobalExceptionHandler : mapped AccessDeniedException to FORBIDDEN
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
In your lambda for mocking the JWT you are calling the post processor twice by using the parentheses twice .with(jwt(jwt ->jwt()...))
Instead, try
mockMvc
.perform(post("/book")
.with(jwt().authorities(new SimpleGrantedAuthority("booking:WRITE"))))
If you need the security context up, then you are not writing an unit-test.
Anyway, why not to use #WithMockUser?
Here you can see a snapshot of how to use it in an Integration Test which is mocking the Business Layer.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("api")
public class GetAllProfilesControllerITest {
#MockBean
private GetAllProfilesDAO getAllProfilesDAO;
#MockBean
private ProfileControllerMapper profileControllerMapper;
#Inject
private WebApplicationContext context;
private MockMvc mockMvc;
private static final String ENDPOINT = "/profiles";
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#WithMockUser(authorities = "MINION")
#Test
public void should_returnUnauthorized_when_cantView() throws Exception {
//when
mockMvc.perform(get(ENDPOINT))
//then
.andExpect(status().isUnauthorized());
}
#WithMockUser(authorities = {"VIEW", "CREATE"})
#Test
public void should_returnOk_when_canView() throws Exception {
//when
mockMvc.perform(get(ENDPOINT))
//then
.andExpect(status().isOk());
}
#WithMockUser(authorities = "PUPPY")
#Test
public void should_returnOk_when_puppy() throws Exception {
//when
mockMvc.perform(get(ENDPOINT))
//then
.andExpect(status().isOk());
}
}
MvcResult result =
mvc.perform(
MockMvcRequestBuilders.get("/v1/path/example")
.with(jwt(token -> token.claim("claimKey","claimValue"))
.accept(MediaType.APPLICATION_JSON))
.andReturn();
to use this you have to use mock mvc. so annotate your test class with #AutoConfigureMockMvc
and inject MockMvc
#Autowired private MockMvc mvc;

Spring Boot Swagger HTML documentation is not getting displayed

I am setting up a REST API / Micro-services project using Spring Boot. I am also trying to enable swagger documentation. When I run spring boot application, I am able to see browse to the localhost:8080/v2/api-docs and it returns API documentation. However, when I attempt browsing http://localhost:8080/documentation/swagger-ui.html or http://localhost:8080/swagger-ui.html browser does not display swagger UI documentation.
I have included following dependencies in the pom file to enable documentation :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Also, I have create following swagger config class:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2);
}
}
The response I am seeing when trying to access http://localhost:8080/servlet-context :
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Dec 21 03:16:26 GMT 2018
There was an unexpected error (type=Not Found, status=404).
No message available
Server Logs :
2018-12-21 03:25:14.294 DEBUG 4049 --- [ restartedMain] o.s.c.e.PropertySourcesPropertyResolver : Found key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties' with value of type String
2018-12-21 03:25:14.351 INFO 4049 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-21 03:25:14.355 INFO 4049 --- [ restartedMain] c.xxxx.springboot.demo.DemoApplication : Started DemoApplication in 3.951 seconds (JVM running for 4.723)
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Creating new Restarter for thread Thread[main,5,main]
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Immediately restarting application
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader#1b3b349
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Starting application com.xxxx.springboot.demo.DemoApplication with URLs [file:/Users/XXXXX/XXXXX/SpringMicroservices/SpringBoot/demo/target/classes/]
2018-12-21 03:25:14.763 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.c.e.PropertySourcesPropertyResolver : Found key 'local.server.port' in PropertySource 'server.ports' with value of type Integer
2018-12-21 03:25:14.995 INFO 4049 --- [on(2)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2018-12-21 03:25:14.995 INFO 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2018-12-21 03:25:14.995 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
2018-12-21 03:25:14.995 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Detected AcceptHeaderLocaleResolver
2018-12-21 03:25:15.003 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
Please find below one of the controllers:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
#RestController
public class UserResource {
#Autowired
private UserDaoService service;
#GetMapping("/users")
public List<User> retrieveAllUsers(){
return service.findAll();
}
#GetMapping("/users/{id}")
public Resource<User> retrieveUser(#PathVariable int id){
User user = service.findOne(id);
if(user==null)
throw new UserNotFoundEception("id-" + id);
Resource<User> resource = new Resource<User>(user);
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
return resource;
}
#PostMapping("/users")
public ResponseEntity<Object> CreateUser(#Valid #RequestBody User user){
User savedUser = service.save(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
#DeleteMapping("/users/{id}")
public void deleteUser(#PathVariable int id){
User user = service.deleteById(id);
if(user==null)
throw new UserNotFoundEception("id-" + id);
}
}
package com.example.config
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.base.Predicate;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SwaggerConfig {
public #Bean Docket restApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().paths(paths()).build();
}
/**
* Url for documentation.
*
* #return
*/
private Predicate<String> paths() {
return regex("/basepackage of your restcontroller/*");
}
/**
* Description your application
*
* #return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("App title").description("App description")
.termsOfServiceUrl("").license("").licenseUrl("").version("1.0").build();
}
}
Here is an example of controller :
#Api(value = "membre")
#RestController
public class ExampleControleur {
#RequestMapping(value = "/find", method = RequestMethod.GET, produces = {
MediaType.APPLICATION_JSON_VALUE }, consumes = { MediaType.APPLICATION_JSON_VALUE })
#ApiOperation(httpMethod = "GET", value = "example text", notes = "note text ")
#ResponseBody
#ApiResponses(value = { #ApiResponse(code = 200, message = "OK !"),
#ApiResponse(code = 422, message = "description."),
#ApiResponse(code = 401, message = "description"),
#ApiResponse(code = 403, message = "description"),
#ApiResponse(code = 404, message = "description"),
#ApiResponse(code = 412, message = "description."),
#ApiResponse(code = 500, message = "description.") })
#ApiImplicitParams({
#ApiImplicitParam(name = "params1", value = "description.", required = true, dataType = "string", paramType = "query", defaultValue = "op"),
#ApiImplicitParam(name = "params2", value = "description.", required = true, dataType = "string", paramType = "query")})
public ResponseEntity<MyObject> getObjet(#RequestParam(value = "params1", required = true) Params1 params1,
#RequestParam(value = "params2", required = true) String param2){
}
}
You can test with this class, but i suppose that you have written all the swagger annotations in your controllers !
You can add to your main class #ComponentScan(basePackages={"the package of your config swagger class"})
Th acces url must be http://localhost:port/context-path if you have it/swagger-ui.html

Testing Spring Boot Eureka Server 404

I'm trying to test authentication in my Spring Boot Eureka Server. To do so, I perform a GET on /eureka/apps. I get a 404 instead of 200.
#RunWith(SpringRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
public class GlobalSecurityTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
#Test
public void givenRoleDiscoveryClient_whenGetEureka_then200() throws Exception {
mockMvc.perform(get("/eureka/apps").header(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT)
.andExpect(status().isOk());
}
}
Eureka starts correctly as the logs prove:
2018-04-12 23:07:39.308 INFO 80833 --- [ Thread-12] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2018-04-12 23:07:39.315 INFO 80833 --- [ main] GlobalSecurityTest : Started GlobalSecurityTest in 7.255 seconds (JVM running for 8.007)
...
2018-04-12 23:07:39.822 DEBUG 80833 --- [ main] o.s.security.web.FilterChainProxy : /eureka/apps/REGISTRY reached end of additional filter chain; proceeding with original chain
2018-04-12 23:07:39.831 DEBUG 80833 --- [ main] w.c.HttpSessionSecurityContextRepository : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl#0: Authentication: StateTokenAuthentication{principalTokenState=be.charliebravo.ibpt.qos3.commons.security.models.ClientState#50b624da, tokenStates={}}' stored to HttpSession: 'org.springframework.mock.web.MockHttpSession#50b4e7b2
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
java.lang.AssertionError: Status
Expected :200
Actual :404
My security config:
#Configuration
public class WebSecurityConfig {
#Configuration
#Order(3)
public static class DiscoveryClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private StateTokenHttpSecurityConfigurer stateTokenHttpSecurityConfigurer;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/eureka/**").authorizeRequests()
.anyRequest().hasRole(Role.DISCOVERY_CLIENT.toString())
.and().exceptionHandling().authenticationEntryPoint(new Http401UnauthorizedEntryPoint());
stateTokenHttpSecurityConfigurer.configure(http);
}
}
}
The Eureka server works fine when I run the application instead of the test.
Don't use MockMvc, because it is limited to testing the web layer, but Eureka mappings aren't registered there. Instead, use TestRestTemplate.
Remove #WebAppConfiguration and add weEnvironment setting in #SpringBootTest
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Autowire TestRestTemplate and local server port
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
private int localServerPort;
Perform the request
#Test
public void givenRoleDiscoveryClient_whenGetEurekaPage_then200() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT);
HttpEntity entity = new HttpEntity<>(null, headers);
String endpoint = "https://localhost:" + localServerPort + "/eureka/apps";
ResponseEntity responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, entity, String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
And off you go.

Resources