I am trying to save oauth login credentials in redis . Using spring oauth2 library, and spring redis.
My saveAuthorizedClient method should be saving the key/val pair in redis, but nothing is being saved. Have attached a debugger and the key value pairs are correctly assigned, its the reactiveOauthRedisTemplate.opsForValue().set(key, authorizedClient) which seems to be not doing anything.
class CustomClientService(
val reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository,
val reactiveOauthRedisTemplate: ReactiveRedisTemplate<String, OAuth2AuthorizedClient>
) : ReactiveOAuth2AuthorizedClientService {
override fun <T : OAuth2AuthorizedClient> loadAuthorizedClient(clientRegistrationId: String, principalName: String): Mono<T> {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty")
Assert.hasText(principalName, "principalName cannot be empty")
return clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
.map { rediskey(clientRegistrationId, principalName) }
.flatMap { key: String ->
reactiveOauthRedisTemplate.opsForValue().get(key) as Mono<T>
}
}
override fun saveAuthorizedClient(authorizedClient: OAuth2AuthorizedClient, principal: Authentication): Mono<Void> {
val key = rediskey(authorizedClient.clientRegistration.registrationId, principal.name)
reactiveOauthRedisTemplate.opsForValue().set(key, authorizedClient)
.let {
val x = reactiveOauthRedisTemplate.opsForValue().get(key)
println("my redis data is $x")
x.map {
println("my data value is $it")
val token = x as OAuth2AuthorizedClient
}
}
return Mono.empty()
}
private fun rediskey(clientRegistrationId: String, principalName: String): String {
return "something..."
}
}
Here's my redis config
#Configuration
class RedisConfig {
#Bean
fun reactiveOauthRedisTemplate(reactiveRedisConnectionFactory: ReactiveRedisConnectionFactory,
resourceLoader: ResourceLoader): ReactiveRedisTemplate<String, OAuth2AuthorizedClient> {
val keySerializer: RedisSerializer<String> = StringRedisSerializer()
val defaultSerializer = JdkSerializationRedisSerializer(resourceLoader.classLoader)
val serializationContext = RedisSerializationContext
.newSerializationContext<String, OAuth2AuthorizedClient>(defaultSerializer).key(keySerializer).hashKey(keySerializer)
.build()
return ReactiveRedisTemplate(reactiveRedisConnectionFactory, serializationContext)
}
}
redis yaml config
spring:
redis:
host: localhost
From debugger , the redis connect has host and port correct. Any help here is appreciated.
Here's my redis client object from debugger
redisURI = {RedisURI#12018} "redis://127.0.0.1"
host = "127.0.0.1"
socket = null
sentinelMasterId = null
port = 6379
database = 0
clientName = null
username = null
password = null
ssl = false
verifyMode = {SslVerifyMode#12033} "FULL"
startTls = false
timeout = {Duration#12010} "PT1M"
sentinels = {ArrayList#12034} size = 0
Your usage of react client is not correct, you should not return Mono.empty() from saveAuthorizedClient, you should return Mono<Boolean> and subscribe to it. You can ignore the return value if you do not need the result, but you must subscribe it so that it will execute the business logic.
Related
I made a simple gRPC server in Kotlin with coroutines and a client with Java. In the cliente I enabled and configured a retry policy, but it does was not work. I speend a lot of time to find a solution, belivied that my client was broken, but the problem it was in the server. I will show you the code.
This is my proto file:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "br.com.will.protoclasses";
option java_outer_classname = "NotificationProto";
package notification;
service Notification {
rpc SendPush (SendPushNotificationRequest) returns (SendPushNotificationResponse);
}
message SendPushNotificationRequest {
string title = 1;
string message = 2;
string customer_id = 3;
}
message SendPushNotificationResponse {
string message = 1;
}
This is the client:
open class NotificationClient(private val channel: ManagedChannel) {
private val stub: NotificationGrpcKt.NotificationCoroutineStub =
NotificationGrpcKt.NotificationCoroutineStub(channel)
suspend fun send() {
val request =
SendPushNotificationRequest.newBuilder().setCustomerId(UUID.randomUUID().toString()).setMessage("test")
.setTitle("test").build()
val response = stub.sendPush(request)
println("Received: ${response.message}")
}
}
suspend fun main(args: Array<String>) {
val port = System.getenv("PORT")?.toInt() ?: 50051
val retryPolicy: MutableMap<String, Any> = HashMap()
retryPolicy["maxAttempts"] = 5.0
retryPolicy["initialBackoff"] = "10s"
retryPolicy["maxBackoff"] = "30s"
retryPolicy["backoffMultiplier"] = 2.0
retryPolicy["retryableStatusCodes"] = listOf<Any>("INTERNAL")
val methodConfig: MutableMap<String, Any> = HashMap()
val name: MutableMap<String, Any> = HashMap()
name["service"] = "notification.Notification"
name["method"] = "SendPush"
methodConfig["name"] = listOf<Any>(name)
methodConfig["retryPolicy"] = retryPolicy
val serviceConfig: MutableMap<String, Any> = HashMap()
serviceConfig["methodConfig"] = listOf<Any>(methodConfig)
print(serviceConfig)
val channel = ManagedChannelBuilder.forAddress("localhost", port)
.usePlaintext()
.defaultServiceConfig(serviceConfig)
.enableRetry()
.build()
val client = NotificationClient(channel)
client.send()
}
This is a part of my gRPC service, where i was testing the retry policy (the retry policy on client does not work with this implementation):
override suspend fun sendPush(request: SendPushNotificationRequest): SendPushNotificationResponse {
val count: Int = retryCounter.incrementAndGet()
log.info("Received a call on method sendPushNotification with payload -> $request")
if (random.nextFloat() < UNAVAILABLE_PERCENTAGE) {
log.info("Returning stubbed INTERNAL error. count: $count")
throw Status.INTERNAL.withDescription("error").asRuntimeException()
}
log.info("Returning successful Hello response, count: $count")
return SendPushNotificationResponse.newBuilder().setMessage("success").build()
}
Another implementation, but now using StreamObserver (This implementation works fine):
override fun sendPush(
request: SendPushNotificationRequest?,
responseObserver: StreamObserver<SendPushNotificationResponse>?
) {
log.info("Received a call on method sendPushNotification with payload -> $request")
val count: Int = retryCounter.incrementAndGet()
if (random.nextFloat() < UNAVAILABLE_PERCENTAGE) {
log.info("Returning stubbed UNAVAILABLE error. count: $count")
responseObserver!!.onError(
Status.UNAVAILABLE.withDescription("error").asRuntimeException()
)
} else {
log.info("Returning successful Hello response, count: $count")
responseObserver!!.onNext(SendPushNotificationResponse.newBuilder().setMessage("success").build())
return responseObserver.onCompleted()
}
}
The question is, whats is wrong? Can someone help me?
Does this code is generated by gRPC:
sendPush(request: SendPushNotificationRequest): SendPushNotificationResponse
gRPC depends on StreamObserver to send response to client after call responseObserver.onCompleted() or responseObserver.onError, please make sure your code could be work correctly.
I have this simple method which listens for Redis Pub/Sub message and emits Server sent event but for some reason it is not working. This is what I have.
#GetMapping
fun getNotifications(#RequestParam(name = "token", required = true) token: String): Flux<ServerSentEvent<Notification>> {
if (token.trim().isEmpty()) {
return Flux.error(ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token"))
}
val claims = jwtService.getAllClaimsFromToken(token) ?: return Flux.error(ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token"))
val userName = jwtService.getUsernameFromToken(claims) ?: return Flux.error(ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token"))
return userRepository.findOneByEmail(userName)
.filter { user -> checkAccess(user) }
.thenMany<ServerSentEvent<Notification>> {
notificationService.subscribe()
.map { ServerSentEvent.builder<Notification>()
.event("notification")
.data(it)
.build()
}.asFlux()
}.switchIfEmpty {
Flux.error<ResponseStatusException>(ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token expired"))
}
}
private fun checkAccess(user: User): Boolean {
if (!user.enabled || !user.account.enabled) {
return false
}
/// More checking... logic
return true
}
Is there anything that I am missing?
I am trying to create live data for the authToken in AccountManager.
This is how I am getting the auth token.
suspend fun Fragment.getAuthToken(): String? {
val am: AccountManager = AccountManager.get(activity)
val accounts: Array<out Account> = am.getAccountsByType(getAccountType())
var authToken: String? = null
if (accounts.isNotEmpty()) {
val account = accounts.first()
withContext(Dispatchers.IO) {
authToken = am.blockingGetAuthToken(account, getAccountType(), true)
}
}
return authToken
}
According to the documentation I should do something like this:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
}
However I can't figure out how to convert the example to match my case.
According to another link
you can use live-data builder for coroutine:
val token: LiveData<String> = liveData {
val tokenValue = someYourFragment.getAuthToken()
emit(tokenValue)
}
So as a follow-up on this question, I am writing a couple of sanity tests to verify Spring Boot's server.tomcat.remote-ip-header=X-Forwarded-For is working as intended...
#RestController
class RemoteAddressTesterController {
#GetMapping("/remoteAddr")
fun getRemoteAddress(request: HttpServletRequest):String = request.remoteAddr
}
#RunWith(SpringJUnit4ClassRunner::class)
#SpringBootTest(
properties = ["server.tomcat.remote-ip-header=X-Forwarded-For"],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
class RemoteAddressProviderIT{
private val NON_LOCAL_IP_1 = "212.13.43.12"
private val NON_LOCAL_IP_2 = "12.13.24.34"
private val LOCAL_HOST = "127.0.0.1"
#Inject
lateinit var rest:TestRestTemplate
private lateinit var headers:HttpHeaders
#Before
fun setup(){
headers = HttpHeaders()
}
works:
#Test
fun withoutXFF_localhost() {
val request = HttpEntity<Any>(headers)
val remoteAddress = rest.exchange("/remoteAddr", HttpMethod.GET,request,String::class.java).body!!
assertThat(remoteAddress).isEqualTo(LOCAL_HOST)
}
this works too:
#Test
fun XFF_clientWithProxy() {
headers["X-Forwarded-For"] = "$NON_LOCAL_IP_2,$NON_LOCAL_IP_1"
val request = HttpEntity<Any>(headers)
val remoteAddress = rest.exchange("/remoteAddr", HttpMethod.GET,request,String::class.java).body!!
assertThat(remoteAddress).isEqualTo(NON_LOCAL_IP_1)
}
This, however ...
#Test
fun withoutXFF_remoteOrigin(){
headers.origin = NON_LOCAL_IP_1
headers.host = InetSocketAddress(NON_LOCAL_IP_1,8080)
val request = HttpEntity<Any>(headers)
val remoteAddress = rest.exchange("/remoteAddr", HttpMethod.GET,request,String::class.java).body!!
assertThat(remoteAddress).isEqualTo(NON_LOCAL_IP_1)
}
gives me an
org.junit.ComparisonFailure:
Expected :"212.13.43.12"
Actual :"127.0.0.1"
My expectation, here, is that the IP is taken from the IP header, not the HTTP headers. Can I somehow change that IP to verify?
I have a stream:
val symbols: Single<List<Symbol>>
Now I want to transform the stream into a UI State with map():
private fun cool(): Single<SymbolContract.State> =
symbols.map { SymbolContract.State.Symbols(it) }
What I want to do is catch an error on the upstream symbols single, so that I can catch any errors and then return SymbolContract.State.GeneralError().
I want something like a onErrorMap() or something. Unfortunately, putting onErrorResumeItem on symbols doesn't work because it needs to return a List<Symbol>.
I can think of a few ugly ways to do this, but what's the cleanest?
I suggest you to use global handling error. I give you a sample so you can get the idea. (It is kotlin) and you can catch as many as exception you would like, some of them are my custom exceptions. Just bear in mind, this sample is about Reactive Webflux but you get the idea. It would be similar in others
#Configuration
class ExceptionTranslator {
#Bean
#Order(-1)
fun handle(objectMapper: ObjectMapper): ErrorWebExceptionHandler {
return ErrorWebExceptionHandler { exchange, ex ->
if (exchange.response.isCommitted) {
return#ErrorWebExceptionHandler Mono.error(ex)
}
val response = exchange.response
response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
response.headers.contentType = MediaType.APPLICATION_PROBLEM_JSON_UTF8
val url: String
var message = ex.message
var params = mutableMapOf<String, Any>()
when (ex) {
is ParametricException -> {
url = ex.url
params = ex.params
}
is BaseException -> {
url = ex.url
}
is BadCredentialsException -> {
url = INVALID_CREDENTIAL_TYPE
message = ex.message ?: "Wrong Credentials"
}
is ConcurrencyFailureException -> {
url = INTERNAL_TYPE
message = ERR_CONCURRENCY_FAILURE
}
is MethodArgumentNotValidException -> {
val result = ex.bindingResult
val fieldErrors = result.fieldErrors.map {
FieldErrorVM(it.objectName, it.field, it.code ?: "Unknown")
}
url = CONSTRAINT_VIOLATION_TYPE
message = ERR_VALIDATION
params = Collections.singletonMap("errors", fieldErrors)
}
else -> url = INTERNAL_TYPE
}
if (ex is BaseException) {
response.statusCode = HttpStatus.valueOf(ex.status.code())
}
val bytes = objectMapper.writeValueAsBytes(ProblemVM(url, message ?: "Internal Error", params))
val buffer = response.bufferFactory().wrap(bytes)
response.writeWith(Mono.just(buffer))
}
}
}
Found a clean answer:
private fun symbols(): Single<SymbolContract.State> =
symbols.map<SymbolContract.State> { SymbolContract.State.Symbols(it) }
.onErrorReturnItem(SymbolContract.State.Error(SymbolContract.State.Error.Type.Network))
The onErrorReturnItem has to come after the map, and the map needs explicit type parameters.