What is the replacement for TokenStore, TokenServices and JwtAccessTokenConverter in Spring Security 5 - spring-boot

I am upgrading Spring Boot from 2.3.12.RELEASE to 2.7.7 in my Kotlin project and found out that I have to change the code for Spring Security because Spring Security OAuth that was used in this project is no longer supported and has to be migrated to Spring Security 5+. I have this configuration that I want to migrate (some business data omitted):
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
class ResourceServerConfig : ResourceServerConfigurerAdapter() {
#Value("...")
private val claimAud: String? = null
#Value("...")
private val urlJwk: String? = null
override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.tokenStore(tokenStore())
resources.resourceId(claimAud)
}
#Bean
fun tokenStore(): TokenStore {
logger.info("JWK settings resource config: $urlJwk")
return JwkTokenStore(urlJwk, createJwtAccessTokenConverter())
}
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.anonymous().and().cors(withDefaults())
.authorizeRequests()
.mvcMatchers(BASE_PATH_PATTERN).permitAll()
.mvcMatchers(API_PATH_PATTERN).permitAll()
.mvcMatchers(ADMIN_PATH_PATTERN).authenticated()
}
#Bean
fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
#Bean
#Primary
fun tokenServices(): DefaultTokenServices {
val defaultTokenServices = DefaultTokenServices()
defaultTokenServices.setTokenStore(tokenStore())
return defaultTokenServices
}
#Bean
fun createJwtAccessTokenConverter(): JwtAccessTokenConverter? {
val converter = JwtAccessTokenConverter()
converter.accessTokenConverter = MyTokenConverter()
return converter
}
#Component
class MyTokenConverter : DefaultAccessTokenConverter(), JwtAccessTokenConverterConfigurer {
override fun extractAuthentication(claims: Map<String?, *>?): OAuth2Authentication {
val authentication = super.extractAuthentication(claims)
authentication.details = claims
return authentication
}
override fun configure(converter: JwtAccessTokenConverter) {
converter.accessTokenConverter = this
}
}
I don't know what to do with those methods related to TokenStore, TokenServices or TokenConverter, how to replace them.
I consulted the migrtation guide https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide but it seems that it lacks a lot of information, there is no specific guide anywhere actually for how to replace those components that I mentioned in my question.

Related

Too many arguments for public open fun http()

I'm following this tutorial to implement a authentication system in Kotlin using Spring Boot
The code works nicely until this part:
#Configuration
#EnableWebSecurity
class MultiHttpSecurityConfig {
#Bean
public fun userDetailsService(): UserDetailsService {
val users: User.UserBuilder = User.withDefaultPasswordEncoder()
val manager = InMemoryUserDetailsManager()
manager.createUser(users.username("user").password("password").roles("USER").build())
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build())
return manager
but in the next part i receive the following error and cant import anything:
#Order(1)
#Bean
open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher("/api/**")
authorizeRequests {
authorize(anyRequest, hasRole("ADMIN"))
}
httpBasic { }
}
return http.build()
}
#Bean
open fun formLoginFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
}
return http.build()
}
}
Am I missing something? Is something necessary besides Spring Security dependency?
Try to add import :
import org.springframework.security.config.web.server.invoke

Spring in Kotlin: from 5.3 to 6.0 security Configuration

I'm facing lots of issues in doing Spring security configurations that I used to have in v5.3 applied in v6.
This is the file I had
#Configuration
#EnableWebSecurity
class WebSecurityConfiguration : WebSecurityConfigurerAdapter() {
#Autowired
lateinit var service: UserService
/**
* Will be resolved into: WebSecurityEntryPoint injected instance.
*/
#Autowired
lateinit var unauthorizedHandler: AuthenticationEntryPoint
#Autowired
lateinit var successHandler: WebSecurityAuthSuccessHandler
#Autowired
override fun configure(auth: AuthenticationManagerBuilder) {
auth.authenticationProvider(authenticationProvider())
}
override fun configure(http: HttpSecurity?) {
http
?.csrf()?.disable()
?.exceptionHandling()
?.authenticationEntryPoint(unauthorizedHandler)
?.and()
?.authorizeRequests()
/**
* Access to Notes and Todos API calls is given to any authenticated system user.
*/
?.antMatchers("/notes")?.authenticated()
?.antMatchers("/notes/**")?.authenticated()
?.antMatchers("/todos")?.authenticated()
?.antMatchers("/todos/**")?.authenticated()
/**
* Access to User API calls is given only to Admin user.
*/
?.antMatchers("/users")?.hasAnyAuthority("ADMIN")
?.antMatchers("/users/**")?.hasAnyAuthority("ADMIN")
?.and()
?.formLogin()
?.successHandler(successHandler)
?.failureHandler(SimpleUrlAuthenticationFailureHandler())
?.and()
?.logout()
}
#Bean
fun authenticationProvider(): DaoAuthenticationProvider {
val authProvider = DaoAuthenticationProvider()
authProvider.setUserDetailsService(service)
authProvider.setPasswordEncoder(encoder())
return authProvider
}
#Bean
fun encoder(): PasswordEncoder = BCryptPasswordEncoder(11)
#Bean
fun accessDecisionManager(): AccessDecisionManager {
val decisionVoters = Arrays.asList(
WebExpressionVoter(),
RoleVoter(),
AuthenticatedVoter()
)
return UnanimousBased(decisionVoters)
}
}
I used the documentation in Spring.io
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
and I'm just hitting the wall since then. their documentation is not helpful and the new dependencies aren't working the same.
how can this be done now?
P.S: I often keep getting this error:
Caused by: java.lang.ClassNotFoundException: org.springframework.security.core.context.DeferredSecurityContext
which i couldn't find anywhere
Okey... I managed to solve it this way
first I had to add the security dependency for v6
implementation("org.springframework.security:spring-security-core:6.0.1")
and I made the Security Configuration this way
#Configuration
#EnableWebSecurity
class SecurityConfiguration(
private val userService: UserService,
private val unauthorizedHandler: AuthenticationEntryPoint,
private val successHandler: WebSecurityAuthSuccessHandler
) {
/**
* Will be resolved into: WebSecurityEntryPoint injected instance.
*/
#Bean
fun myPasswordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder(11)
}
#Primary
fun configureAuthentication(auth: AuthenticationManagerBuilder): AuthenticationManagerBuilder {
return auth.authenticationProvider(authenticationProvider())
}
#Bean
fun authenticationProvider(): DaoAuthenticationProvider {
val authProvider = DaoAuthenticationProvider()
authProvider.setUserDetailsService(userService)
authProvider.setPasswordEncoder(myPasswordEncoder())
return authProvider
}
#Bean
fun accessDecisionManager(): AccessDecisionManager {
val decisionVoter = listOf(
WebExpressionVoter(),
RoleVoter(),
AuthenticatedVoter()
)
return UnanimousBased(decisionVoter)
}
#Bean
fun configureHttpSecurity(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.authorizeHttpRequests()
/**
* Access to Notes and Todos API calls is given to any authenticated system user.
*/
.requestMatchers("/notes").authenticated()
.requestMatchers("/notes/**").authenticated()
.requestMatchers("/todos").authenticated()
.requestMatchers("/todos/**").authenticated()
/**
* Access to User API calls is given only to Admin user.
*/
.requestMatchers("/users").hasAnyAuthority("ADMIN")
.requestMatchers("/users/**").hasAnyAuthority("ADMIN")
.and()
.formLogin()
.successHandler(successHandler)
.failureHandler(SimpleUrlAuthenticationFailureHandler())
.and()
.logout()
return httpSecurity.build()
}
}

Spring Security without WebSecurityConfiguererAdapter, register two AuthenticationProvider

Since the deprication of WebSecurityConfiguererAdapter, I am not sure on how to implement my two custom AuthenticationProvider classes. Everything works in case I only use one of the AuthenticationProviders, but having two prevents both of them to function. I tried giving them #Order annotations but that doesn't work either. How would I register my custom AuthenticationProviders to make them work along each other and being called when the class defined in its support method matches. By the way, I'am a SpringBoot noob ;)
#Component
class AuthenticationService(
private val authenticationManager: AuthenticationManager,
) {
fun login(loginDto: LoginDto): Boolean {
authenticationManager.authenticate(
Token2(
loginDto.username, loginDto.password
)
)
return true
}
}
#Configuration
class ProdSecurityConfiguration {
#Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
#Bean
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager {
return authenticationConfiguration.authenticationManager
}
#Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf().disable().cors().disable()
http.authorizeRequests()
.antMatchers("/login", "/register").permitAll().anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
return http.build()
}
}
#Component
class CredentialsAuthProvider : AuthenticationProvider {
override fun authenticate(authentication: Authentication): Authentication {
return UsernamePasswordAuthenticationToken(
"myUser#mailbox.org", "password"
)
}
override fun supports(authentication: Class<*>?): Boolean {
return authentication == Token1::class.java
}
}
#Component
class CredentialsAuthProvider2 : AuthenticationProvider {
override fun authenticate(authentication: Authentication): Authentication {
return UsernamePasswordAuthenticationToken(
"myUser#mailbox.org", "password"
)
}
override fun supports(authentication: Class<*>?): Boolean {
return authentication == Token2::class.java
}
}
I removed the #Component annotation from my custom AuthenticationProviders, I created #Beans in my SecurityConfiguration class for each custom AuthenticationProvider where I directly call their constructor instead of using springs DI. Then used them within my AthenticationManager Bean where I return a ProviderManager.
Code snippets for explanation:
#Configuration
class ProdSecurityConfiguration {
#Bean
fun credentialsAuthProvider(): AuthenticationProvider {
return CredentialsAuthProvider()
}
#Bean
fun credentialsAuthProvider2(): AuthenticationProvider {
return CredentialsAuthProvider2()
}
#Bean
fun authenticationManager(): AuthenticationManager {
return ProviderManager(listOf(credentialsAuthProvider(), credentialsAuthProvider2()))
}
#Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf().disable().cors().disable()
http.authorizeRequests()
.antMatchers("/login", "/register").permitAll().anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
return http.build()
}
}

required a bean of type 'org.springframework.cloud.netflix.ribbon.SpringClientFactory' that could not be found

I have this test project which I would like to migrate to more recent version:
#Configuration
public class LoadbalancerConfig extends RibbonLoadBalancerClient {
public LoadbalancerConfig(SpringClientFactory clientFactory) {
super(clientFactory);
}
}
Full code example: https://github.com/rcbandit111/Generic_SO_POC/blob/master/src/main/java/org/merchant/database/service/sql/LoadbalancerConfig.java
Do you know how I can migrate this code to latest load balancer version?
I think examining the RibbonAutoConfiguration class gives you a good hint of how you should configure things.
First remove #Configuration from LoadbalancerConfig, I also renamed LoadbalancerConfig to CustomLoadbalancer to prevent confusion.
public class CustomLoadbalancer extends RibbonLoadBalancerClient {
public CustomLoadbalancer(SpringClientFactory clientFactory) {
super(clientFactory);
}
}
add the following dependency to your gradle
com.netflix.ribbon:ribbon:2.7.18
then add a configuration class like:
#Configuration
#ConditionalOnClass({Ribbon.class})
#AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
#ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
havingValue = "true", matchIfMissing = true)
#AutoConfigureBefore(LoadBalancerAutoConfiguration.class)
public class LoadBalancerClientConfig {
#Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
#Bean
public CustomLoadbalancer customLoadbalancer() {
return new CustomLoadbalancer(springClientFactory());
}
#Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
}
If you want to use Spring cloud load balancer instead of above configuration add spring-cloud-starter-loadbalancer dependency to your gradle.build and for configuration you only need this bean:
#LoadBalanced
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
}
This RestTemplate pretty works identical to standard RestTemplate class, except instead of using physical location of the service, you need to build the URL using Eureka service ID.
Here is an example of how could you possibly use it in your code
#Component
public class LoadBalancedTemplateClient {
#Autowired
RestTemplate restTemplate;
#Autowired
User user;
public ResponseEntity<Result> getResult() {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromHttpUrl("http://application-id/v1/") // application id registered in eureka
.queryParam("id", user.getUserId());
return restTemplate.getForEntity(uriComponentsBuilder.toUriString(),
Result.class);
}
}
Also if you wish to use reactive client the process is the same first define the bean:
#LoadBalanced
#Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
and then inject and use it when you need:
#Autowired
private WebClient.Builder webClient;
public Mono<String> doSomething() {
return webClient
.build()
.get()
.uri("http://application-id/v1/")
.retrieve()
.bodyToMono(String.class);
}
Also you can check documentation for additional information: documentation

How to configure i18n in Spring boot 2 + Webflux + Thymeleaf?

I just start a new project based on Spring boot 2 + Webflux. On upgrading version of spring boot and replace spring-boot-starter-web with spring-boot-starter-webflux classes like
WebMvcConfigurerAdapter
LocaleResolver
LocaleChangeInterceptor
are missing. How now can I configure defaultLocale, and interceptor to change the language?
Just add a WebFilter that sets the Accept-Language header from the value of a query parameter. The following example gets the language from the language query parameter on URIs like http://localhost:8080/examples?language=es:
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import reactor.core.publisher.Mono;
import static org.springframework.util.StringUtils.isEmpty;
#Component
public class LanguageQueryParameterWebFilter implements WebFilter {
private final ApplicationContext applicationContext;
private HttpWebHandlerAdapter httpWebHandlerAdapter;
public LanguageQueryParameterWebFilter(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#EventListener(ApplicationReadyEvent.class)
public void loadHttpHandler() {
this.httpWebHandlerAdapter = applicationContext.getBean(HttpWebHandlerAdapter.class);
}
#Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
final ServerHttpRequest request = exchange.getRequest();
final MultiValueMap<String, String> queryParams = request.getQueryParams();
final String languageValue = queryParams.getFirst("language");
final ServerWebExchange localizedExchange = getServerWebExchange(languageValue, exchange);
return chain.filter(localizedExchange);
}
private ServerWebExchange getServerWebExchange(final String languageValue, final ServerWebExchange exchange) {
return isEmpty(languageValue)
? exchange
: getLocalizedServerWebExchange(languageValue, exchange);
}
private ServerWebExchange getLocalizedServerWebExchange(final String languageValue, final ServerWebExchange exchange) {
final ServerHttpRequest httpRequest = exchange.getRequest()
.mutate()
.headers(httpHeaders -> httpHeaders.set("Accept-Language", languageValue))
.build();
return new DefaultServerWebExchange(httpRequest, exchange.getResponse(),
httpWebHandlerAdapter.getSessionManager(), httpWebHandlerAdapter.getCodecConfigurer(),
httpWebHandlerAdapter.getLocaleContextResolver());
}
}
It uses #EventListener(ApplicationReadyEvent.class) in order to avoid cyclic dependencies.
Feel free to test it and provide feedback on this POC.
With spring-boot-starter-webflux, there are
DelegatingWebFluxConfiguration
LocaleContextResolver
For example, to use a query parameter "lang" to explicitly control the locale:
Implement LocaleContextResolver, so that
resolveLocaleContext() returns a SimpleLocaleContext determined by a GET parameter of "lang". I name this implementation QueryParamLocaleContextResolver. Note that the default LocaleContextResolver is an org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver.
Create a #Configuration class that extends DelegatingWebFluxConfiguration. Override DelegatingWebFluxConfiguration.localeContextResolver() to return QueryParamLocaleContextResolver that we just created in step 1. Name this configuration class WebConfig.
In WebConfig, override DelegatingWebFluxConfiguration.configureViewResolvers() and add the ThymeleafReactiveViewResolver bean as a view resolver. We do this because, for some reason, DelegatingWebFluxConfiguration will miss ThymeleafReactiveViewResolver after step 2.
Also, I have to mention that, to use i18n with the reactive stack, this bean is necessary:
#Bean
public MessageSource messageSource() {
final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(5);
return messageSource;
}
After creating a natural template, some properties files, and a controller, you will see that:
localhost:8080/test?lang=zh gives you the Chinese version
localhost:8080/test?lang=en gives you the English version
Just don't forget <meta charset="UTF-8"> in <head>, otherwise you may see some nasty display of Chinese characters.
Starting with Spring Boot 2.4.0, the WebFluxAutoConfiguration contains a bean definition for the LocaleContextResolver, which allows us to inject custom LocaleContextResolver. For reference, the following is the default bean definition in Spring Boot 2.5.4 (the implementation may be different in earlier versions):
#Bean
#Override
#ConditionalOnMissingBean(name = WebHttpHandlerBuilder.LOCALE_CONTEXT_RESOLVER_BEAN_NAME)
public LocaleContextResolver localeContextResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleContextResolver(this.webProperties.getLocale());
}
AcceptHeaderLocaleContextResolver localeContextResolver = new AcceptHeaderLocaleContextResolver();
localeContextResolver.setDefaultLocale(this.webProperties.getLocale());
return localeContextResolver;
}
You can provide your own LocaleContextResolver implementation to get the locale from the query parameter by providing a custom bean definition:
//#Component("localeContextResolver")
#Component(WebHttpHandlerBuilder.LOCALE_CONTEXT_RESOLVER_BEAN_NAME)
public class RequestParamLocaleContextResolver implements LocaleContextResolver {
#Override
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {
List<String> lang = exchange.getRequest().getQueryParams().get("lang");
Locale targetLocale = null;
if (lang != null && !lang.isEmpty()) {
targetLocale = Locale.forLanguageTag(lang.get(0));
}
if (targetLocale == null) {
targetLocale = Locale.getDefault();
}
return new SimpleLocaleContext(targetLocale);
}
#Override
public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext) {
throw new UnsupportedOperationException(
"Cannot change lang query parameter - use a different locale context resolution strategy");
}
}
Note that the framework consumes the LocaleContextResolver with a specific name localeContextResolver (WebHttpHandlerBuilder.LOCALE_CONTEXT_RESOLVER_BEAN_NAME). You need to provide the bean with the given name. See #24209.
Another solution with spring boot starter web flux, which is much more cleaner, is to define your own HttpHandler using WebHttpHandlerBuilder in which you can set your LocaleContextResolver.
Documentation (see 1.2.2. WebHandler API) : https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-config-customize
MyLocaleContextResolver.java
public class MyLocaleContextResolver implements LocaleContextResolver {
#Override
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {
return new SimpleLocaleContext(Locale.FRENCH);
}
#Override
public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext) {
throw new UnsupportedOperationException();
}
}
Then in a config file (annotated with #Configuration) or in your spring boot application file, defined your own HttpHandler bean.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public HttpHandler httpHandler(ApplicationContext context) {
MyLocaleContextResolver localeContextResolver = new MyLocaleContextResolver();
return WebHttpHandlerBuilder.applicationContext(context)
.localeContextResolver(localeContextResolver) // set your own locale resolver
.build();
}
}
That's it!
Based on Jonatan Mendoza's answer, but simpliefied and in kotlin:
/**
* Override Accept-Language header by "lang" query parameter.
*/
#Component
class LanguageQueryParameterWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val languageValue = exchange.request.queryParams.getFirst("lang") ?: ""
if (languageValue.isEmpty()) {
return chain.filter(exchange)
}
return chain.filter(
exchange.mutate().request(
exchange.request
.mutate()
.headers {
it[HttpHeaders.ACCEPT_LANGUAGE] = languageValue
}
.build(),
).build(),
)
}
}

Resources