I am using spring cloud gateway for create microservice gateway. The gateway is configured with OAuth2 protocol. The application getting authenticated successfully. How to get access Token from gateway of the authenticated user?
I have implemented GlobalFilter to fetch the Principal. Following is the implementation:
return exchange.getPrincipal()
.defaultIfEmpty(DEFAULT_PRINCIPAL).map(principal -> {
// adds header to proxied request
ServerHttpRequest.Builder b = exchange.getRequest().mutate();
OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) principal;
OAuth2User p = (OAuth2User) auth.getPrincipal();
String userId = (String) p.getAttributes()
.getOrDefault("sub", new String());
String firstName = (String) p.getAttributes()
.getOrDefault("given_name", new String());
String lastName = (String) p.getAttributes()
.getOrDefault("family_name", new String());
String name = (String) p.getAttributes()
.getOrDefault("preferred_username",
new String());
String email = (String) p.getAttributes()
.getOrDefault("email",
new String());
b.build();
return exchange;
}).flatMap(chain::filter);
private static Principal DEFAULT_PRINCIPAL = () -> "<EMPTY>";
And following is the SecurityConfig
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
var authenticateExchangeOrUrl = http.authorizeExchange().pathMatchers(s.trim()).authenticated().and();
securityFilterChain = authenticateExchangeOrUrl.oauth2Client().and().oauth2Login().and();
return securityFilterChain
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.build();
}
SecurityConfig is annotated with #EnableWebFluxSecurity. In application.yaml spring.main.web-application-type: reactive is added.
How to get access Token of the authenticated user?
Related
I have an API that uses OKTA for authentication. I need the opaque token so that I can access the OKTA APIs on behalf of the user. How can I have access to the opaque token in my controller?
I found this.
I created this ExchangeFilterFunction:
private ExchangeFilterFunction myExchangeFilterFunction(OAuth2AuthorizedClientService clientService) {
return new ExchangeFilterFunction() {
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String accessToken = client.getAccessToken().getTokenValue();
ClientRequest newRequest = ClientRequest.from(request)
.headers((headers) -> headers.setBearerAuth(accessToken))
.build();
return next.exchange(newRequest);
}
};
}
I am loading oidcUser from OidcUserRequest in my Oauth2UserService implementation class.
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = delegate.loadUser(userRequest);
List<GrantedAuthority> rolesAsAuthorities = getRolesAsAuthorities(user);
CustomOidcUserDetailsImpl customUser = new CustomOidcUserDetailsImpl(user, rolesAsAuthorities);
customUser.setFullName(getFullName(user));
customUser.setTelephone(getTelephone(user));
customUser.setEmail(getEmail(user));
return customUser;
}
The problem is that i just can get OauthAccessToken and IdToken from OidcUserRequest. Are there any ways of getting Oauth2RefreshToken in my service?
I get id,access,refresh tokens if i exchange authorization code for tokens manually.
#Autowired
private OAuth2AuthorizedClientService authorizedClientService;
Authentication authentication =SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthorizedClient client = authorizedClientService
.loadAuthorizedClient(
"wso2", // client registrationId
authentication.getName());
Oauth2RefreshToken refreshToken = client.getRefreshtoken();
I have a Spring OAuth 2 server based on Spring Boot 1.5 (Spring Security v4) which generates customized tokens and a few resource servers who communicate with this authorization server, making use of /oauth/check_token endpoint by configuration of RemoteTokenServices.
All the logic related to storing/retrieving tokens on Authorization server side is done with JdbcTokenStore.
I am building a new Spring Boot 2 application which is build with Spring webflux module and trying to implement client_credentials flow with existing Authorization Server using Spring Security 5.1.1.
I found that support for resource servers was added in 5.1.0.RC1 (https://spring.io/blog/2018/08/21/spring-security-5-1-0-rc1-released#oauth2-resource-servers) and updated in 5.1.0.RC2 (https://spring.io/blog/2018/09/10/spring-security-5-1-0-rc2-released#oauth2-resource-server) but looks like it's only possible to configure it with JWT support.
I might be messing up with concepts here but looking for more info and a way to configure all these components together.
I'm in same situation as you.I solve that problem in next way, maybe it can help you:
spring-boot-starter-parent.version: 2.1.1
spring-cloud-dependencies.version: Greenwich.R1
Security configuration:
#EnableWebFluxSecurity
public class SecurityConfig {
#Autowired
private ReactiveAuthenticationManager manager; //custom implementation
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/role").hasRole("ADMIN")
.pathMatchers("/test").access(new HasScope("server")) //custom implementation
.anyExchange().authenticated()
.and()
.httpBasic().disable()
.oauth2ResourceServer()
.jwt()
.authenticationManager(manager)
.and().and()
.build();
}
}
ReactiveAuthorizationManager (HasScope) implementation:
Helper which allow search for scopes in authentication object
public class HasScope implements ReactiveAuthorizationManager<AuthorizationContext> {
public HasScope(String...scopes) {
this.scopes = Arrays.asList(scopes);
}
private final Collection<String> scopes;
#Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
return authentication
.flatMap(it -> {
OAuth2Authentication auth = (OAuth2Authentication) it;
Set<String> requestScopes = auth.getOAuth2Request().getScope();
boolean allow = requestScopes.containsAll(scopes);
return Mono.just(new AuthorizationDecision(allow));
});
}
}
ReactiveAuthenticationManager implementation:
That is the main component in configuration which create OAuth2Authentication. There is a problem with response for wrong access_token, it returns only status code without body response.
#Component
public class ReactiveAuthenticationManagerImpl implements ReactiveAuthenticationManager {
private final ResourceServerProperties sso;
private final WebClient.Builder webClient;
private final ObjectMapper objectMapper;
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
public ReactiveAuthenticationManagerImpl(ResourceServerProperties sso,
#Qualifier("loadBalancedWebClient") WebClient.Builder webClient, ObjectMapper objectMapper) {
this.sso = sso;
this.webClient = webClient;
this.objectMapper = objectMapper;
}
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.just(authentication)
.cast(BearerTokenAuthenticationToken.class)
.flatMap(it -> getMap(it.getToken()))
.flatMap(result -> Mono.just(extractAuthentication(result)));
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
OAuth2Request request = getRequest(map);
List<GrantedAuthority> authorities = authoritiesExtractor.extractAuthorities(map);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
private Object getPrincipal(Map<String, Object> map) {
if (map.containsKey("principal")) {
try {
//that is the case for user authentication
return objectMapper.convertValue(map.get("principal"), UserPrincipal.class);
} catch (IllegalArgumentException ex) {
//that is the case for client authentication
return objectMapper.convertValue(map.get("principal"), String.class);
}
}
return null;
}
#SuppressWarnings({"unchecked"})
private OAuth2Request getRequest(Map<String, Object> map) {
Map<String, Object> request = (Map<String, Object>) map.get("oauth2Request");
String clientId = (String) request.get("clientId");
Set<String> scope = new LinkedHashSet<>(request.containsKey("scope") ?
(Collection<String>) request.get("scope") : Collections.emptySet());
return new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),
null, null, null, null);
}
private Mono<Map<String, Object>> getMap(String accessToken) {
String uri = sso.getUserInfoUri();
return webClient.build().get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + accessToken)
.exchange()
.flatMap(it -> it.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}))
.onErrorMap(InvalidTokenException.class, mapper -> new InvalidTokenException("Invalid token: " + accessToken));
}
I have created a very basic Spring Cloud Zuul application to act as a token relay to a resource server. This is extremely simple to do for authorization_code grant. The below does exactly what I need:
package zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
#SpringBootApplication
#EnableOAuth2Sso
#EnableZuulProxy
public class RelayProxy {
public static void main(String[] args) {
SpringApplication.run(RelayProxy.class, args);
}
}
And in the application.yml (my authorization server runs on port 9999):
server:
port: 8000
security:
oauth2:
client:
accessTokenUri: http://localhost:9999/oauth/token
userAuthorizationUri: http://localhost:9999/oauth/authorize
clientAuthenticationScheme: form
clientId: client
clientSecret: secret
resource:
jwt:
key-uri: http://localhost:9999/oauth/token_key
zuul:
routes:
resource:
path: /resource/**
url: http://localhost:8002/
To allow a legacy system that only supports Basic Authentication to get access to the resource server, I would like a request with a basic authentication header to be intercepted and a password grant performed to obtain and relay the access token. I have tried to configure this using Spring Security / OAuth2 configuration, but was unable to get it to work. The zuul filter below is what I am currently using and it works fine, but I would like to know if it is possible to achieve the same result through the correct Spring configuration:
public class PasswordGrantZuulFilter extends ZuulFilter {
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Autowired
private OAuth2ClientContext clientContext;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String header = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
return header != null && header.toLowerCase().startsWith("basic");
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (clientContext.getAccessToken() == null) {
String header = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
String base64Credentials = header.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials), Charset.forName("UTF-8"));
final String[] values = credentials.split(":", 2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList("Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes())));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("username", values[0]);
map.add("password", values[1]);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<OAuth2AccessToken> response = new RestTemplate().postForEntity(accessTokenUri, request, OAuth2AccessToken.class);
clientContext.setAccessToken(response.getBody());
}
ctx.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, OAuth2AccessToken.BEARER_TYPE + " " + clientContext.getAccessToken().getValue());
return null;
}
}
I have been struggling while trying to create a sample client that uses the OAuth 2.0 Authorization Code Grant flow.
I am able to successfully use the Client Credentials flow but when I try to use the Authorization Code flow I do not get redirected to the correct uri.
When calling the OAuth2RestTmplate.exchange method, I get a redirect exception in the RestTemplate.doExecute(...) method. It is thrown from the finally clause.
The response is null, but the if is not stopping it.
finally {
if (response != null) {
response.close();
}
I still get prompted for login and authorization, but am not directed to the response containing the data. I am just redirected back to the client home page. The same call from Postman using the authorization code flow with the same client credentials is successful so I know the client registration is correct.
I could use another pair of eyes to see what I'm missing. Thanks in Advance! Below are my code excerpts.
Working client using oauth2 client credentials flow:
Client App:
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
public class ClientExampleClientCredentials extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ClientExampleClientCredentials.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ClientExampleClientCredentials.class);
}
}
Controller:
#RestController
public class HomeController {
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Value("${security.oauth2.client.apiUrl}")
private String apiUrl;
#Value("${security.oauth2.client.scope}")
private List<String> scopes;
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
/**
* Example of using the OAuth2RestTemplate to access external resources
*
* The OAuth2RestTemplate takes care of exchanging credentials with the auth server, as well as adding the
* bearer token to each request to the FHIR services.
*
*/
#RequestMapping("/ex-1")
public String retrievePatientByIdUsingRestTemplate(#RequestParam String id) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(getClientCredentialsResourceDetails(), new DefaultOAuth2ClientContext());
ResponseEntity<String> response = oAuth2RestTemplate.exchange(apiUrl + "/Patient/" + id, HttpMethod.GET, null, String.class);
String responseBody = response.getBody();
return responseBody;
}
private ClientCredentialsResourceDetails getClientCredentialsResourceDetails() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setAccessTokenUri(accessTokenUri);
clientCredentialsResourceDetails.setAuthenticationScheme(AuthenticationScheme.header);
clientCredentialsResourceDetails.setClientId(clientId);
clientCredentialsResourceDetails.setClientSecret(clientSecret);
clientCredentialsResourceDetails.setScope(scopes);
return clientCredentialsResourceDetails;
}
}
application.yml
security:
oauth2:
client:
clientId: client_id
clientSecret: secret
apiUrl: http://localhost:8080/testData/data
accessTokenUri: http://localhost:8080/test-auth/token
scope: system/*.read
This works great authenticating me and then redirecting to my service url. However, The Authorization Code flow is not working.
Broken client using oauth2 authorization code flow:
Client App:
#SpringBootApplication (exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
public class ClientExampleAccessToken extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ClientExampleAccessToken.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ClientExampleAccessToken.class);
}
}
Controller:
package org.ihc.clinical.controller;
#Configuration
#EnableOAuth2Client
#RestController
public class HomeController {
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
#Value(("${security.oauth2.client.userAuthorizationUri}"))
private String userAuthorizationUri;
#Value("${security.oauth2.client.apiUrl}")
private String apiUrl;
#Value("${security.oauth2.client.redirectUri}")
private String redirectUri;
#RequestMapping("/ex-1")
public String retrievePatientByIdUsingRestTemplate(#RequestParam String empi) {
OAuth2ProtectedResourceDetails resource = resource();
String path = apiUrl + "/Patient/" + empi;
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext());
***/*error occurs here in RestTemplate.doExcute. error:org.springframework.security.oauth2.client.resource.UserRedirectRequiredException:
A redirect is required to get the users approval */***
ResponseEntity<String> response = oAuth2RestTemplate.exchange(path, HttpMethod.GET, null, String.class);
//AuthorizationCodeAccessTokenProvider provider = new //AuthorizationCodeAccessTokenProvider();
//Token Request
//AccessTokenRequest request = new DefaultAccessTokenRequest();
//String code = provider.obtainAuthorizationCode(resource, request);
//request.setAuthorizationCode(code);
//OAuth2AccessToken oAuth2AccessToken = //provider.obtainAccessToken(resource, request);
//Token Response
//String tokenValue = oAuth2AccessToken.getValue();
//return tokenValue;
}
//Call when ready to send token Request
private OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails authorizationCodeResourceDetails = new AuthorizationCodeResourceDetails();
authorizationCodeResourceDetails.setClientId(clientId);
authorizationCodeResourceDetails.setClientSecret(clientSecret);
authorizationCodeResourceDetails.setAccessTokenUri(accessTokenUri);
authorizationCodeResourceDetails.setUserAuthorizationUri(userAuthorizationUri);
//authorizationCodeResourceDetails.setScope(scopes);
authorizationCodeResourceDetails.setPreEstablishedRedirectUri(redirectUri);
return authorizationCodeResourceDetails;
}
}
application.yml
security:
oauth2:
client:
clientId: clientid
clientSecret: secret
accessTokenUri: http://localhost:8080/test-auth/token
userAuthorizationUri: http://localhost:8080/test-auth/authorize
apiUrl: http://localhost:8080/test-fhir-cdr/data
redirectUri: http://localhost:8080/test-examples-access-token
I finally found the solution here: https://projects.spring.io/spring-security-oauth/docs/oauth2.html
I needed to add the below code to the controller:
#Autowired
private OAuth2ClientContext oauth2Context;
#Bean
public OAuth2RestTemplate getOauth2RestTemplate() {
return new OAuth2RestTemplate(resource(), oauth2Context);
}
Then call getOauth2RestTemplate() like below:
#RequestMapping("/ex-1")
public String retrievePatientByIdUsingRestTemplate(#RequestParam String empi) {
String path = apiUrl + "/Patient/" + empi;
OAuth2RestTemplate oAuth2RestTemplate = getOauth2RestTemplate();
ResponseEntity<String> response = oAuth2RestTemplate.exchange(path, HttpMethod.GET, null, String.class);
return response.getBody();
}