We are implementing a Spring Cloud Gateway application (with Webflux) that is mediating the OAuth2 authentication with Keycloak.
SCG checks if the Spring Session is active: if not, redirects to Keycloak login page and handles the response from the IDP. This process is executed out-of-the-box by the framework itself.
Our needs is to intercept the IDP Keycloak response in order to retrieve a field from the response payload.
Do you have any advices that will help us to accomplish this behavior?
Thanks!
You can implement ServerAuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
private ServerRedirectStrategy redirectStrategy;
public AuthenticationSuccessHandler(AuthenticationService authenticationService) {
redirectStrategy = new DefaultServerRedirectStrategy();
}
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
if(authentication instanceof OAuth2AuthenticationToken) {
//Your logic here to retrieve oauth2 user info
}
ServerWebExchange exchange = webFilterExchange.getExchange();
URI location = URI.create(httpRequest.getURI().getHost());
return redirectStrategy.sendRedirect(exchange, location);
}
}
And update your security configuration to include success handler:
#Configuration
public class SecurityConfiguration {
private AuthenticationSuccessHandler authSuccessHandler;
public SecurityConfiguration(AuthenticationSuccessHandler authSuccessHandler) {
this.authSuccessHandler = authSuccessHandler;
}
#Bean
SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchange -> exchange
//other security configs
.anyExchange().authenticated()
.and()
.oauth2Login(oauth2 -> oauth2
.authenticationSuccessHandler(authSuccessHandler)
);
return http.build();
}
}
Related
I am struggling with configuring security for my Spring Cloud Gateway service.
For now i have configured in my api-gateway just one route to user service /api/v1/users. Requests are correctly routed to user service untill I add Spring Security to the dependescies.
Even with that simple config, that should allow all traffic, I am still getting 401 Unathorized response:
#Configuration
#EnableWebFluxSecurity
class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity serverHttpSecurity) {
return serverHttpSecurity
.authorizeExchange()
.anyExchange().permitAll().and()
.csrf().disable()
.build();
}
}
What am I doing wrong?
You need to create user to do that. See the sample attached in below. I am using in-memory user to authenticate. Note in-memory user is just for testing purpose only.
#Configuration
public class InMemoryUserSecurityAdapter {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/school-library-service/**").authenticated()
.and().authenticationManager(reactiveAuthenticationManager())
.authorizeExchange().anyExchange().permitAll().and()
.httpBasic().and()
.build();
}
#Bean
ReactiveAuthenticationManager reactiveAuthenticationManager(){
return new UserDetailsRepositoryReactiveAuthenticationManager(getInMemoryUserDetails());
}
#Bean
public MapReactiveUserDetailsService getInMemoryUserDetails() {
UserDetails admin = User.withDefaultPasswordEncoder().username("admin1").password("password")
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(admin);
}
}
https://github.com/DeepuGeorgeJacob/school-management/blob/main/security/in-memory-user-security/src/main/java/com/school/management/config/InMemoryUserSecurityAdapter.java
Happy coding :)
I am implementing a Spring MVC REST web service and attempting to security it with Spring Security Oauth2.
My authorization URL is at the following address (note that there is no registration id):
http://localhost:8080/myoauthserver/oauthservlet
So, in my Spring Security Config, I have this:
#Bean
#Profile("dev")
public ClientRegistration devClientRegistration() {
// set up variables to pass to ClientRegistration
ClientRegistration result = ClientRegistration
.withRegistrationId("") // this obviously doesn't work
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri(authorizationUri)
.tokenUri(clientSecret)
.userInfoUri(userInfoUri)
.userNameAttributeName(userNameAttribute)
.redirectUri(redirectUrl)
.providerConfigurationMetadata(providerDetails)
.build();
return result;
}
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistrationRepository repository = new InMemoryClientRegistrationRepository(devClientRegistration());
return repository;
}
#Bean
#Profile("dev")
public SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
// this prevents throwing an exception in the oauth2 lambda function
Map<String, String> loginProps = ...
http
.authorizeRequests()
// what we want is for a few urls to be accessible to all, but most to require
// oauth authentication/authorization
.antMatchers("/login/**","/error/**", "/oauth2/**").anonymous()
.anyRequest().authenticated()
.and()
.oauth2Login(oauth2 -> {
oauth2
.clientRegistrationRepository(clientRegistrationRepository(loginPropsCopy))
.authorizationEndpoint()
.baseUri("http://localhost:8080/myoauthserver/oauthservlet");
}
);
return http.build();
}
How do I customize the entire authorization endpoint to not try to tack the registration id onto it?
I have a resource server done with Spring Boot. I'm using Spring Security 5.3 to authenticate and authorize the frontend exchange data. I've configured a authorization server "issuer-uri" in application.yml that provides and validates the access_token (jwt).
Until there ok. The problem that authorization server doesn't provide at once every user's information that I need in access_token or id_token. With the sub claim and access_token I need to do a request to endpoint to get more extra data about user.
I would like to know how can I do a request to get that information just when the user authenticates and put them into security context togheter the information that's already comes. So that way, I could get that information in some service when needed without make a request to endpoint each time:
SecurityContextHolder.getContext().getAuthentication().getDetails()
It's here my WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String CLAIM_ROLES = "role";
private static final String AUTHORITY_PREFIX = "ROLE_";
#Value("${sso.issuers_uri}")
private String issuers;
Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(authenticationManagers::get);
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] result = issuers.split(",");
List<String> arrIssuers = Arrays.asList(result);
arrIssuers.stream().forEach(issuer -> addManager(authenticationManagers, issuer));
http
.httpBasic().disable()
.formLogin(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(auth -> auth
.antMatchers(
"/*",
"/signin-oidc",
"/uri-login_unico",
"/assets/**","/views/**",
"index.html",
"/api/segmentos/listar_publicados",
"/api/modelos",
"/api/modelos/*"
).permitAll()
.antMatchers(
"/api/admin/**"
).hasRole("role.PGR.Admin")
.antMatchers(
"/api/govbr/**"
).hasAnyAuthority("SCOPE_govbr_empresa")
.anyRequest().authenticated()
).oauth2ResourceServer(oauth2ResourceServer -> {
oauth2ResourceServer.authenticationManagerResolver(this.authenticationManagerResolver);
});
}
public void addManager(Map<String, AuthenticationManager> authenticationManagers, String issuer) {
JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(JwtDecoders.fromOidcIssuerLocation(issuer));
authenticationProvider.setJwtAuthenticationConverter(getJwtAuthenticationConverter());
authenticationManagers.put(issuer, authenticationProvider::authenticate);
}
private Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(getJwtGrantedAuthoritiesConverter());
return jwtAuthenticationConverter;
}
private Converter<Jwt, Collection<GrantedAuthority>> getJwtGrantedAuthoritiesConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix(AUTHORITY_PREFIX);
converter.setAuthoritiesClaimName(CLAIM_ROLES);
return converter;
}
}
I don't know if I need to do a custom AuthenticationManger or if I can do this with a security filter after authenticated. If someone could help me, I really apprecite it. Tks!!!
in fact the spring-oauth project turn into maintenance mode we trying migrate our application into pure spring security 5 which support resource server configuration as well.
Our actual resource server configuration looks like this:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
authorizeRequests -> {
authorizeRequests.antMatchers("/api/unsecured/**").permitAll();
authorizeRequests.anyRequest().authenticated();
}
);
}
#Bean
#ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean
public TokenStore jwkTokenStore() {
return new JwkTokenStore("http://localhost:8080/...", new JwtAccessTokenConverter());
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(){
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
}
#Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
and these properties:
security:
oauth2:
client:
client-id: service-id
client-secret: secret
access-token-uri: http://localhost:8081/oauth/token
This resource server is configured to work with jwt token. To verify token uses rsa public key from link passes to jwkstore. It is also able call another resource server with Feign.
And this is new configuration:
#Configuration
static class OAuth2ResourceServerConfig extends WebSecurityConfigurerAdapter {
private final JwtDecoder jwtDecoder;
ResourceServerConfiguration(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/public/unsecured/**").permitAll()
.anyRequest().authenticated())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
.jwt(jwtConfigurer -> {
jwtConfigurer.decoder(NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/...").build());
jwtConfigurer.jwtAuthenticationConverter(tokenExtractor());
})
);
}
This configuration works fine to decode and verify tokens, but Feign doesn't work. Previous configuration with spring oauth2 supports Oauth2 feign interceptor which call authorization server to get its own access token. But I don't know how to configure this in spring security 5. This is flow which I need:
frontend client call spring resource server A with token
resource server A need data from resource server B
resource server A call authorization server to get access token with client_credentials grant type
resource server A call resource server B with its access token set to request header by feign
resource server A return all data to frontend client
Can you tell me how to configure 3. and 4. step in spring security 5 without spring's oauth project? Thank you.
I'm implementing an OAuth2 web application Client using Spring Boot 2.1.3 and Spring Security 5.1.3 that is obtaining JWT tokens from an authorization server through authorization code grant type and calls a protected resource server.
This is how the implementation looks up till now:
Security configuration and a restTemplate bean used to call the protected resource:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.and().logout().logoutSuccessUrl("/");
}
#Bean
public RestTemplate restTemplate(OAuth2AuthorizedClientService clientService) {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new AuthorizationHeaderInterceptor(clientService));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
The interceptor that adds the authorization header (from the framework's InMemoryOAuth2AuthorizedClientService) in the restTemplate:
public class AuthorizationHeaderInterceptor implements ClientHttpRequestInterceptor {
private OAuth2AuthorizedClientService clientService;
public AuthorizationHeaderInterceptor(OAuth2AuthorizedClientService clientService) {
this.clientService = clientService;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String accessToken = null;
if (authentication != null && authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) authentication;
String clientRegistrationId = auth.getAuthorizedClientRegistrationId();
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(clientRegistrationId, auth.getName());
accessToken = client.getAccessToken().getTokenValue();
request.getHeaders().add("Authorization", "Bearer " + accessToken);
}
return execution.execute(request, bytes);
}
}
And the controller that calls the protected resource server:
#Controller
#RequestMapping("/profile")
public class ProfileController {
#Autowired
private RestTemplate restTemplate;
#Value("${oauth.resourceServerBase}")
private String resourceServerBase;
#GetMapping
public String getProfile(Model model) {
Profile profile = restTemplate.getForEntity(resourceServerBase + "/api/profile/", Profile.class).getBody();
model.addAttribute("profile", profile);
return "profile";
}
}
The OAuth2 client configuration is directly in the application.yml:
spring:
security:
oauth2:
client:
registration:
auth-server:
client-id: webClient
client-secret: clientSecret
scope: read,write
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8081/client/login/oauth2/code/auth-server
provider:
auth-server:
authorization-uri: http://localhost:8080/auth-server/oauth/authorize
token-uri: http://localhost:8080/auth-server/oauth/token
user-info-uri: http://localhost:8082/resource-server/users/info
user-name-attribute: user_name
After doing some debugging I've observed that at the end of a successful authentication flow through OAuth2LoginAuthtenticationFilter the framework is storing the obtained access and refresh JWT tokens under OAuth2AuthorizedClient model in memory through the provided InMemoryOAuth2AuthorizedClientService.
I am trying to find out how to override this behaviour so that the tokens can remain available after a server restart. And also keep the user logged in based on this.
Should I just provide a custom OAuth2AuthorizedClientService implementation? How could I configure Spring Security to use it? And should this custom implementation store the tokens in a cookie?
Should I just provide a custom OAuth2AuthorizedClientService
implementation?
I think yes, for solving your use case
How could I configure Spring Security to use it?
From spring doc:
If you would like to provide a custom implementation of
AuthorizationRequestRepository that stores the attributes of
OAuth2AuthorizationRequest in a Cookie, you may configure it as shown
in the following example:
#EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Client()
.authorizationCodeGrant()
.authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
...
}
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
return new HttpCookieOAuth2AuthorizationRequestRepository();
}
}