Summary

I wonder if I use webflux and oauth2 without using jwt, I want to store token in redis, how can I configure the Resource Server.

Actual Behavior

First of all, I retrieved token which is created by DefaultTokenServices and RedisTokenStore, and when it has been created, it was stored in redis. The return value was like the following json:

{
    "access_token": "0d0dd242-cbdf-4e47-842b-c6ad5edae332",
    "token_type": "bearer",
    "refresh_token": "2189ff33-eb96-49d1-ab60-6ad337ae06a9",
    "expires_in": 7199,
    "scope": "read write"
}

And then I try to access protected resource localhost:3660/api/auth/user to retrieve current user information, and I add a header like Authorization: Bearer 0d0dd242-cbdf-4e47-842b-c6ad5edae332

Then I got 401 Unauthorized.

Expected Behavior

I want to access the protected resource /api/auth/user, but I can't. I have searched for many solutions, and I still don't have any thought about this.

Configuration

@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, ReactiveAuthenticationManager reactiveAuthenticationManager) {
        http.authorizeExchange(
                exchange -> exchange.pathMatchers(HttpMethod.OPTIONS).permitAll()
                    .pathMatchers("/api/auth/signup/**", "/api/auth/password").permitAll()
                    .pathMatchers("/api/auth/login", "/api/auth/admin/login").permitAll()
                    .pathMatchers(HttpMethod.POST, "/api/auth/setting/mfa").hasAuthority(Role.MFA.getAuthority())
                    .pathMatchers("/api/auth/setting/sms").hasAnyAuthority(Role.SMS.getAuthority(), Role.USER.getAuthority())
                    .anyExchange().hasAnyAuthority(Role.USER.getAuthority(), Role.ADMIN.getAuthority(), Role.ROOT.getAuthority())
        );
        return http
                .authenticationManager(reactiveAuthenticationManager)
//                .oauth2ResourceServer(oAuth2ResourceServerSpec -> oAuth2ResourceServerSpec.)
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .build();
    }

    @Bean
    @ConditionalOnMissingBean(ReactiveAuthenticationManager.class)
    ReactiveAuthenticationManager reactiveAuthenticationManager(DefaultTokenServices tokenServices,
            RedisConnection redisConnection) {
        return new MscpAuthenticationManager(tokenServices, redisConnection);
    }
}

This is how I configure the webflux security configuration.

Version

Spring Boot 2.2.5.RELEASE Spring Cloud Hoxton.SR3 org.springframework.boot:spring-boot-starter-webflux org.springframework.security.oauth:spring-security-oauth2:2.3.8.RELEASE

Sample

@Slf4j
@Configuration
@EnableJpaAuditing
@EnableScheduling
public class MscpAuthorizationConfig {

    private final RedisConnectionFactory connectionFactory;
    private final ClientDetailsService clientDetailsService;

    public MscpAuthorizationConfig(RedisConnectionFactory connectionFactory,
                                   ClientDetailsService clientDetailsService) {
        this.connectionFactory = connectionFactory;
        this.clientDetailsService = clientDetailsService;
    }

    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(connectionFactory);
    }

    @Bean
    RouterFunction<ServerResponse> routes(AuthenticationHandler authenticationHandler) {
        return route()
                .path("/api/auth", api -> api
                        .path("/", authenticationHandler::routes)
                )
                .build();
    }

    @Bean
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        return tokenServices;
    }

    @Bean
    public RedisConnection redisConnection() {
        return connectionFactory.getConnection();
    }
}

This is my handler:

@Component
@Slf4j
public class AuthenticationHandler {

    private final AuthenticationService authenticationService;

    public AuthenticationHandler(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    public RouterFunction<ServerResponse> routes() {
        return route()
                .POST("/login", request -> request.bodyToMono(Login.class)
                        .switchIfEmpty(Mono.error(new ApplicationException(ApiErrors.SYSTEM_ERROR, "request parameters can not be null.")))
                        .publishOn(Schedulers.elastic())
                        .doOnNext(System.out::println)
                        .map(authenticationService::authenticate)
                        .flatMap(oAuth2AccessToken -> {
                            String format = String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, oAuth2AccessToken.getValue());
                            log.info("Token Type is [{}]", oAuth2AccessToken.getTokenType());
                            log.info("Expired Date is [{}]", oAuth2AccessToken.getExpiration());
                            return ok()
                                    .header(HttpHeaders.AUTHORIZATION, format)
                                    .header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
                                    .header(HttpHeaders.CACHE_CONTROL, "no-store")
                                    .header(HttpHeaders.PRAGMA, "no-cache")
                                    .bodyValue(oAuth2AccessToken);
                        })
                )
                .GET("/user", request -> ok().bodyValue(request.principal()))
                .build();
    }
}

This is application.yml:

server:
  port: 3660
spring:
  application:
    name: auth-api
  redis:
    host: 192.168.10.113
    lettuce:
      pool:
        max-wait: 3000ms
        max-idle: 15
        max-active: 15
        min-idle: 3
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    username: 
    password: 
    hikari:
      maximum-pool-size: 12
      minimum-idle: 5
      connection-test-query: SELECT 1
    url: jdbc:mysql://192.168.10.113:3306/mscp
  jpa:
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL8Dialect
#jwt:
#  alias: mscp
#  keystore: mscp.jks
#  keypass: bXNjcF9wYXNzd29yZA==
#  storepass: bXNjcF9wYXNzd29yZA==
logging:
  level:
    com.keanu.mscp.auth: debug
    org.springframework.web: trace

this is gradle:

plugins {
    id 'org.springframework.boot' version '2.2.5.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.keanu.mscp'
version = '1.0.0-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
    mavenLocal()
}

ext {
    set('springCloudVersion', "Hoxton.SR3")
}

dependencies {
    implementation 'com.keanu.mscp:java-infra:1.0.0-SNAPSHOT'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.apache.commons:commons-pool2'
//    implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'
    implementation 'org.springframework.security.oauth:spring-security-oauth2:2.3.8.RELEASE'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

test {
    useJUnitPlatform()
}

Comment From: rwinch

Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add some more details if you feel this is a genuine bug.