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.