How to emulate Oauth2RestTemplate getAccessToken with WebClient - spring-security

I have a Oauth2 client app which other apps are using to get access token against Keycloak (Authorization server). Internally i was using Oauth2RestTemplate but as its marked deprecated some links have suggested using WebClient. I have done the Webclient configuration as per spring docs for password grant OAuth2 client, but have not found a way to get access token from it. Any leads here would be appreciated
`Web Client Configuration:
#Configuration
public class WebClientConfig {
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("keycloak");
return WebClient.builder().apply(oauth2Client.oauth2Configuration()).build();
}
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.password()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager (
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
return authorizeRequest -> {
Map<String, Object> contextAttributes = Collections.emptyMap();
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = new HashMap<>();
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return contextAttributes;
};
}
}
Now what i am looking is to emulate Oauth2RestTemplate.getAccessToken() sort of behaviour with WebClient,
where user can generate access token using password grant.
Please let me know if my understanding is wrong.
`

Related

Is it neseccary to use AuthenticationManager to load user from database when authorizing in Spring security?

I have a controller that authorizes user:
#PostMapping
public ResponseEntity<Token> signInUser(Credentials credentials) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword())
);
Token token = tokenService.generateToken(authentication);
return ResponseEntity.ok(token);
}
"Credentials" is just a simple data object, generated by swagger:
Credentials
AuthenticationManager bean:
#Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {
var authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
return new ProviderManager(authProvider);
}
As I understand, AuthenticationManager only retrieves user from the database and checks if passwords match. And I would like to write all this logic myself. Do I have to write my own implementation of AuthenticationManager or I can just write UserService that will not implement anything?
For example, like these:
UserController:
#PostMapping
public ResponseEntity<Token> signInUser(Credentials credentials) {
Token token = userService.signIn(credentials);
return ResponseEntity.ok(token);
}
UserService:
public Token signIn(Credentials credentials) {
Optional<UserEntity> userEntityOpt =
userEntityRepo.findByUsername(credentials.getUsername());
return tokenService.generateToken(userEntityOpt.get());
}
Validation and stuff omitted

How can a jwt protected resource server call userinfo?

The documentation at spring security is missing important detail. Our idp does not provide an introspection link, and our resource server is not a client in its own right. It receives JWT access tokens from the actual client, and "needs to know" details about the user associated with the access token.
In our case standard jwt processing gives us a useful start, but we need to fill out the authentication with the claims from userinfo.
How do we 1. get a baseline valid oauth2 authentication, 2. fill it out with the results of the userinfo call.
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
private final WebClient rest = WebClient.create();
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token);
return makeUserInfoRequest(authorized);
}
}
Current implementation using a converter:
#Configuration
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired JwtConverterWithUserInfo jwtConverter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests(authz -> authz
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll())
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtConverter);
}
}
#Configuration
public class WebClientConfig {
/**
* Provides a Web-Client Bean containing the bearer token of the authenticated user.
*/
#Bean
WebClient webClient(){
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(5))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
}
#Component
#Log4j2
public class JwtConverterWithUserInfo implements Converter<Jwt, AbstractAuthenticationToken> {
#Autowired WebClient webClient;
#Value("${userinfo-endpoint}")
String userinfoEndpoint;
#SuppressWarnings("unchecked")
#Override
public AbstractAuthenticationToken convert(Jwt jwt) {
String token = jwt.getTokenValue();
log.debug("Calling userinfo endpoint for token: {}", token);
String identityType = jwt.getClaimAsString("identity_type");
Map<String,Object> userInfo = new HashMap<>();
if ("user".equals(identityType)) {
// invoke the userinfo endpoint
userInfo =
webClient.get()
.uri(userinfoEndpoint)
.headers(h -> h.setBearerAuth(token))
.retrieve()
.onStatus(s -> s.value() >= HttpStatus.SC_BAD_REQUEST, response -> response.bodyToMono(String.class).flatMap(body -> {
return Mono.error(new HttpException(String.format("%s, %s", response.statusCode(), body)));
}))
.bodyToMono(Map.class)
.block();
log.debug("User info Map is: {}",userInfo);
// construct an Authentication including the userinfo
OidcIdToken oidcIdToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
OidcUserInfo oidcUserInfo = new OidcUserInfo(userInfo);
List<OidcUserAuthority> authorities = new ArrayList<>();
if (oidcIdToken.hasClaim("scope")) {
String scope = String.format("SCOPE_%s", oidcIdToken.getClaimAsString("scope"));
authorities.add(new OidcUserAuthority(scope, oidcIdToken, oidcUserInfo));
}
OidcUser oidcUser = new DefaultOidcUser(authorities, oidcIdToken, oidcUserInfo, IdTokenClaimNames.SUB);
//TODO replace this OAuth2 Client authentication with a more appropriate Resource Server equivalent
return new OAuth2AuthenticationTokenWithCredentials(oidcUser, authorities, oidcUser.getName());
} else {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (jwt.hasClaim("scope")) {
authorities.add(new SimpleGrantedAuthority(String.format("SCOPE_%s", jwt.getClaimAsString("scope"))));
}
return new JwtAuthenticationToken(jwt, authorities);
}
}
}
public class OAuth2AuthenticationTokenWithCredentials extends OAuth2AuthenticationToken {
public OAuth2AuthenticationTokenWithCredentials(OAuth2User principal,
Collection<? extends GrantedAuthority> authorities,
String authorizedClientRegistrationId) {
super(principal, authorities, authorizedClientRegistrationId);
}
#Override
public Object getCredentials() {
return ((OidcUser) this.getPrincipal()).getIdToken();
}
}
Instead of a custom OpaqueTokenIntrospector, try a custom JwtAuthenticationConverter:
#Component
public class UserInfoJwtAuthenticationConverter implements Converter<Jwt, BearerTokenAuthentication> {
private final ClientRegistrationRepository clients;
private final JwtGrantedAuthoritiesConverter authoritiesConverter =
new JwtGrantedAuthoritiesConverter();
#Override
public BearerTokenAuthentication convert(Jwt jwt) {
// Spring Security has already verified the JWT at this point
OAuth2AuthenticatedPrincipal principal = invokeUserInfo(jwt);
Instant issuedAt = jwt.getIssuedAt();
Instant expiresAt = jwt.getExpiresAt();
OAuth2AccessToken token = new OAuth2AccessToken(
BEARER, jwt.getTokenValue(), issuedAt, expiresAt);
Collection<GrantedAuthority> authorities = this.authoritiesConverter.convert(jwt);
return new BearerTokenAuthentication(principal, token, authorities);
}
private OAuth2AuthenticatedPrincipal invokeUserInfo(Jwt jwt) {
ClientRegistration registration =
this.clients.findByRegistrationId("registration-id");
OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest(
registration, jwt.getTokenValue());
return this.oauth2UserService.loadUser(oauth2UserRequest);
}
}
And then wire into the DSL like so:
#Bean
SecurityFilterChain web(
HttpSecurity http, UserInfoJwtAuthenticationConverter authenticationConverter) {
http
.oauth2ResourceServer((oauth2) -> oauth2
.jwt((jwt) -> jwt.jwtAuthenticationConverter())
);
return http.build();
}
our resource server is not a client in its own right
oauth2-client is where Spring Security's support for invoking /userinfo lives and ClientRegistration is where the application's credentials are stored for addressing /userinfo. If you don't have those, then you are on your own to invoke the /userinfo endpoint yourself. Nimbus provides good support, or you may be able to simply use RestTemplate.

Success Handler for Multiple Spring Security SAML RelyingPartyRegistrations

I am using Spring Security 5.5.3 to hit two separate SAML Identity Providers (IDP) (similar to, say, Google and Facebook, but not OAuth).
So, in my Security configuration, I define the two RelyingPartyRegistration beans:
public RelyingPartyRegistration site1RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("site1")
// rest of configuration
.build();
return registration;
}
public RelyingPartyRegistration site2RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("site2")
// rest of configuration
.build();
return registration;
}
#Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() throws Exception {
Collection<RelyingPartyRegistration> registrations = Collections.unmodifiableList(
Arrays.asList(
site1RelyingPartyRegistration(),
site2elyingPartyRegistration())
);
InMemoryRelyingPartyRegistrationRepository repository = new InMemoryRelyingPartyRegistrationRepository(registrations);
return repository;
}
Then I define my SuccessHandler:
public class MySuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
// need to determine whether the success came from site1 or site2
}
}
My question is, since I can authenticate from more than one Identity Provider, how can I find out which site it was? The only idea I have is to pull the entire <samlp:Response> from the Saml2AuthenticationToken and parse it to an XML document to get a single String attribute.
Is there an easier way?
In Spring Security 5.6, it's included in the Authentication:
Saml2AuthenticatedPrincipal principal =
(Saml2AuthenticatedPrincipal) authentication.getPrincipal();
String registrationId = principal.getRelyingPartyRegistrationId();
Before Spring Security 5.6, you can customize the Saml2AuthenticatedPrincipal in the authentication provider:
OpenSaml4AuthenticationProvider provider =
new OpenSaml4AuthenticationProvider();
provider.setResponseAuthenticationConverter((params) -> {
Response response = params.getResponse();
Saml2AuthenticationToken token = params.getToken();
RelyingPartyRegistration registration = token.getRelyingPartyRegistration();
String registrationId = registration.getRegistrationId();
// ... create custom authentication that contains the registration id
});

How to add(overwrite) expiry time for oAuth2 access token in spring + java

I have a situation where the authorisation server is not returning expires_in field to the token response, but the token expires after certain time. Can I set this manually somewhere in my code ?
Below is my code for ROPC.
#Bean(name = “myROPCRestTemplate")
public OAuth2RestTemplate myROPCRestTemplate() {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(myPasswordResourceDetails());
restTemplate.setAccessTokenProvider(getAccessTokenProvider());
return restTemplate;
}
private AccessTokenProvider getAccessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider resourceOwnerPasswordAccessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return new AccessTokenProviderChain(Collections.singletonList(resourceOwnerPasswordAccessTokenProvider));
}
private OAuth2ProtectedResourceDetails myPasswordResourceDetails() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setUsername(username);
resource.setPassword(password);
resource.setClientAuthenticationScheme(AuthenticationScheme.form);
resource.setGrantType("password");
return resource;
}
I know this is an old question but maybe someone need to override AccessToken implementation which is DefaultOAuth2AccessToken under spring security oauth2 autoconfigure project, here is the one workaround that we used
Our approach was not extend default access token or override new accesstoken from scratch with using OAuth2AccessToken, instead create ClientContext which is extend DefaultOAuth2ClientContext and make necessary changes on same AccessToken during set operation.
Here is the code sample, first extends client context, create a new component and make neccessary changes in setAccessToken (in this case setting exiparation) :
#Component
public class MyOAuth2ClientContext extends DefaultOAuth2ClientContext {
#Override
public void setAccessToken(OAuth2AccessToken accessToken) {
DefaultOAuth2AccessToken dxpAccessToken = new DefaultOAuth2AccessToken(accessToken);
dxpAccessToken.setExpiration(new Date());
super.setAccessToken(dxpAccessToken);
}
}
And finaly use this context when constructing your OAuth2RestTemplate use your own context :
#Configuration
public class MyWebConfiguration {
#Resource MyOAuth2ClientContext myOAuth2ClientContext;
#Bean
#ConfigurationProperties("spring.security.oauth2.client.authserver")
protected ClientCredentialsResourceDetails authServerDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean(name = "myRestTemplate")
protected RestTemplate myRestTemplate() {
return new OAuth2RestTemplate(authServerDetails(), myOAuth2ClientContext);
}
}
Hope this will be helpful.
You could register a DefaultTokenServices bean and configure it:
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setAccessTokenValiditySeconds(3600); // valid for one hour
return defaultTokenServices;
}

SpringSecurity WithSecurityContext MockMvc OAuth2 always unauthorised

I have followed the following links to try and test OAuth2 #PreAuthorise(hasAnyRole('ADMIN', 'TEST') for example but I can't any of the tests to pass or even authenticate.
When I try to access the end point with admin (or any role) it will never authenticate properly. Am I missing something obvious, it seems I have everything just as it is in the examples. I have also tried another alternative to the WithSecurityContext Factory with OAuth Specific Authentication and still no luck. Any help would be appreciated.
https://stackoverflow.com/a/31679649/2594130
and
http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test
My Controller I'm testing
#RestController
#RequestMapping("/bookmark/")
public class GroupBookmarkController {
#Autowired
BookmarkService bookmarkService;
/**
* Get list of all bookmarks
*/
#RequestMapping(value = "{groupId}", method = RequestMethod.GET)
#PreAuthorize("hasAnyRole(['ADMIN', 'USER'])")
public ResponseEntity<List<Bookmark>> listAllGroupBookmarks(#PathVariable("groupId") String groupId) throws BookmarkNotFoundException {
List<Bookmark> bookmarks = bookmarkService.findAllBookmarksByGroupId(groupId);
return new ResponseEntity<>(bookmarks, HttpStatus.OK);
}
...
}
My Test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = BookmarkServiceApplication.class)
#WebAppConfiguration
public class BookmarkServiceApplicationTests {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void loadData() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
#Test
#WithMockCustomUser(username = "test")
public void getBookmarkAuthorised() throws Exception {
mockMvc.perform(get("/bookmark/nvjdbngkjlsdfngkjlfdsnlkgsd"))
.andExpect(status().is(HttpStatus.SC_OK));
// always 401 here
}
}
My BookmarkServiceApplication
#SpringBootApplication
#EnableResourceServer
public class BookmarkServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookmarkServiceApplication.class, args);
}
}
My WithSecurityContextFactory
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
#Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
UserDetails principal = new User(customUser.username(), "password", true, true, true, true, grantedAuthorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(
principal, principal.getPassword(), principal.getAuthorities());
context.setAuthentication(authentication);
return context;
}
}
My WithSecurityContext Annotation
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUser {
String username() default "user";
String name() default "Test User";
}
As per #RobWinch 's reply
Hi #RobWinch I've tried you suggestion with the stateless flag, this helped with part of the answer. However in your reply to this question [Spring OAuth and Boot Integration Test] (https://stackoverflow.com/a/31679649/2594130) you mention
You no longer need to worry about running in stateless mode or not
Why is it that I need to still add the stateless false, is this a bug or are we using it slightly differently?
The other thing I needed to do to get this to work was adding OAuth2Request and OAuth2Authentication to the WithSecurityContextFactory as you can see in the following
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuthUser> {
#Override
public SecurityContext createSecurityContext(WithMockOAuthUser withClient) {
// Get the username
String username = withClient.username();
if (username == null) {
throw new IllegalArgumentException("Username cannot be null");
}
// Get the user roles
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : withClient.roles()) {
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("roles cannot start with ROLE_ Got " + role);
}
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
// Get the client id
String clientId = withClient.clientId();
// get the oauth scopes
String[] scopes = withClient.scope();
Set<String> scopeCollection = Sets.newSet(scopes);
// Create the UsernamePasswordAuthenticationToken
User principal = new User(username, withClient.password(), true, true, true, true, authorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(),
principal.getAuthorities());
// Create the authorization request and OAuth2Authentication object
OAuth2Request authRequest = new OAuth2Request(null, clientId, null, true, scopeCollection, null, null, null,
null);
OAuth2Authentication oAuth = new OAuth2Authentication(authRequest, authentication);
// Add the OAuth2Authentication object to the security context
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(oAuth);
return context;
}
}
The problem is that OAuth2AuthenticationProcessingFilter will clear the SecurityContext if it is marked as stateless. To workaround this configure it to allow the state to be populated externally (i.e. stateless = false).
to add some more infos how to set stateless to false:
in your ResourceServerConfigurerAdapter do the following:
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(false);
}
which worked for me.

Resources