Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
3f00ba3c
Commit
3f00ba3c
authored
Nov 03, 2017
by
Phillip Webb
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Polish
parent
200eb8f5
Changes
21
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
454 additions
and
399 deletions
+454
-399
AccessLevel.java
.../boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java
+1
-0
CloudFoundryAuthorizationException.java
...gure/cloudfoundry/CloudFoundryAuthorizationException.java
+3
-1
SecurityResponse.java
.../actuate/autoconfigure/cloudfoundry/SecurityResponse.java
+1
-0
Token.java
...mework/boot/actuate/autoconfigure/cloudfoundry/Token.java
+6
-10
CloudFoundryWebFluxEndpointHandlerMapping.java
...y/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java
+51
-43
ReactiveCloudFoundryActuatorAutoConfiguration.java
...active/ReactiveCloudFoundryActuatorAutoConfiguration.java
+31
-21
ReactiveCloudFoundrySecurityInterceptor.java
...dry/reactive/ReactiveCloudFoundrySecurityInterceptor.java
+16
-14
ReactiveCloudFoundrySecurityService.java
...foundry/reactive/ReactiveCloudFoundrySecurityService.java
+40
-35
ReactiveTokenValidator.java
...nfigure/cloudfoundry/reactive/ReactiveTokenValidator.java
+18
-29
CloudFoundryActuatorAutoConfiguration.java
...oundry/servlet/CloudFoundryActuatorAutoConfiguration.java
+4
-6
CloudFoundrySecurityInterceptor.java
...cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java
+4
-8
CloudFoundryWebEndpointServletHandlerMapping.java
...servlet/CloudFoundryWebEndpointServletHandlerMapping.java
+5
-5
TokenValidator.java
...te/autoconfigure/cloudfoundry/servlet/TokenValidator.java
+2
-2
CloudFoundryWebFluxEndpointIntegrationTests.java
...reactive/CloudFoundryWebFluxEndpointIntegrationTests.java
+8
-6
ReactiveCloudFoundryActuatorAutoConfigurationTests.java
...e/ReactiveCloudFoundryActuatorAutoConfigurationTests.java
+27
-23
ReactiveCloudFoundrySecurityInterceptorTests.java
...eactive/ReactiveCloudFoundrySecurityInterceptorTests.java
+53
-57
ReactiveCloudFoundrySecurityServiceTests.java
...ry/reactive/ReactiveCloudFoundrySecurityServiceTests.java
+83
-58
ReactiveTokenValidatorTests.java
...re/cloudfoundry/reactive/ReactiveTokenValidatorTests.java
+73
-48
CloudFoundrySecurityInterceptorTests.java
...foundry/servlet/CloudFoundrySecurityInterceptorTests.java
+8
-16
AbstractWebFluxEndpointHandlerMapping.java
...t/web/reactive/AbstractWebFluxEndpointHandlerMapping.java
+13
-14
WebFluxEndpointHandlerMapping.java
.../endpoint/web/reactive/WebFluxEndpointHandlerMapping.java
+7
-3
No files found.
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java
View file @
3f00ba3c
...
...
@@ -24,6 +24,7 @@ import java.util.List;
* endpoints.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public
enum
AccessLevel
{
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryAuthorizationException.java
View file @
3f00ba3c
...
...
@@ -22,6 +22,7 @@ import org.springframework.http.HttpStatus;
* Authorization exceptions thrown to limit access to the endpoints.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public
class
CloudFoundryAuthorizationException
extends
RuntimeException
{
...
...
@@ -31,7 +32,8 @@ public class CloudFoundryAuthorizationException extends RuntimeException {
this
(
reason
,
message
,
null
);
}
public
CloudFoundryAuthorizationException
(
Reason
reason
,
String
message
,
Throwable
cause
)
{
public
CloudFoundryAuthorizationException
(
Reason
reason
,
String
message
,
Throwable
cause
)
{
super
(
message
);
this
.
reason
=
reason
;
}
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/SecurityResponse.java
View file @
3f00ba3c
...
...
@@ -22,6 +22,7 @@ import org.springframework.http.HttpStatus;
* Response from the Cloud Foundry security interceptors.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public
class
SecurityResponse
{
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/Token.java
View file @
3f00ba3c
...
...
@@ -29,6 +29,7 @@ import org.springframework.util.StringUtils;
* The JSON web token provided with each request that originates from Cloud Foundry.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public
class
Token
{
...
...
@@ -47,16 +48,14 @@ public class Token {
int
firstPeriod
=
encoded
.
indexOf
(
'.'
);
int
lastPeriod
=
encoded
.
lastIndexOf
(
'.'
);
if
(
firstPeriod
<=
0
||
lastPeriod
<=
firstPeriod
)
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"JWT must have header, body and signature"
);
}
this
.
header
=
parseJson
(
encoded
.
substring
(
0
,
firstPeriod
));
this
.
claims
=
parseJson
(
encoded
.
substring
(
firstPeriod
+
1
,
lastPeriod
));
this
.
signature
=
encoded
.
substring
(
lastPeriod
+
1
);
if
(!
StringUtils
.
hasLength
(
this
.
signature
))
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"Token must have non-empty crypto segment"
);
}
}
...
...
@@ -67,8 +66,7 @@ public class Token {
return
JsonParserFactory
.
getJsonParser
().
parseMap
(
new
String
(
bytes
,
UTF_8
));
}
catch
(
RuntimeException
ex
)
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"Token could not be parsed"
,
ex
);
}
}
...
...
@@ -106,13 +104,11 @@ public class Token {
private
<
T
>
T
getRequired
(
Map
<
String
,
Object
>
map
,
String
key
,
Class
<
T
>
type
)
{
Object
value
=
map
.
get
(
key
);
if
(
value
==
null
)
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"Unable to get value from key "
+
key
);
}
if
(!
type
.
isInstance
(
value
))
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"Unexpected value type from key "
+
key
+
" value "
+
value
);
}
return
(
T
)
value
;
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java
View file @
3f00ba3c
...
...
@@ -28,6 +28,7 @@ import org.reactivestreams.Publisher;
import
reactor.core.publisher.Mono
;
import
org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel
;
import
org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse
;
import
org.springframework.boot.actuate.endpoint.EndpointInfo
;
import
org.springframework.boot.actuate.endpoint.OperationInvoker
;
import
org.springframework.boot.actuate.endpoint.OperationType
;
...
...
@@ -58,7 +59,8 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
*
* @author Madhura Bhave
*/
public
class
CloudFoundryWebFluxEndpointHandlerMapping
extends
AbstractWebFluxEndpointHandlerMapping
{
class
CloudFoundryWebFluxEndpointHandlerMapping
extends
AbstractWebFluxEndpointHandlerMapping
{
private
final
Method
handleRead
=
ReflectionUtils
.
findMethod
(
ReadOperationHandler
.
class
,
"handle"
,
ServerWebExchange
.
class
);
...
...
@@ -85,38 +87,38 @@ public class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEn
if
(
operation
.
isBlocking
())
{
operationInvoker
=
new
ElasticSchedulerOperationInvoker
(
operationInvoker
);
}
registerMapping
(
createRequestMappingInfo
(
operation
),
operationType
==
OperationType
.
WRITE
?
new
WriteOperationHandler
(
operationInvoker
,
operation
.
getId
())
:
new
ReadOperationHandler
(
operationInvoker
,
operation
.
getId
()),
operationType
==
OperationType
.
WRITE
?
this
.
handleWrite
:
this
.
handleRea
d
);
Object
handler
=
(
operationType
==
OperationType
.
WRITE
?
new
WriteOperationHandler
(
operationInvoker
,
operation
.
getId
())
:
new
ReadOperationHandler
(
operationInvoker
,
operation
.
getId
()));
Method
method
=
(
operationType
==
OperationType
.
WRITE
?
this
.
handleWrite
:
this
.
handleRead
);
registerMapping
(
createRequestMappingInfo
(
operation
),
handler
,
metho
d
);
}
@ResponseBody
private
Publisher
<
ResponseEntity
<
Object
>>
links
(
ServerWebExchange
exchange
)
{
ServerHttpRequest
request
=
exchange
.
getRequest
();
return
this
.
securityInterceptor
.
preHandle
(
exchange
,
""
)
.
map
(
securityResponse
->
{
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
return
new
ResponseEntity
<>(
securityResponse
.
getStatus
());
}
AccessLevel
accessLevel
=
exchange
.
getAttribute
(
AccessLevel
.
REQUEST_ATTRIBUTE
);
Map
<
String
,
Link
>
links
=
this
.
endpointLinksResolver
.
resolveLinks
(
getEndpoints
(),
request
.
getURI
().
toString
());
return
new
ResponseEntity
<>(
Collections
.
singletonMap
(
"_links"
,
getAccessibleLinks
(
accessLevel
,
links
)),
HttpStatus
.
OK
);
});
return
this
.
securityInterceptor
.
preHandle
(
exchange
,
""
).
map
(
securityResponse
->
{
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
return
new
ResponseEntity
<>(
securityResponse
.
getStatus
());
}
AccessLevel
accessLevel
=
exchange
.
getAttribute
(
AccessLevel
.
REQUEST_ATTRIBUTE
);
Map
<
String
,
Link
>
links
=
this
.
endpointLinksResolver
.
resolveLinks
(
getEndpoints
(),
request
.
getURI
().
toString
());
return
new
ResponseEntity
<>(
Collections
.
singletonMap
(
"_links"
,
getAccessibleLinks
(
accessLevel
,
links
)),
HttpStatus
.
OK
);
});
}
private
Map
<
String
,
Link
>
getAccessibleLinks
(
AccessLevel
accessLevel
,
Map
<
String
,
Link
>
links
)
{
private
Map
<
String
,
Link
>
getAccessibleLinks
(
AccessLevel
accessLevel
,
Map
<
String
,
Link
>
links
)
{
if
(
accessLevel
==
null
)
{
return
new
LinkedHashMap
<>();
}
return
links
.
entrySet
().
stream
()
.
filter
((
e
)
->
e
.
getKey
().
equals
(
"self"
)
||
accessLevel
.
isAccessAllowed
(
e
.
getKey
()))
.
filter
((
e
ntry
)
->
entry
.
getKey
().
equals
(
"self"
)
||
accessLevel
.
isAccessAllowed
(
e
ntry
.
getKey
()))
.
collect
(
Collectors
.
toMap
(
Map
.
Entry
::
getKey
,
Map
.
Entry
::
getValue
));
}
...
...
@@ -129,7 +131,7 @@ public class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEn
* @param corsConfiguration the CORS configuration for the endpoints
* @param securityInterceptor the Security Interceptor
*/
public
CloudFoundryWebFluxEndpointHandlerMapping
(
EndpointMapping
endpointMapping
,
CloudFoundryWebFluxEndpointHandlerMapping
(
EndpointMapping
endpointMapping
,
Collection
<
EndpointInfo
<
WebEndpointOperation
>>
webEndpoints
,
EndpointMediaTypes
endpointMediaTypes
,
CorsConfiguration
corsConfiguration
,
ReactiveCloudFoundrySecurityInterceptor
securityInterceptor
)
{
...
...
@@ -148,31 +150,35 @@ public class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEn
private
final
ReactiveCloudFoundrySecurityInterceptor
securityInterceptor
;
AbstractOperationHandler
(
OperationInvoker
operationInvoker
,
String
endpointId
,
ReactiveCloudFoundrySecurityInterceptor
securityInterceptor
)
{
AbstractOperationHandler
(
OperationInvoker
operationInvoker
,
String
endpointId
,
ReactiveCloudFoundrySecurityInterceptor
securityInterceptor
)
{
this
.
operationInvoker
=
operationInvoker
;
this
.
endpointId
=
endpointId
;
this
.
securityInterceptor
=
securityInterceptor
;
}
@SuppressWarnings
({
"unchecked"
})
Publisher
<
ResponseEntity
<
Object
>>
doHandle
(
ServerWebExchange
exchange
,
Map
<
String
,
String
>
body
)
{
return
this
.
securityInterceptor
.
preHandle
(
exchange
,
this
.
endpointId
)
.
flatMap
(
securityResponse
->
{
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
return
Mono
.
just
(
new
ResponseEntity
<>(
securityResponse
.
getStatus
()));
}
Map
<
String
,
Object
>
arguments
=
new
HashMap
<>(
exchange
.
getAttribute
(
HandlerMapping
.
URI_TEMPLATE_VARIABLES_ATTRIBUTE
));
if
(
body
!=
null
)
{
arguments
.
putAll
(
body
);
}
exchange
.
getRequest
().
getQueryParams
().
forEach
((
name
,
values
)
->
arguments
.
put
(
name
,
values
.
size
()
==
1
?
values
.
get
(
0
)
:
values
));
return
handleResult
((
Publisher
<?>)
this
.
operationInvoker
.
invoke
(
arguments
),
exchange
.
getRequest
().
getMethod
());
});
return
this
.
securityInterceptor
.
preHandle
(
exchange
,
this
.
endpointId
)
.
flatMap
((
securityResponse
)
->
flatMapResponse
(
exchange
,
body
,
securityResponse
));
}
private
Mono
<?
extends
ResponseEntity
<
Object
>>
flatMapResponse
(
ServerWebExchange
exchange
,
Map
<
String
,
String
>
body
,
SecurityResponse
securityResponse
)
{
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
return
Mono
.
just
(
new
ResponseEntity
<>(
securityResponse
.
getStatus
()));
}
Map
<
String
,
Object
>
arguments
=
new
HashMap
<>(
exchange
.
getAttribute
(
HandlerMapping
.
URI_TEMPLATE_VARIABLES_ATTRIBUTE
));
if
(
body
!=
null
)
{
arguments
.
putAll
(
body
);
}
exchange
.
getRequest
().
getQueryParams
().
forEach
((
name
,
values
)
->
arguments
.
put
(
name
,
(
values
.
size
()
==
1
?
values
.
get
(
0
)
:
values
)));
return
handleResult
((
Publisher
<?>)
this
.
operationInvoker
.
invoke
(
arguments
),
exchange
.
getRequest
().
getMethod
());
}
private
Mono
<
ResponseEntity
<
Object
>>
handleResult
(
Publisher
<?>
result
,
...
...
@@ -203,7 +209,8 @@ public class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEn
final
class
WriteOperationHandler
extends
AbstractOperationHandler
{
WriteOperationHandler
(
OperationInvoker
operationInvoker
,
String
endpointId
)
{
super
(
operationInvoker
,
endpointId
,
CloudFoundryWebFluxEndpointHandlerMapping
.
this
.
securityInterceptor
);
super
(
operationInvoker
,
endpointId
,
CloudFoundryWebFluxEndpointHandlerMapping
.
this
.
securityInterceptor
);
}
@ResponseBody
...
...
@@ -220,7 +227,8 @@ public class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEn
final
class
ReadOperationHandler
extends
AbstractOperationHandler
{
ReadOperationHandler
(
OperationInvoker
operationInvoker
,
String
endpointId
)
{
super
(
operationInvoker
,
endpointId
,
CloudFoundryWebFluxEndpointHandlerMapping
.
this
.
securityInterceptor
);
super
(
operationInvoker
,
endpointId
,
CloudFoundryWebFluxEndpointHandlerMapping
.
this
.
securityInterceptor
);
}
@ResponseBody
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java
View file @
3f00ba3c
...
...
@@ -69,13 +69,16 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
public
CloudFoundryWebFluxEndpointHandlerMapping
cloudFoundryWebFluxEndpointHandlerMapping
(
ParameterMapper
parameterMapper
,
EndpointMediaTypes
endpointMediaTypes
,
WebClient
.
Builder
webClientBuilder
,
Environment
environment
,
DefaultCachingConfigurationFactory
cachingConfigurationFactory
,
WebEndpointProperties
webEndpointProperties
)
{
DefaultCachingConfigurationFactory
cachingConfigurationFactory
,
WebEndpointProperties
webEndpointProperties
)
{
WebAnnotationEndpointDiscoverer
endpointDiscoverer
=
new
WebAnnotationEndpointDiscoverer
(
this
.
applicationContext
,
parameterMapper
,
cachingConfigurationFactory
,
endpointMediaTypes
,
(
id
)
->
id
);
return
new
CloudFoundryWebFluxEndpointHandlerMapping
(
new
EndpointMapping
(
"/cloudfoundryapplication"
),
endpointDiscoverer
.
discoverEndpoints
(),
endpointMediaTypes
,
getCorsConfiguration
(),
getSecurityInterceptor
(
webClientBuilder
,
environment
));
endpointDiscoverer
.
discoverEndpoints
(),
endpointMediaTypes
,
getCorsConfiguration
(),
getSecurityInterceptor
(
webClientBuilder
,
environment
));
}
private
ReactiveCloudFoundrySecurityInterceptor
getSecurityInterceptor
(
...
...
@@ -91,11 +94,10 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
private
ReactiveCloudFoundrySecurityService
getCloudFoundrySecurityService
(
WebClient
.
Builder
webClientBuilder
,
Environment
environment
)
{
String
cloudControllerUrl
=
environment
.
getProperty
(
"vcap.application.cf_api"
);
String
cloudControllerUrl
=
environment
.
getProperty
(
"vcap.application.cf_api"
);
return
(
cloudControllerUrl
==
null
?
null
:
new
ReactiveCloudFoundrySecurityService
(
webClientBuilder
,
cloudControllerUrl
));
cloudControllerUrl
));
}
private
CorsConfiguration
getCorsConfiguration
()
{
...
...
@@ -111,30 +113,38 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
@Configuration
@ConditionalOnClass
(
MatcherSecurityWebFilterChain
.
class
)
static
class
IgnoredPathsSecurityConfiguration
{
@Bean
public
BeanPostProcessor
webFilterChainPostProcessor
()
{
return
new
BeanPostProcessor
()
{
@Override
public
Object
postProcessAfterInitialization
(
Object
bean
,
String
beanName
)
throws
BeansException
{
if
(
bean
instanceof
WebFilterChainProxy
)
{
return
postProcess
((
WebFilterChainProxy
)
bean
);
}
return
bean
;
}
};
public
WebFilterChainPostProcessor
webFilterChainPostProcessor
()
{
return
new
WebFilterChainPostProcessor
();
}
}
private
static
class
WebFilterChainPostProcessor
implements
BeanPostProcessor
{
@Override
public
Object
postProcessAfterInitialization
(
Object
bean
,
String
beanName
)
throws
BeansException
{
if
(
bean
instanceof
WebFilterChainProxy
)
{
return
postProcess
((
WebFilterChainProxy
)
bean
);
}
return
bean
;
}
WebFilterChainProxy
postProcess
(
WebFilterChainProxy
existing
)
{
ServerWebExchangeMatcher
cloudFoundryRequestMatcher
=
ServerWebExchangeMatchers
.
pathMatchers
(
"/cloudfoundryapplication/**"
);
private
WebFilterChainProxy
postProcess
(
WebFilterChainProxy
existing
)
{
ServerWebExchangeMatcher
cloudFoundryRequestMatcher
=
ServerWebExchangeMatchers
.
pathMatchers
(
"/cloudfoundryapplication/**"
);
WebFilter
noOpFilter
=
(
exchange
,
chain
)
->
chain
.
filter
(
exchange
);
MatcherSecurityWebFilterChain
ignoredRequestFilterChain
=
new
MatcherSecurityWebFilterChain
(
cloudFoundryRequestMatcher
,
Collections
.
singletonList
(
noOpFilter
));
MatcherSecurityWebFilterChain
allRequestsFilterChain
=
new
MatcherSecurityWebFilterChain
(
ServerWebExchangeMatchers
.
anyExchange
(),
Collections
.
singletonList
(
existing
));
return
new
WebFilterChainProxy
(
ignoredRequestFilterChain
,
allRequestsFilterChain
);
ServerWebExchangeMatchers
.
anyExchange
(),
Collections
.
singletonList
(
existing
));
return
new
WebFilterChainProxy
(
ignoredRequestFilterChain
,
allRequestsFilterChain
);
}
}
}
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityInterceptor.java
View file @
3f00ba3c
...
...
@@ -63,28 +63,31 @@ class ReactiveCloudFoundrySecurityInterceptor {
}
if
(!
StringUtils
.
hasText
(
this
.
applicationId
))
{
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Application id is not available"
));
Reason
.
SERVICE_UNAVAILABLE
,
"Application id is not available"
));
}
if
(
this
.
cloudFoundrySecurityService
==
null
)
{
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Cloud controller URL is not available"
));
Reason
.
SERVICE_UNAVAILABLE
,
"Cloud controller URL is not available"
));
}
return
check
(
exchange
,
endpointId
)
.
then
(
SUCCESS
)
.
doOnError
(
throwable
->
logger
.
error
(
throwable
.
getMessage
(),
throwable
))
return
check
(
exchange
,
endpointId
).
then
(
SUCCESS
).
doOnError
(
this
::
logError
)
.
onErrorResume
(
this
::
getErrorResponse
);
}
private
void
logError
(
Throwable
ex
)
{
logger
.
error
(
ex
.
getMessage
(),
ex
);
}
private
Mono
<
Void
>
check
(
ServerWebExchange
exchange
,
String
path
)
{
try
{
Token
token
=
getToken
(
exchange
.
getRequest
());
return
this
.
tokenValidator
.
validate
(
token
).
then
(
this
.
cloudFoundrySecurityService
.
getAccessLevel
(
token
.
toString
(),
this
.
applicationId
))
.
filter
(
accessLevel
->
accessLevel
.
isAccessAllowed
(
path
))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
ACCESS_DENIED
,
"Access denied"
)))
.
doOnSuccess
(
accessLevel
->
exchange
.
getAttributes
().
put
(
"cloudFoundryAccessLevel"
,
accessLevel
))
return
this
.
tokenValidator
.
validate
(
token
)
.
then
(
this
.
cloudFoundrySecurityService
.
getAccessLevel
(
token
.
toString
(),
this
.
applicationId
))
.
filter
((
accessLevel
)
->
accessLevel
.
isAccessAllowed
(
path
))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
ACCESS_DENIED
,
"Access denied"
)))
.
doOnSuccess
((
accessLevel
)
->
exchange
.
getAttributes
()
.
put
(
"cloudFoundryAccessLevel"
,
accessLevel
))
.
then
();
}
catch
(
CloudFoundryAuthorizationException
ex
)
{
...
...
@@ -107,8 +110,7 @@ class ReactiveCloudFoundrySecurityInterceptor {
String
bearerPrefix
=
"bearer "
;
if
(
authorization
==
null
||
!
authorization
.
toLowerCase
().
startsWith
(
bearerPrefix
))
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
MISSING_AUTHORIZATION
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
MISSING_AUTHORIZATION
,
"Authorization header is missing or invalid"
);
}
return
new
Token
(
authorization
.
substring
(
bearerPrefix
.
length
()));
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java
View file @
3f00ba3c
...
...
@@ -29,14 +29,19 @@ import org.springframework.core.ParameterizedTypeReference;
import
org.springframework.http.HttpStatus
;
import
org.springframework.util.Assert
;
import
org.springframework.web.reactive.function.client.WebClient
;
import
org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec
;
import
org.springframework.web.reactive.function.client.WebClientResponseException
;
/**
* Reactive Cloud Foundry security service to handle REST calls to the cloud controller and UAA.
* Reactive Cloud Foundry security service to handle REST calls to the cloud controller
* and UAA.
*
* @author Madhura Bhave
*/
public
class
ReactiveCloudFoundrySecurityService
{
class
ReactiveCloudFoundrySecurityService
{
private
static
final
ParameterizedTypeReference
<
Map
<
String
,
Object
>>
STRING_OBJECT_MAP
=
new
ParameterizedTypeReference
<
Map
<
String
,
Object
>>()
{
};
private
final
WebClient
webClient
;
...
...
@@ -62,28 +67,29 @@ public class ReactiveCloudFoundrySecurityService {
public
Mono
<
AccessLevel
>
getAccessLevel
(
String
token
,
String
applicationId
)
throws
CloudFoundryAuthorizationException
{
String
uri
=
getPermissionsUri
(
applicationId
);
return
this
.
webClient
.
get
().
uri
(
uri
)
.
header
(
"Authorization"
,
"bearer "
+
token
)
.
retrieve
().
bodyToMono
(
Map
.
class
)
.
map
(
this
::
getAccessLevel
)
.
onErrorMap
(
throwable
->
{
if
(
throwable
instanceof
WebClientResponseException
)
{
HttpStatus
statusCode
=
((
WebClientResponseException
)
throwable
).
getStatusCode
();
if
(
statusCode
.
equals
(
HttpStatus
.
FORBIDDEN
))
{
return
new
CloudFoundryAuthorizationException
(
Reason
.
ACCESS_DENIED
,
"Access denied"
);
}
if
(
statusCode
.
is4xxClientError
())
{
return
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"Invalid token"
,
throwable
);
}
}
return
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Cloud controller not reachable"
);
});
return
this
.
webClient
.
get
().
uri
(
uri
).
header
(
"Authorization"
,
"bearer "
+
token
)
.
retrieve
().
bodyToMono
(
Map
.
class
).
map
(
this
::
getAccessLevel
)
.
onErrorMap
(
this
::
mapError
);
}
private
Throwable
mapError
(
Throwable
throwable
)
{
if
(
throwable
instanceof
WebClientResponseException
)
{
HttpStatus
statusCode
=
((
WebClientResponseException
)
throwable
)
.
getStatusCode
();
if
(
statusCode
.
equals
(
HttpStatus
.
FORBIDDEN
))
{
return
new
CloudFoundryAuthorizationException
(
Reason
.
ACCESS_DENIED
,
"Access denied"
);
}
if
(
statusCode
.
is4xxClientError
())
{
return
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"Invalid token"
,
throwable
);
}
}
return
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Cloud controller not reachable"
);
}
private
AccessLevel
getAccessLevel
(
Map
body
)
{
private
AccessLevel
getAccessLevel
(
Map
<?,
?>
body
)
{
if
(
Boolean
.
TRUE
.
equals
(
body
.
get
(
"read_sensitive_data"
)))
{
return
AccessLevel
.
FULL
;
}
...
...
@@ -91,8 +97,7 @@ public class ReactiveCloudFoundrySecurityService {
}
private
String
getPermissionsUri
(
String
applicationId
)
{
return
this
.
cloudControllerUrl
+
"/v2/apps/"
+
applicationId
+
"/permissions"
;
return
this
.
cloudControllerUrl
+
"/v2/apps/"
+
applicationId
+
"/permissions"
;
}
/**
...
...
@@ -100,14 +105,14 @@ public class ReactiveCloudFoundrySecurityService {
* @return a Mono of token keys
*/
public
Mono
<
Map
<
String
,
String
>>
fetchTokenKeys
()
{
return
getUaaUrl
()
.
flatMap
(
url
->
this
.
webClient
.
get
()
.
uri
(
url
+
"/token_keys"
)
.
retrieve
().
bodyToMono
(
new
ParameterizedTypeReference
<
Map
<
String
,
Object
>>()
{
})
.
map
(
this
::
extractTokenKeys
)
.
onErrorMap
((
throwable
->
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
throwable
.
getMessage
()))));
return
getUaaUrl
().
flatMap
(
this
::
fetchTokenKeys
);
}
private
Mono
<?
extends
Map
<
String
,
String
>>
fetchTokenKeys
(
String
url
)
{
RequestHeadersSpec
<?>
uri
=
this
.
webClient
.
get
().
uri
(
url
+
"/token_keys"
);
return
uri
.
retrieve
().
bodyToMono
(
STRING_OBJECT_MAP
).
map
(
this
::
extractTokenKeys
)
.
onErrorMap
(((
ex
)
->
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
ex
.
getMessage
())));
}
private
Map
<
String
,
String
>
extractTokenKeys
(
Map
<
String
,
Object
>
response
)
{
...
...
@@ -124,11 +129,11 @@ public class ReactiveCloudFoundrySecurityService {
* @return the UAA url Mono
*/
public
Mono
<
String
>
getUaaUrl
()
{
this
.
uaaUrl
=
this
.
webClient
.
get
().
uri
(
this
.
cloudControllerUrl
+
"/info"
)
this
.
uaaUrl
=
this
.
webClient
.
get
().
uri
(
this
.
cloudControllerUrl
+
"/info"
)
.
retrieve
().
bodyToMono
(
Map
.
class
)
.
map
(
response
->
(
String
)
response
.
get
(
"token_endpoint"
)).
cache
()
.
onErrorMap
(
throwable
->
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
.
map
((
response
)
->
(
String
)
response
.
get
(
"token_endpoint"
)).
cache
()
.
onErrorMap
((
ex
)
->
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Unable to fetch token keys from UAA."
));
return
this
.
uaaUrl
;
}
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveTokenValidator.java
View file @
3f00ba3c
...
...
@@ -23,7 +23,6 @@ import java.security.PublicKey;
import
java.security.Signature
;
import
java.security.spec.InvalidKeySpecException
;
import
java.security.spec.X509EncodedKeySpec
;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
import
reactor.core.publisher.Mono
;
...
...
@@ -38,27 +37,25 @@ import org.springframework.util.Base64Utils;
*
* @author Madhura Bhave
*/
public
class
ReactiveTokenValidator
{
class
ReactiveTokenValidator
{
private
final
ReactiveCloudFoundrySecurityService
securityService
;
public
ReactiveTokenValidator
(
ReactiveCloudFoundrySecurityService
securityService
)
{
ReactiveTokenValidator
(
ReactiveCloudFoundrySecurityService
securityService
)
{
this
.
securityService
=
securityService
;
}
public
Mono
<
Void
>
validate
(
Token
token
)
{
return
validateAlgorithm
(
token
)
.
then
(
validateKeyIdAndSignature
(
token
))
.
then
(
validateExpiry
(
token
))
.
then
(
validateIssuer
(
token
))
return
validateAlgorithm
(
token
).
then
(
validateKeyIdAndSignature
(
token
))
.
then
(
validateExpiry
(
token
)).
then
(
validateIssuer
(
token
))
.
then
(
validateAudience
(
token
));
}
private
Mono
<
Void
>
validateAlgorithm
(
Token
token
)
{
String
algorithm
=
token
.
getSignatureAlgorithm
();
if
(
algorithm
==
null
)
{
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_SIGNATURE
,
"Signing algorithm cannot be null"
));
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_SIGNATURE
,
"Signing algorithm cannot be null"
));
}
if
(!
algorithm
.
equals
(
"RS256"
))
{
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
...
...
@@ -71,24 +68,16 @@ public class ReactiveTokenValidator {
private
Mono
<
Void
>
validateKeyIdAndSignature
(
Token
token
)
{
String
keyId
=
token
.
getKeyId
();
return
this
.
securityService
.
fetchTokenKeys
()
.
filter
(
tokenKeys
->
hasValidKeyId
(
keyId
,
tokenKeys
))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_KEY_ID
,
"Key Id present in token header does not match"
)))
.
filter
(
tokenKeys
->
tokenKeys
.
containsKey
(
keyId
))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_KEY_ID
,
"Key Id present in token header does not match"
)))
.
filter
(
tokenKeys
->
hasValidSignature
(
token
,
tokenKeys
.
get
(
keyId
)))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_SIGNATURE
,
"RSA Signature did not match content"
)))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_SIGNATURE
,
"RSA Signature did not match content"
)))
.
then
();
}
private
boolean
hasValidKeyId
(
String
keyId
,
Map
<
String
,
String
>
tokenKeys
)
{
for
(
String
candidate
:
tokenKeys
.
keySet
())
{
if
(
keyId
.
equals
(
candidate
))
{
return
true
;
}
}
return
false
;
}
private
boolean
hasValidSignature
(
Token
token
,
String
key
)
{
try
{
PublicKey
publicKey
=
getPublicKey
(
key
);
...
...
@@ -123,17 +112,17 @@ public class ReactiveTokenValidator {
private
Mono
<
Void
>
validateIssuer
(
Token
token
)
{
return
this
.
securityService
.
getUaaUrl
()
.
map
(
uaaUrl
->
String
.
format
(
"%s/oauth/token"
,
uaaUrl
))
.
filter
(
issuerUri
->
issuerUri
.
equals
(
token
.
getIssuer
()))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_ISSUER
,
"Token issuer does not match"
)))
.
map
(
(
uaaUrl
)
->
String
.
format
(
"%s/oauth/token"
,
uaaUrl
))
.
filter
(
(
issuerUri
)
->
issuerUri
.
equals
(
token
.
getIssuer
()))
.
switchIfEmpty
(
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_ISSUER
,
"Token issuer does not match"
)))
.
then
();
}
private
Mono
<
Void
>
validateAudience
(
Token
token
)
{
if
(!
token
.
getScope
().
contains
(
"actuator.read"
))
{
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_AUDIENCE
,
"Token does not have audience actuator"
));
return
Mono
.
error
(
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_AUDIENCE
,
"Token does not have audience actuator"
));
}
return
Mono
.
empty
();
}
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java
View file @
3f00ba3c
...
...
@@ -82,8 +82,7 @@ public class CloudFoundryActuatorAutoConfiguration {
RestTemplateBuilder
restTemplateBuilder
,
Environment
environment
)
{
CloudFoundrySecurityService
cloudfoundrySecurityService
=
getCloudFoundrySecurityService
(
restTemplateBuilder
,
environment
);
TokenValidator
tokenValidator
=
new
TokenValidator
(
cloudfoundrySecurityService
);
TokenValidator
tokenValidator
=
new
TokenValidator
(
cloudfoundrySecurityService
);
return
new
CloudFoundrySecurityInterceptor
(
tokenValidator
,
cloudfoundrySecurityService
,
environment
.
getProperty
(
"vcap.application.application_id"
));
...
...
@@ -91,13 +90,12 @@ public class CloudFoundryActuatorAutoConfiguration {
private
CloudFoundrySecurityService
getCloudFoundrySecurityService
(
RestTemplateBuilder
restTemplateBuilder
,
Environment
environment
)
{
String
cloudControllerUrl
=
environment
.
getProperty
(
"vcap.application.cf_api"
);
String
cloudControllerUrl
=
environment
.
getProperty
(
"vcap.application.cf_api"
);
boolean
skipSslValidation
=
environment
.
getProperty
(
"management.cloudfoundry.skip-ssl-validation"
,
Boolean
.
class
,
false
);
return
(
cloudControllerUrl
==
null
?
null
:
new
CloudFoundrySecurityService
(
restTemplateBuilder
,
cloudControllerUrl
,
skipSslValidation
));
:
new
CloudFoundrySecurityService
(
restTemplateBuilder
,
cloudControllerUrl
,
skipSslValidation
));
}
private
CorsConfiguration
getCorsConfiguration
()
{
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java
View file @
3f00ba3c
...
...
@@ -63,13 +63,11 @@ class CloudFoundrySecurityInterceptor {
}
try
{
if
(!
StringUtils
.
hasText
(
this
.
applicationId
))
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Application id is not available"
);
}
if
(
this
.
cloudFoundrySecurityService
==
null
)
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
SERVICE_UNAVAILABLE
,
"Cloud controller URL is not available"
);
}
if
(
HttpMethod
.
OPTIONS
.
matches
(
request
.
getMethod
()))
{
...
...
@@ -96,8 +94,7 @@ class CloudFoundrySecurityInterceptor {
AccessLevel
accessLevel
=
this
.
cloudFoundrySecurityService
.
getAccessLevel
(
token
.
toString
(),
this
.
applicationId
);
if
(!
accessLevel
.
isAccessAllowed
(
path
))
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
ACCESS_DENIED
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
ACCESS_DENIED
,
"Access denied"
);
}
request
.
setAttribute
(
AccessLevel
.
REQUEST_ATTRIBUTE
,
accessLevel
);
...
...
@@ -108,8 +105,7 @@ class CloudFoundrySecurityInterceptor {
String
bearerPrefix
=
"bearer "
;
if
(
authorization
==
null
||
!
authorization
.
toLowerCase
().
startsWith
(
bearerPrefix
))
{
throw
new
CloudFoundryAuthorizationException
(
Reason
.
MISSING_AUTHORIZATION
,
throw
new
CloudFoundryAuthorizationException
(
Reason
.
MISSING_AUTHORIZATION
,
"Authorization header is missing or invalid"
);
}
return
new
Token
(
authorization
.
substring
(
bearerPrefix
.
length
()));
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java
View file @
3f00ba3c
...
...
@@ -93,12 +93,13 @@ class CloudFoundryWebEndpointServletHandlerMapping
@ResponseBody
private
Map
<
String
,
Map
<
String
,
Link
>>
links
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
SecurityResponse
securityResponse
=
this
.
securityInterceptor
.
preHandle
(
request
,
""
);
SecurityResponse
securityResponse
=
this
.
securityInterceptor
.
preHandle
(
request
,
""
);
if
(!
securityResponse
.
getStatus
().
equals
(
HttpStatus
.
OK
))
{
sendFailureResponse
(
response
,
securityResponse
);
}
AccessLevel
accessLevel
=
(
AccessLevel
)
request
.
getAttribute
(
AccessLevel
.
REQUEST_ATTRIBUTE
);
AccessLevel
accessLevel
=
(
AccessLevel
)
request
.
getAttribute
(
AccessLevel
.
REQUEST_ATTRIBUTE
);
Map
<
String
,
Link
>
links
=
this
.
endpointLinksResolver
.
resolveLinks
(
getEndpoints
(),
request
.
getRequestURL
().
toString
());
Map
<
String
,
Link
>
filteredLinks
=
new
LinkedHashMap
<>();
...
...
@@ -174,8 +175,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
}
}
private
Object
failureResponse
(
SecurityResponse
response
)
{
private
Object
failureResponse
(
SecurityResponse
response
)
{
return
handleResult
(
new
WebEndpointResponse
<>(
response
.
getMessage
(),
response
.
getStatus
().
value
()));
}
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/TokenValidator.java
View file @
3f00ba3c
...
...
@@ -36,13 +36,13 @@ import org.springframework.util.Base64Utils;
*
* @author Madhura Bhave
*/
public
class
TokenValidator
{
class
TokenValidator
{
private
final
CloudFoundrySecurityService
securityService
;
private
Map
<
String
,
String
>
tokenKeys
;
public
TokenValidator
(
CloudFoundrySecurityService
cloudFoundrySecurityService
)
{
TokenValidator
(
CloudFoundrySecurityService
cloudFoundrySecurityService
)
{
this
.
securityService
=
cloudFoundrySecurityService
;
}
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java
View file @
3f00ba3c
...
...
@@ -23,7 +23,6 @@ import java.util.function.BiConsumer;
import
java.util.function.Consumer
;
import
org.junit.Test
;
import
org.mockito.BDDMockito
;
import
reactor.core.publisher.Mono
;
import
org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel
;
...
...
@@ -37,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import
org.springframework.boot.actuate.endpoint.cache.CachingConfiguration
;
import
org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper
;
import
org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes
;
import
org.springframework.boot.actuate.endpoint.web.EndpointPathResolver
;
import
org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer
;
import
org.springframework.boot.endpoint.web.EndpointMapping
;
import
org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
;
...
...
@@ -60,6 +60,7 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
import
static
org
.
mockito
.
ArgumentMatchers
.
eq
;
import
static
org
.
mockito
.
BDDMockito
.
given
;
import
static
org
.
mockito
.
BDDMockito
.
willThrow
;
import
static
org
.
mockito
.
Mockito
.
mock
;
/**
...
...
@@ -69,7 +70,8 @@ import static org.mockito.Mockito.mock;
*/
public
class
CloudFoundryWebFluxEndpointIntegrationTests
{
private
static
ReactiveTokenValidator
tokenValidator
=
mock
(
ReactiveTokenValidator
.
class
);
private
static
ReactiveTokenValidator
tokenValidator
=
mock
(
ReactiveTokenValidator
.
class
);
private
static
ReactiveCloudFoundrySecurityService
securityService
=
mock
(
ReactiveCloudFoundrySecurityService
.
class
);
...
...
@@ -137,7 +139,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
public
void
linksToOtherEndpointsForbidden
()
{
CloudFoundryAuthorizationException
exception
=
new
CloudFoundryAuthorizationException
(
Reason
.
INVALID_TOKEN
,
"invalid-token"
);
BDDMockito
.
willThrow
(
exception
).
given
(
tokenValidator
).
validate
(
any
());
willThrow
(
exception
).
given
(
tokenValidator
).
validate
(
any
());
load
(
TestEndpointConfiguration
.
class
,
(
client
)
->
client
.
get
().
uri
(
"/cfApplication"
)
.
accept
(
MediaType
.
APPLICATION_JSON
)
...
...
@@ -203,8 +205,8 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
@Bean
public
ReactiveCloudFoundrySecurityInterceptor
interceptor
()
{
return
new
ReactiveCloudFoundrySecurityInterceptor
(
tokenValidator
,
securityService
,
"app-id"
);
return
new
ReactiveCloudFoundrySecurityInterceptor
(
tokenValidator
,
securityService
,
"app-id"
);
}
@Bean
...
...
@@ -235,7 +237,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
DefaultConversionService
.
getSharedInstance
());
return
new
WebAnnotationEndpointDiscoverer
(
applicationContext
,
parameterMapper
,
(
id
)
->
new
CachingConfiguration
(
0
),
endpointMediaTypes
,
(
id
)
->
id
);
endpointMediaTypes
,
EndpointPathResolver
.
useEndpointId
()
);
}
@Bean
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java
View file @
3f00ba3c
...
...
@@ -80,9 +80,9 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
setupContextWithCloudEnabled
();
this
.
context
.
refresh
();
CloudFoundryWebFluxEndpointHandlerMapping
handlerMapping
=
getHandlerMapping
();
EndpointMapping
endpointMapping
=
(
EndpointMapping
)
ReflectionTestUtils
.
getField
(
handlerMapping
,
"endpointMapping"
);
assertThat
(
endpointMapping
.
getPath
())
.
isEqualTo
(
"/cloudfoundryapplication"
);
EndpointMapping
endpointMapping
=
(
EndpointMapping
)
ReflectionTestUtils
.
getField
(
handlerMapping
,
"endpointMapping"
);
assertThat
(
endpointMapping
.
getPath
())
.
isEqualTo
(
"/cloudfoundryapplication"
);
CorsConfiguration
corsConfiguration
=
(
CorsConfiguration
)
ReflectionTestUtils
.
getField
(
handlerMapping
,
"corsConfiguration"
);
assertThat
(
corsConfiguration
.
getAllowedOrigins
()).
contains
(
"*"
);
...
...
@@ -96,9 +96,10 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
public
void
cloudfoundryapplicationProducesActuatorMediaType
()
throws
Exception
{
setupContextWithCloudEnabled
();
this
.
context
.
refresh
();
WebTestClient
webTestClient
=
WebTestClient
.
bindToApplicationContext
(
this
.
context
).
build
();
webTestClient
.
get
().
uri
(
"/cloudfoundryapplication"
)
.
header
(
"Content-Type"
,
ActuatorMediaType
.
V2_JSON
+
";charset=UTF-8"
);
WebTestClient
webTestClient
=
WebTestClient
.
bindToApplicationContext
(
this
.
context
)
.
build
();
webTestClient
.
get
().
uri
(
"/cloudfoundryapplication"
).
header
(
"Content-Type"
,
ActuatorMediaType
.
V2_JSON
+
";charset=UTF-8"
);
}
@Test
...
...
@@ -135,7 +136,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
.
applyTo
(
this
.
context
);
setupContext
();
this
.
context
.
refresh
();
CloudFoundryWebFluxEndpointHandlerMapping
handlerMapping
=
this
.
context
.
getBean
(
"cloudFoundryWebFluxEndpointHandlerMapping"
,
CloudFoundryWebFluxEndpointHandlerMapping
handlerMapping
=
this
.
context
.
getBean
(
"cloudFoundryWebFluxEndpointHandlerMapping"
,
CloudFoundryWebFluxEndpointHandlerMapping
.
class
);
Object
securityInterceptor
=
ReflectionTestUtils
.
getField
(
handlerMapping
,
"securityInterceptor"
);
...
...
@@ -145,20 +147,26 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
}
@Test
@SuppressWarnings
(
"unchecked"
)
public
void
cloudFoundryPathsIgnoredBySpringSecurity
()
throws
Exception
{
setupContextWithCloudEnabled
();
this
.
context
.
refresh
();
WebFilterChainProxy
chainProxy
=
this
.
context
.
getBean
(
WebFilterChainProxy
.
class
);
List
<
SecurityWebFilterChain
>
filters
=
(
List
<
SecurityWebFilterChain
>)
ReflectionTestUtils
.
getField
(
chainProxy
,
"filters"
);
WebFilterChainProxy
chainProxy
=
this
.
context
.
getBean
(
WebFilterChainProxy
.
class
);
List
<
SecurityWebFilterChain
>
filters
=
(
List
<
SecurityWebFilterChain
>)
ReflectionTestUtils
.
getField
(
chainProxy
,
"filters"
);
Boolean
cfRequestMatches
=
filters
.
get
(
0
).
matches
(
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/cloudfoundryapplication/my-path"
).
build
())).
block
();
Boolean
otherRequestMatches
=
filters
.
get
(
0
).
matches
(
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/some-other-path"
).
build
())).
block
();
MockServerHttpRequest
.
get
(
"/cloudfoundryapplication/my-path"
).
build
()))
.
block
();
Boolean
otherRequestMatches
=
filters
.
get
(
0
)
.
matches
(
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/some-other-path"
).
build
()))
.
block
();
assertThat
(
cfRequestMatches
).
isTrue
();
assertThat
(
otherRequestMatches
).
isFalse
();
otherRequestMatches
=
filters
.
get
(
1
).
matches
(
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/some-other-path"
).
build
())).
block
();
otherRequestMatches
=
filters
.
get
(
1
)
.
matches
(
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/some-other-path"
).
build
()))
.
block
();
assertThat
(
otherRequestMatches
).
isTrue
();
}
...
...
@@ -166,8 +174,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
public
void
cloudFoundryPlatformInactive
()
throws
Exception
{
setupContext
();
this
.
context
.
refresh
();
assertThat
(
this
.
context
.
containsBean
(
"cloudFoundryWebFluxEndpointHandlerMapping"
))
assertThat
(
this
.
context
.
containsBean
(
"cloudFoundryWebFluxEndpointHandlerMapping"
))
.
isFalse
();
}
...
...
@@ -196,8 +203,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
}
@Test
public
void
endpointPathCustomizationIsNotApplied
()
throws
Exception
{
public
void
endpointPathCustomizationIsNotApplied
()
throws
Exception
{
setupContextWithCloudEnabled
();
this
.
context
.
register
(
TestConfiguration
.
class
);
this
.
context
.
refresh
();
...
...
@@ -223,10 +229,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
WebFluxAutoConfiguration
.
class
,
JacksonAutoConfiguration
.
class
,
HttpMessageConvertersAutoConfiguration
.
class
,
PropertyPlaceholderAutoConfiguration
.
class
,
WebClientCustomizerConfig
.
class
,
WebClientAutoConfiguration
.
class
,
ManagementContextAutoConfiguration
.
class
,
EndpointAutoConfiguration
.
class
,
WebClientCustomizerConfig
.
class
,
WebClientAutoConfiguration
.
class
,
ManagementContextAutoConfiguration
.
class
,
EndpointAutoConfiguration
.
class
,
ReactiveCloudFoundryActuatorAutoConfiguration
.
class
);
}
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityInterceptorTests.java
View file @
3f00ba3c
...
...
@@ -18,7 +18,6 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.mockito.BDDMockito
;
import
org.mockito.Mock
;
import
org.mockito.MockitoAnnotations
;
import
reactor.core.publisher.Mono
;
...
...
@@ -35,6 +34,7 @@ import org.springframework.util.Base64Utils;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
import
static
org
.
mockito
.
BDDMockito
.
given
;
/**
* Tests for {@link ReactiveCloudFoundrySecurityInterceptor}.
...
...
@@ -54,128 +54,124 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
@Before
public
void
setup
()
throws
Exception
{
MockitoAnnotations
.
initMocks
(
this
);
this
.
interceptor
=
new
ReactiveCloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
"my-app-id"
);
this
.
interceptor
=
new
ReactiveCloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
"my-app-id"
);
}
@Test
public
void
preHandleWhenRequestIsPreFlightShouldBeOk
()
throws
Exception
{
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
options
(
"/a"
)
.
header
(
HttpHeaders
.
ORIGIN
,
"http://example.com"
)
.
header
(
HttpHeaders
.
ACCESS_CONTROL_REQUEST_METHOD
,
"GET"
)
.
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
))
.
consumeNextWith
(
response
->
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
))
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
options
(
"/a"
).
header
(
HttpHeaders
.
ORIGIN
,
"http://example.com"
)
.
header
(
HttpHeaders
.
ACCESS_CONTROL_REQUEST_METHOD
,
"GET"
).
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
)).
consumeNextWith
(
(
response
)
->
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
))
.
verifyComplete
();
}
@Test
public
void
preHandleWhenTokenIsMissingShouldReturnMissingAuthorization
()
throws
Exception
{
public
void
preHandleWhenTokenIsMissingShouldReturnMissingAuthorization
()
throws
Exception
{
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
)
.
build
());
.
from
(
MockServerHttpRequest
.
get
(
"/a"
).
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
))
.
consumeNextWith
(
response
->
assertThat
(
response
.
getStatus
())
.
consumeNextWith
(
(
response
)
->
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
MISSING_AUTHORIZATION
.
getStatus
()))
.
verifyComplete
();
}
@Test
public
void
preHandleWhenTokenIsNotBearerShouldReturnMissingAuthorization
()
throws
Exception
{
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
mockAccessToken
())
.
build
());
public
void
preHandleWhenTokenIsNotBearerShouldReturnMissingAuthorization
()
throws
Exception
{
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
).
header
(
HttpHeaders
.
AUTHORIZATION
,
mockAccessToken
()).
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
))
.
consumeNextWith
(
response
->
assertThat
(
response
.
getStatus
())
.
consumeNextWith
(
(
response
)
->
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
MISSING_AUTHORIZATION
.
getStatus
()))
.
verifyComplete
();
}
@Test
public
void
preHandleWhenApplicationIdIsNullShouldReturnError
()
throws
Exception
{
this
.
interceptor
=
new
ReactiveCloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
null
);
this
.
interceptor
=
new
ReactiveCloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
null
);
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
"bearer "
+
mockAccessToken
())
.
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
))
.
consumeErrorWith
(
throwable
->
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
).
getReason
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
))
.
consumeErrorWith
((
ex
)
->
assertThat
(
((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
))
.
verify
();
}
@Test
public
void
preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnError
()
throws
Exception
{
this
.
interceptor
=
new
ReactiveCloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
null
,
"my-app-id"
);
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
mockAccessToken
())
.
build
());
this
.
interceptor
=
new
ReactiveCloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
null
,
"my-app-id"
);
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
).
header
(
HttpHeaders
.
AUTHORIZATION
,
mockAccessToken
()).
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
))
.
consumeErrorWith
(
throwable
->
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
).
getReason
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
))
.
consumeErrorWith
((
ex
)
->
assertThat
(
((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
))
.
verify
();
}
@Test
public
void
preHandleWhenAccessIsNotAllowedShouldReturnAccessDenied
()
throws
Exception
{
BDDMockito
.
given
(
this
.
securityService
.
getAccessLevel
(
mockAccessToken
(),
"my-app-id"
))
public
void
preHandleWhenAccessIsNotAllowedShouldReturnAccessDenied
()
throws
Exception
{
given
(
this
.
securityService
.
getAccessLevel
(
mockAccessToken
(),
"my-app-id"
))
.
willReturn
(
Mono
.
just
(
AccessLevel
.
RESTRICTED
));
BDDMockito
.
given
(
this
.
tokenValidator
.
validate
(
any
()))
.
willReturn
(
Mono
.
empty
());
given
(
this
.
tokenValidator
.
validate
(
any
())).
willReturn
(
Mono
.
empty
());
MockServerWebExchange
request
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
"bearer "
+
mockAccessToken
())
.
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
request
,
"/a"
))
.
consumeNextWith
(
response
->
{
.
consumeNextWith
(
(
response
)
->
{
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
ACCESS_DENIED
.
getStatus
());
})
.
verifyComplete
();
}).
verifyComplete
();
}
@Test
public
void
preHandleSuccessfulWithFullAccess
()
throws
Exception
{
String
accessToken
=
mockAccessToken
();
BDDMockito
.
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
Mono
.
just
(
AccessLevel
.
FULL
));
BDDMockito
.
given
(
this
.
tokenValidator
.
validate
(
any
()))
.
willReturn
(
Mono
.
empty
());
given
(
this
.
tokenValidator
.
validate
(
any
())).
willReturn
(
Mono
.
empty
());
MockServerWebExchange
exchange
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/a"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
"bearer "
+
mockAccessToken
())
.
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
exchange
,
"/a"
))
.
consumeNextWith
(
response
->
{
.
consumeNextWith
(
(
response
)
->
{
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
assertThat
((
AccessLevel
)
exchange
.
getAttribute
(
"cloudFoundryAccessLevel"
))
.
isEqualTo
(
AccessLevel
.
FULL
);
assertThat
((
AccessLevel
)
exchange
.
getAttribute
(
"cloudFoundryAccessLevel"
))
.
isEqualTo
(
AccessLevel
.
FULL
);
}).
verifyComplete
();
}
@Test
public
void
preHandleSuccessfulWithRestrictedAccess
()
throws
Exception
{
String
accessToken
=
mockAccessToken
();
BDDMockito
.
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
Mono
.
just
(
AccessLevel
.
RESTRICTED
));
BDDMockito
.
given
(
this
.
tokenValidator
.
validate
(
any
()))
.
willReturn
(
Mono
.
empty
());
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
Mono
.
just
(
AccessLevel
.
RESTRICTED
));
given
(
this
.
tokenValidator
.
validate
(
any
())).
willReturn
(
Mono
.
empty
());
MockServerWebExchange
exchange
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/info"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
"bearer "
+
mockAccessToken
())
.
build
());
.
from
(
MockServerHttpRequest
.
get
(
"/info"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
"bearer "
+
mockAccessToken
())
.
build
());
StepVerifier
.
create
(
this
.
interceptor
.
preHandle
(
exchange
,
"info"
))
.
consumeNextWith
(
response
->
{
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
assertThat
((
AccessLevel
)
exchange
.
getAttribute
(
"cloudFoundryAccessLevel"
))
.
isEqualTo
(
AccessLevel
.
RESTRICTED
);
}).
verifyComplete
();
.
consumeNextWith
((
response
)
->
{
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
assertThat
((
AccessLevel
)
exchange
.
getAttribute
(
"cloudFoundryAccessLevel"
))
.
isEqualTo
(
AccessLevel
.
RESTRICTED
);
}).
verifyComplete
();
}
private
String
mockAccessToken
()
{
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java
View file @
3f00ba3c
This diff is collapsed.
Click to expand it.
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveTokenValidatorTests.java
View file @
3f00ba3c
...
...
@@ -100,100 +100,125 @@ public class ReactiveTokenValidatorTests {
public
void
validateTokenWhenKidValidationFailsShouldThrowException
()
throws
Exception
{
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
INVALID_KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
String
header
=
"{\"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}"
;
String
claims
=
"{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
consumeErrorWith
(
throwable
->
{
assertThat
(
throwable
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
)
.
getReason
()).
isEqualTo
(
Reason
.
INVALID_KEY_ID
);
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
consumeErrorWith
((
ex
)
->
{
assertThat
(
ex
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
INVALID_KEY_ID
);
}).
verify
();
}
@Test
public
void
validateTokenWhenKidValidationSucceeds
()
throws
Exception
{
public
void
validateTokenWhenKidValidationSucceeds
()
throws
Exception
{
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
VALID_KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
String
header
=
"{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}"
;
String
claims
=
"{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
verifyComplete
();
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
verifyComplete
();
}
@Test
public
void
validateTokenWhenSignatureInvalidShouldThrowException
()
throws
Exception
{
Map
<
String
,
String
>
KEYS
=
Collections
.
singletonMap
(
"valid-key"
,
INVALID_KEY
);
Map
<
String
,
String
>
KEYS
=
Collections
.
singletonMap
(
"valid-key"
,
INVALID_KEY
);
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
String
header
=
"{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}"
;
String
claims
=
"{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
consumeErrorWith
(
throwable
->
{
assertThat
(
throwable
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
)
.
getReason
()).
isEqualTo
(
Reason
.
INVALID_SIGNATURE
);
}).
verify
();
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
consumeErrorWith
((
ex
)
->
{
assertThat
(
ex
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
INVALID_SIGNATURE
);
}).
verify
();
}
@Test
public
void
validateTokenWhenTokenAlgorithmIsNotRS256ShouldThrowException
()
throws
Exception
{
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
VALID_KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
String
header
=
"{ \"alg\": \"HS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}"
;
String
claims
=
"{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
consumeErrorWith
(
throwable
->
{
assertThat
(
throwable
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
)
.
getReason
()).
isEqualTo
(
Reason
.
UNSUPPORTED_TOKEN_SIGNING_ALGORITHM
);
}).
verify
();
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
consumeErrorWith
((
ex
)
->
{
assertThat
(
ex
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
UNSUPPORTED_TOKEN_SIGNING_ALGORITHM
);
}).
verify
();
}
@Test
public
void
validateTokenWhenExpiredShouldThrowException
()
throws
Exception
{
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
VALID_KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
String
header
=
"{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}"
;
String
claims
=
"{ \"jti\": \"0236399c350c47f3ae77e67a75e75e7d\", \"exp\": 1477509977, \"scope\": [\"actuator.read\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
consumeErrorWith
(
throwable
->
{
assertThat
(
throwable
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
)
.
getReason
()).
isEqualTo
(
Reason
.
TOKEN_EXPIRED
);
}).
verify
();
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
consumeErrorWith
((
ex
)
->
{
assertThat
(
ex
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
TOKEN_EXPIRED
);
}).
verify
();
}
@Test
public
void
validateTokenWhenIssuerIsNotValidShouldThrowException
()
throws
Exception
{
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
VALID_KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://other-uaa.com"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://other-uaa.com"
));
String
header
=
"{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\", \"scope\": [\"actuator.read\"]}"
;
String
claims
=
"{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"foo.bar\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
consumeErrorWith
(
throwable
->
{
assertThat
(
throwable
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
)
.
getReason
()).
isEqualTo
(
Reason
.
INVALID_ISSUER
);
}).
verify
();
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
consumeErrorWith
((
ex
)
->
{
assertThat
(
ex
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
INVALID_ISSUER
);
}).
verify
();
}
@Test
public
void
validateTokenWhenAudienceIsNotValidShouldThrowException
()
throws
Exception
{
given
(
this
.
securityService
.
fetchTokenKeys
()).
willReturn
(
Mono
.
just
(
VALID_KEYS
));
given
(
this
.
securityService
.
getUaaUrl
()).
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
given
(
this
.
securityService
.
getUaaUrl
())
.
willReturn
(
Mono
.
just
(
"http://localhost:8080/uaa"
));
String
header
=
"{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}"
;
String
claims
=
"{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"foo.bar\"]}"
;
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
())))).
consumeErrorWith
(
throwable
->
{
assertThat
(
throwable
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
throwable
)
.
getReason
()).
isEqualTo
(
Reason
.
INVALID_AUDIENCE
);
}).
verify
();
StepVerifier
.
create
(
this
.
tokenValidator
.
validate
(
new
Token
(
getSignedToken
(
header
.
getBytes
(),
claims
.
getBytes
()))))
.
consumeErrorWith
((
ex
)
->
{
assertThat
(
ex
).
isExactlyInstanceOf
(
CloudFoundryAuthorizationException
.
class
);
assertThat
(((
CloudFoundryAuthorizationException
)
ex
).
getReason
())
.
isEqualTo
(
Reason
.
INVALID_AUDIENCE
);
}).
verify
();
}
private
String
getSignedToken
(
byte
[]
header
,
byte
[]
claims
)
throws
Exception
{
...
...
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptorTests.java
View file @
3f00ba3c
...
...
@@ -65,15 +65,13 @@ public class CloudFoundrySecurityInterceptorTests {
this
.
request
.
setMethod
(
"OPTIONS"
);
this
.
request
.
addHeader
(
HttpHeaders
.
ORIGIN
,
"http://example.com"
);
this
.
request
.
addHeader
(
HttpHeaders
.
ACCESS_CONTROL_REQUEST_METHOD
,
"GET"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
()).
isEqualTo
(
HttpStatus
.
OK
);
}
@Test
public
void
preHandleWhenTokenIsMissingShouldReturnFalse
()
throws
Exception
{
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
MISSING_AUTHORIZATION
.
getStatus
());
}
...
...
@@ -81,8 +79,7 @@ public class CloudFoundrySecurityInterceptorTests {
@Test
public
void
preHandleWhenTokenIsNotBearerShouldReturnFalse
()
throws
Exception
{
this
.
request
.
addHeader
(
"Authorization"
,
mockAccessToken
());
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
MISSING_AUTHORIZATION
.
getStatus
());
}
...
...
@@ -92,8 +89,7 @@ public class CloudFoundrySecurityInterceptorTests {
this
.
interceptor
=
new
CloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
this
.
securityService
,
null
);
this
.
request
.
addHeader
(
"Authorization"
,
"bearer "
+
mockAccessToken
());
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
.
getStatus
());
}
...
...
@@ -104,8 +100,7 @@ public class CloudFoundrySecurityInterceptorTests {
this
.
interceptor
=
new
CloudFoundrySecurityInterceptor
(
this
.
tokenValidator
,
null
,
"my-app-id"
);
this
.
request
.
addHeader
(
"Authorization"
,
"bearer "
+
mockAccessToken
());
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
())
.
isEqualTo
(
Reason
.
SERVICE_UNAVAILABLE
.
getStatus
());
}
...
...
@@ -116,8 +111,7 @@ public class CloudFoundrySecurityInterceptorTests {
this
.
request
.
addHeader
(
"Authorization"
,
"bearer "
+
accessToken
);
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
AccessLevel
.
RESTRICTED
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
assertThat
(
response
.
getStatus
()).
isEqualTo
(
Reason
.
ACCESS_DENIED
.
getStatus
());
}
...
...
@@ -127,8 +121,7 @@ public class CloudFoundrySecurityInterceptorTests {
this
.
request
.
addHeader
(
"Authorization"
,
"Bearer "
+
accessToken
);
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
AccessLevel
.
FULL
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"/a"
);
ArgumentCaptor
<
Token
>
tokenArgumentCaptor
=
ArgumentCaptor
.
forClass
(
Token
.
class
);
verify
(
this
.
tokenValidator
).
validate
(
tokenArgumentCaptor
.
capture
());
Token
token
=
tokenArgumentCaptor
.
getValue
();
...
...
@@ -144,8 +137,7 @@ public class CloudFoundrySecurityInterceptorTests {
this
.
request
.
addHeader
(
"Authorization"
,
"Bearer "
+
accessToken
);
given
(
this
.
securityService
.
getAccessLevel
(
accessToken
,
"my-app-id"
))
.
willReturn
(
AccessLevel
.
RESTRICTED
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"info"
);
SecurityResponse
response
=
this
.
interceptor
.
preHandle
(
this
.
request
,
"info"
);
ArgumentCaptor
<
Token
>
tokenArgumentCaptor
=
ArgumentCaptor
.
forClass
(
Token
.
class
);
verify
(
this
.
tokenValidator
).
validate
(
tokenArgumentCaptor
.
capture
());
Token
token
=
tokenArgumentCaptor
.
getValue
();
...
...
spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java
View file @
3f00ba3c
...
...
@@ -48,8 +48,10 @@ import org.springframework.web.util.pattern.PathPatternParser;
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 2.0.0
*/
public
abstract
class
AbstractWebFluxEndpointHandlerMapping
extends
RequestMappingInfoHandlerMapping
{
public
abstract
class
AbstractWebFluxEndpointHandlerMapping
extends
RequestMappingInfoHandlerMapping
{
private
static
final
PathPatternParser
pathPatternParser
=
new
PathPatternParser
();
...
...
@@ -103,18 +105,16 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
private
void
registerLinksMapping
()
{
registerMapping
(
new
RequestMappingInfo
(
new
PatternsRequestCondition
(
pathPatternParser
.
parse
(
this
.
endpointMapping
.
getPath
())),
new
RequestMethodsRequestCondition
(
RequestMethod
.
GET
),
null
,
null
,
null
,
new
ProducesRequestCondition
(
this
.
endpointMediaTypes
.
getProduced
()
.
toArray
(
new
String
[
this
.
endpointMediaTypes
.
getProduced
().
size
()])),
null
),
this
,
getLinks
());
PatternsRequestCondition
patterns
=
new
PatternsRequestCondition
(
pathPatternParser
.
parse
(
this
.
endpointMapping
.
getPath
()));
RequestMethodsRequestCondition
methods
=
new
RequestMethodsRequestCondition
(
RequestMethod
.
GET
);
ProducesRequestCondition
produces
=
new
ProducesRequestCondition
(
this
.
endpointMediaTypes
.
getProduced
().
toArray
(
new
String
[
this
.
endpointMediaTypes
.
getProduced
().
size
()]));
RequestMappingInfo
mapping
=
new
RequestMappingInfo
(
patterns
,
methods
,
null
,
null
,
null
,
produces
,
null
);
registerMapping
(
mapping
,
this
,
getLinks
());
}
protected
RequestMappingInfo
createRequestMappingInfo
(
...
...
@@ -193,4 +193,3 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
}
spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMapping.java
View file @
3f00ba3c
...
...
@@ -56,7 +56,8 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Andy Wilkinson
* @since 2.0.0
*/
public
class
WebFluxEndpointHandlerMapping
extends
AbstractWebFluxEndpointHandlerMapping
implements
InitializingBean
{
public
class
WebFluxEndpointHandlerMapping
extends
AbstractWebFluxEndpointHandlerMapping
implements
InitializingBean
{
private
final
Method
handleRead
=
ReflectionUtils
.
findMethod
(
ReadOperationHandler
.
class
,
"handle"
,
ServerWebExchange
.
class
);
...
...
@@ -111,8 +112,10 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle
}
registerMapping
(
createRequestMappingInfo
(
operation
),
operationType
==
OperationType
.
WRITE
?
new
WebFluxEndpointHandlerMapping
.
WriteOperationHandler
(
operationInvoker
)
:
new
WebFluxEndpointHandlerMapping
.
ReadOperationHandler
(
operationInvoker
),
?
new
WebFluxEndpointHandlerMapping
.
WriteOperationHandler
(
operationInvoker
)
:
new
WebFluxEndpointHandlerMapping
.
ReadOperationHandler
(
operationInvoker
),
operationType
==
OperationType
.
WRITE
?
this
.
handleWrite
:
this
.
handleRead
);
}
...
...
@@ -124,6 +127,7 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle
UriComponentsBuilder
.
fromUri
(
request
.
getURI
()).
replaceQuery
(
null
)
.
toUriString
()));
}
/**
* Base class for handlers for endpoint operations.
*/
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment