Spring RSocket Accessing setup information - spring

I am using Spring RSocket support and Spring Security RSocket. I setup the security configuration so that on each request/response interaction, the requester send bearer token which my code can access after successful authentication by using ReactiveSecurityContextHolder.
My current configuration looks like this :
#EnableRSocketSecurity
#SpringBootApplication
public class MyConfiguration{
#Bean
fun rSocketStrategies() : RSocketStrategies = RSocketStrategies.builder()
.encoder(BearerTokenAuthenticationEncoder())
.encoder(KotlinSerializationJsonEncoder())
.decoder(KotlinSerializationJsonDecoder())
.build()
#Bean
fun rSocketInterceptor(rSocketSecurity: RSocketSecurity,authenticationManager: ReactiveAuthenticationManager) : PayloadSocketAcceptorInterceptor =
rSocketSecurity
.authorizePayload { authorize -> authorize.anyExchange().authenticated() }
.jwt { jwtSpec -> jwtSpec.authenticationManager(authenticationManager) }
.build()
#Bean
fun authenticationManager(...) = ReactiveAuthenticationManager{
...
}
}
I want to ask if it's possible to do the authentication once during setup exchange and then access the token in RSocket controllers? And if yes, what kind of configuration changes should I make?
My controllers' code currently looks like this(which works only because the token is included in every request/response interaction):
#MessageMapping("${v1}.message")
#Controller
class MessageRSocketController(private val messageService: MessageService) {
#MessageMapping("stream")
suspend fun messages() : Flow<EncryptedMessageWithID> {
val jwt = ReactiveSecurityContextHolder.getContext().awaitSingle().authentication.name
return messageService.messages(jwt)
}
}

Related

How to make the path public in RSocketSecurity(Spring)

I have config class for RSocketSecurity
Something like that
#Configuration
#EnableRSocketSecurity
#EnableReactiveMethodSecurity
class RSocketAuthConfiguration {
and authorization for it (allows only authenticated users to subscribe )
security.addPayloadInterceptor(interceptor).authorizePayload {
it.setup().authenticated().anyRequest().permitAll()
}
I want to set some routes with public access, but most of them should be with authorization. What is the best way to achieve that?
Spring Security Rsocket configures the setup and route respectively.
The following is an example of the configuration part.
#Bean
public PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
return rsocket
.authorizePayload(
authorize -> {
authorize
// must have ROLE_SETUP to make connection
.setup().hasRole("SETUP")
// must have ROLE_ADMIN for routes starting with "greet."
.route("greet*").hasRole("ADMIN")
// any other request must be authenticated for
.anyRequest().authenticated();
}
)
.basicAuthentication(Customizer.withDefaults())
.build();
}
Get the complete example from my Github.
Something along the following lines should work:
#Configuration
#EnableRSocketSecurity
#EnableReactiveMethodSecurity
class RSocketSecurityConfiguration(val authenticationService: AuthenticationService) {
#Bean
fun authorization(security: RSocketSecurity): PayloadSocketAcceptorInterceptor {
return security
.authorizePayload {
it.route("route-A").hasRole("role-A")
.route("route-B").permitAll()
}
.simpleAuthentication(Customizer.withDefaults())
.authenticationManager(authenticationService)
.build()
}
}
route-A is authenticated and requires role-A while route-B is publicly available.

Spring Boot Social Login and Google Calendar API

Problem
Reuse End-User Google Authentication via Spring Security OAuth2 to access Google Calendar API in Web Application
Description
I was able to create a small Spring Boot Web application with Login through Spring Security
application.yaml
spring:
security:
oauth2:
client:
registration:
google:
client-id: <id>
client-secret: <secret>
scope:
- email
- profile
- https://www.googleapis.com/auth/calendar.readonly
When application starts I can access http://localhost:8080/user and user is asked for google login. After successful login profile json is shown in a browser as the response from:
SecurityController
#RestController
class SecurityController {
#RequestMapping("/user")
fun user(principal: Principal): Principal {
return principal
}
}
SecurityConfiguration.kt
#Configuration
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
}
}
Question
I want to reuse this authentication to retrieve all user's Calendar Events. The following code is taken from google's tutorial on accessing calendar API but it creates a completely independent authorization flow and asks user to log in.
#Throws(IOException::class)
private fun getCredentials(httpTransport: NetHttpTransport): Credential {
val clientSecrets = loadClientSecrets()
return triggerUserAuthorization(httpTransport, clientSecrets)
}
private fun loadClientSecrets(): GoogleClientSecrets {
val `in` = CalendarQuickstart::class.java.getResourceAsStream(CREDENTIALS_FILE_PATH)
?: throw FileNotFoundException("Resource not found: $CREDENTIALS_FILE_PATH")
return GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(`in`))
}
private fun triggerUserAuthorization(httpTransport: NetHttpTransport, clientSecrets: GoogleClientSecrets): Credential {
val flow = GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build()
val receiver = LocalServerReceiver.Builder().setPort(8880).build()
return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
}
How can I reuse already done authentication to access end user's calendar events on Google account?
If I understand correctly, what you mean be reusing the authentication is that you want to use the access and refresh tokens Spring retrieved for you in order to use them for requests against Google API.
The user authentication details can be injected into an endpoint method like this:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class FooController(val historyService: HistoryService) {
#GetMapping("/foo")
fun foo(#RegisteredOAuth2AuthorizedClient("google") user: OAuth2AuthorizedClient) {
user.accessToken
}
}
With the details in OAuth2AuthorizedClient you should be able to do anything you need with the google API.
If you need to access the API without a user making a request to your service, you can inject OAuth2AuthorizedClientService into a managed component, and use it like this:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
import org.springframework.stereotype.Service
#Service
class FooService(val clientService: OAuth2AuthorizedClientService) {
fun foo() {
val user = clientService.loadAuthorizedClient<OAuth2AuthorizedClient>("google", "principal-name")
user.accessToken
}
}

FeignClient is passing on headers

I have about 10 microservices all built with Spring boot 2 using Eureka and FeignClients. My microservices use certain header values to keep track of data so when a FeignClient is used it needs to pass on certain values that are in the incoming request. So if Microservice 1 does a call to Microservice 2 it must pass on the headers from the incoming request onto microservice 2. I haven't been able to find out how I can do that. I understand their is #Header however if you have 20 FeignClients then you don't want to have to manually add the #header to all the FeignClients. Can you indicate that FeignClients must read a certain header from the incoming request and pass it on in the FeignClient?
You can use request interceptor in Feign.
Example Implementation:
Request Interceptor:
#Component
public class MyRequestInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String authorization = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
if(null != authorization) {
template.header(HttpHeaders.AUTHORIZATION, authorization);
}
}
}
Bean Configuration:
#Configuration
public class CustomFeignConfig {
#Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
#Bean
public MyRequestInterceptor basicAuthRequestInterceptor() {
return new MyRequestInterceptor();
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
}

How to define global static header on Spring Boot Feign Client

I have a spring boot app and want to create a Feign client which has a statically defined header value (for auth, but not basic auth). I found the #Headers annotation but it doesn't seem to work in the realm of Spring Boot. My suspicion is this has something to do with it using the SpringMvcContract.
Here's the code I want to work:
#FeignClient(name = "foo", url = "http://localhost:4444/feign")
#Headers({"myHeader:value"})
public interface LocalhostClient {
But it does not add the headers.
I made a clean spring boot app with my attempts and posted to github here: github example
The only way I was able to make it work was to define the RequestInterceptor as a global bean, but I don't want to do that because it would impact other clients.
You can also achieve this by adding header to individual methods as follows:
#RequestMapping(method = RequestMethod.GET, path = "/resource", headers = {"myHeader=value"})
Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2) discusses a solution for dynamic values using #RequestHeader.
You can set a specific configuration class on your feign interface and define a RequestInterceptor bean in there. For example:
#FeignClient(name = "foo", url = "http://localhost:4444/feign",
configuration = FeignConfiguration.class)
public interface LocalhostClient {
}
#Configuration
public class FeignConfiguration {
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
// Do what you want to do
}
};
}
}
You could specify that through the application.yml file:
feign:
client:
config:
default:
defaultRequestHeaders:
Authorization:
- Basic 3ncond2dS3cr2t
otherHeader:
- value
Note that this will be applicable to all your Feign Clients if it happened that you're using more than one. If that's the case, you could add a section per client instead of adding this to the default section.
Try this
#Component
public class AuthFeignInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
template.header("Header_name","Value");
}
}
}

How to overwrite Spring Cloud OAuth2 client autoconfiguration?

We want to setup a microservice which provides a REST API so it is configured as a OAuth2 resource server. This service should also act as a OAuth2 client with the client credential grant. Here is the configuration:
spring.oauth2.client.id=clientCredentialsResource
spring.oauth2.client.accessTokenUri=http://localhost:9003/oauth/token
spring.oauth2.client.userAuthorizationUri=http://localhost:9003/oauth/authorize
spring.oauth2.client.grantType=client_credentials
spring.oauth2.client.clientId=<service-id>
spring.oauth2.client.clientSecret=<service-pw>
The resource server part works fine. For the client part we want to use Feign, Ribbon and Eureka:
#FeignClient("user")
public interface UserClient
{
#RequestMapping( method = RequestMethod.GET, value = "/user/{uid}")
Map<String, String> getUser(#PathVariable("uid") String uid);
}
Based on the gist in issue https://github.com/spring-cloud/spring-cloud-security/issues/56 I created a feign request intercepter which sets the access token from the autowired OAuth2RestOperations template in the feign request header
#Autowired
private OAuth2RestOperations restTemplate;
template.header(headerName, String.format("%s %s", tokenTypeName, restTemplate.getAccessToken().toString()));
But this gives me the error on calling the user service:
error="access_denied", error_description="Unable to obtain a new access token for resource 'clientCredentialsResource'. The provider manager is not configured to support it.
As I can see the OAuth2ClientAutoConfiguration creates always an instance of AuthorizationCodeResourceDetails for an web application but not the required ClientCredentialsResourceDetails which is only used for non-web applications. In the end the no access token privider is responsible for the resource details and the call failed in
AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:146)
I tried to overwrite the auto configuration but failed. Can somebody please give me a hint how to do it?
To switch off this piece of autoconfiguration you can set spring.oauth2.client.clientId= (empty), (per the source code), otherwise you have to "exclude" it in the #EnableAutoConfiguration. If you do that you can just set up your own OAuth2RestTemplate and fill in the "real" client ID from your own configuration, e.g.
#Configuration
#EnableOAuth2Client
public class MyConfiguration {
#Value("myClientId")
String myClientId;
#Bean
#ConfigurationProperties("spring.oauth2.client")
#Primary
public ClientCredentialsResourceDetails oauth2RemoteResource() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setClientId(myClientId);
return details;
}
#Bean
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
}
#Bean
#Primary
public OAuth2RestTemplate oauth2RestTemplate(
OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
OAuth2RestTemplate template = new OAuth2RestTemplate(details,
oauth2ClientContext);
return template;
}
}

Resources