Spring OAuth2RestTemplate for accessing resource server gives 401 Unauthorized - spring-security

I m trying to have an OAuth2Client using authorization_code grant type, I can authorize the user and redirect the url, but when I try to access the resource using OAuth2RestTemplate, I get 401 UnAuthorized
Is there something I need to do for the OAuth2RestTemplate to add the Authorization header ? I thought Spring-oauth2 will take care of adding the headers to OAuthRestTemplate by itself
Verified with TRACE logging as well
#GetMapping("/")
public OAuth2User hello(#AuthenticationPrincipal OAuth2User oAuth2User){
logger.info("User="+oAuth2User.getAttributes().get("unique_name"));
String response = oAuth2RestTemplate.getForObject("https://localhost:8090/me", String.class);
return oAuth2User;
}
#Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext) {
return new OAuth2RestTemplate(azureDetails(),oauth2ClientContext);
}
#Bean
public AuthorizationCodeResourceDetails azureDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId("myclientId");
details.setClientSecret("myclientsecret");
details.setAccessTokenUri("https://login.microsoftonline.com/common/oauth2/token");
details.setUserAuthorizationUri("https://login.microsoftonline.com/common/oauth2/authorize");
details.setScope(Arrays.asList("openid","profile","User.Read","Calendars.Read","Chat.Read","Files.Read","Mail.Read","Notes.Read","Tasks.Read"));
return details;
}
OAuth2RestTemplate should do a GET on MS Graph API and get the response

You need to update your AccessTokenUri and UserAuthorizationUri, your AccessTokenUri should be https://login.microsoftonline.com/common/oauth2/v2.0/tokenand your UserAuthorizationUri should be https://login.microsoftonline.com/common/oauth2/v2.0/authorize. For more details, please refer to https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow.

Related

How to set modify the access token request entity for Client Credentials grant when using Spring Security OAuth2 framrwork

I'm writing client for a 3rd party service that doesn't have the standard request format for getting an access token. The access token request body is a JSON with two attributes and the client_id and client_secret needs to be sent as a basic auth header. How do I build the custom request entity and headers converter to appropriately set these values in the access token request?
I have the client configuration with the client manager and responseclient.
public class RestClientConfig {
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient){
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(configurer -> configurer.accessTokenResponseClient(accessTokenResponseClient))
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
#Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient(){
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.setParametersConverter(null); --> this is where I'm stuck. Need to build a request entity converter bean to pass to this method
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return accessTokenResponseClient;
}
}

OAuth2 v2.0 authorize - error AADSTS90009 Application is requesting a token for itself

Requirement:
Use OAuth2 Implicit Grant flow do authorization in swagger ui page.
Swagger configuration code:
#Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("*"))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext()));
}
private SecurityScheme securitySchema() {
String scopeForAppIdUri = "http://xxxxxxx/.default";
AuthorizationScope authorizationScopeForAppIdUri = new AuthorizationScope(scopeForAppIdUri, "scope to restrict access to data protected by APIs");
List<AuthorizationScope> authorizationScopes = Collections.singletonList(authorizationScopeForAppIdUri);
LoginEndpoint loginEndpoint = new LoginEndpoint("https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize");
List<GrantType> grantTypes = Collections.singletonList(new ImplicitGrant(loginEndpoint, "Authorization"));
return new OAuth("oauth2", authorizationScopes, grantTypes, null);
}
private SecurityContext securityContext() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
List<SecurityReference> securityReferences = Collections.singletonList(
new SecurityReference("oauth2", new AuthorizationScope[]{authorizationScope}));
return SecurityContext.builder().securityReferences(securityReferences).build();
}
Sent request:
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize
?response_type=token
&client_id={client id GUID}
&redirect_uri=http://localhost:8080/webjars/springfox-swagger-ui/oauth2-redirect.html
&scope=https://xxxxxxx/.default
&state=xxxxxxxxx
Error:
AADSTS90009: Application is requesting a token for itself. This scenario is supported only if resource is specified using the GUID based App identifier
Question:
There's a similar question about this error: OAuth 2.0 and Azure Active Directory - error AADSTS90009, which suggests to set 'resource' parameter to be client id for v1.0, so for v2.0, it can clear the error by setting 'scope' value to be client id.
But for my case, Our endpoints will validate 'aud' value in token that should equals to the application id uri 'http://xxxxxxx'(this feature cannot change), which means i cannot just set 'scope' to be '{client-id}/.default', or else even though it can return token successfully but the token cannot pass the validation of endpoint.

Spring Security oauth2 client - How does one obtain a JWT token

I am trying to use spring-security-oauth2-client and spring-security-oauth2-jose to authenticate against Azure AD and get JWT tokens.
The login part works but the token that I receive is not a JWT. Here's my configuration :
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.loginPage("/oauth2/authorization/azure")
.userInfoEndpoint()
.oidcUserService(oidcUserService);
}
}
After authentication, I retrieve the token from the security context as follows :
OAuth2AuthenticationToken authentication = (OAuth2AuthenticationToken)
SecurityContextHolder.getContext().getAuthentication();
OAuth2AccessToken accessToken = authorizedClientService.loadAuthorizedClient(
authentication.getAuthorizedClientRegistrationId()
,authentication.getName()
).getAccessToken();
I get a Bearer token that looks like:
"AQABAAAAAADXzZ3ifr-GRbDT45zNSEFElTInSJQ19I2zONWkrBPgoKf8MCYL_z_IzU2lmF_ZadgBMdCr337faL0bpqHAzmFhsxq8peWUX7iYeTLbmcHDIdCR617VSKKHISLn_AiXhNr9rF6AMSrQTzdV2mKhEVlycTXlHUsZkA-gMA4z4FQFQMYkFNcLKqr7b-NewnV07lbG55joRIkcCMDrM1s4X8mRcJpRF6ek1yNSpveFmlbkrt3cXPUqtDe5EWI_5gfuGEVIon57LFLos_JtcQWSL6CTrUlY8EuF8MVuwJpTNG3OR80ikK7ycH_dXFCYmYDRrtTbFkf3R61aDSnqEUe2IIl2T8QdqWqH65ykSVooG6uIi5KsRK9zXPRuRuC_XC5w6SCcGionQYIgSEp-kCtIzlfHIBRK2o_CpjYVMBdmbfIkCvFoTGGGAvpOP1_MkgVeBiQzYFg8m_dn_roXFF17oBhCdYrZ2Y41_-GngLU3VJj4ltFIxzRziH6CZ2aFl1N3MwzIUcTiN6Ci0oyODTsSNDPc2zvxg609SjEqrO-6Xp0LMEwiOgY5L5rrcLA5d4LN-Xq9NiG0KqybZPU7wW0AHNA2Nw7bSg1Cle0ReaBU4ANbkjHxYeQJf65-ONNMGdfkV8xlKtRXZoiOBFip87Z72cS4NjLjM3x9_Qk9MQ5eGQTNj4fHCzJp9ukcjQ1MSUol_VIgAA
"
Which is then rejected by the Microsoft Graph API. Any help or suggestion is greatly appreciated.
You can also get the id token from the Authentication object. You need to cast the authentication.principal to OidcUser. The OidcUser gives you complete details of the user.
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
if (authentication.getPrincipal() instanceof OidcUser) {
OidcUser principal = ((OidcUser) authentication.getPrincipal());
idToken = principal.getIdToken().getTokenValue();
}
I was able to find a solution. What you had was the authorization code returned. To get the access token, use the following:
public void getToken(OAuth2AuthenticationToken oAuth2AuthenticationToken, #AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {
System.out.println(idToken);
}
Based on #govind's answer, this is the way to get an OIDC token in modern functional Java:
public Optional<String> getCurrentToken() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.filter(OidcUser.class::isInstance)
.map(OidcUser.class::cast)
.map(OidcUser::getIdToken)
.map(OidcIdToken::getTokenValue);
}

null principal returned by ServerRequest in webflux request handler

I have set up authentication in a Spring WebFlux application. The authentication mechanism appears to work fine. For example the following code is used to set up security web filter chain:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/path/to/resource").hasAuthority("A_ROLE")
.anyExchange().authenticated()
.and().httpBasic()
.and().build();
}
This works as expected in conjunction with the UserDetailsRepositoryReactiveAuthenticationManager and MapReactiveUserDetailsService. If a user doesn't have the required authority a forbidden error code is returned and otherwise the request is passed on to the handler.
I have a requirement to apply fine grained permission checks within the handler itself and figured that I should be able to retrieve the authorities from the request as follows:
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
Authentication authentication = (Authentication)serverRequest.principal().block();
...
}
However, I find that the principal is always null. First, is this the correct way to get a handle on the authorities, and if so is there possibly some upstream configuration I'm missing?
You are blocking the result before is available. You can simply flatmap it so that you don't have to block it.
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
return serverRequest.principal().flatMap((principal) -> ServerResponse.ok()
.body(fromObject("Hello " + principal.getName())));
}
UPDATE: If you want to retrieve the principal and body you could zip them.
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
return Mono.zip(
serverRequest.principal(),
serverRequest.bodyToMono(String.class)
).flatMap(tuple -> {
Principal principal = tuple.getT1();
String body = tuple.getT2();
return ServerResponse.ok().build();
});
}

Spring Security OAuth2 AngularJS | Logout Flow

Referring to the logout flow in oauth2 spring-guides project, once the the user has authenticated using user/password for the first time, the credentials are not asked next time after logout.
How can I ensure that username/password are asked every time after a logout.
This is what I am trying to implement:-
OAuth2 server issuing JWT token using "authorization_code" grant type
with auto approval. This has html/angularjs form to collect
username/password.
UI/Webfront - Uses #EnableSSO. ALL its endpoints are authenticated
i.e it does not have any unauthorized landing page/ui/link that user
clicks to go to /uaa server. So hitting http://localhost:8080
instantly redirects you to http://localhost:9999/uaa and presents
custom form to collect username/password.
Resource server - Uses #EnableResourceServer. Plain & simple REST api.
With the above approach I am not able to workout the logout flow. HTTP POST /logout to the UI application clears the session/auth in UI application but the users gets logged in again automatically ( as I have opted for auto approval for all scopes) without being asked for username password again.
Looking at logs and networks calls, it looks like that all the "oauth dance" happens all over again successfully without user being asked for username/password again and seems like the auth server remembers last auth token issued for a client ( using org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices? ).
How can I tell auth server to ask for username/password every time it is requested for code/token - stateless.
Or what is the best way to implement logout in my given scenario.
( To recreate somewhat near to my requirements, remove permitAll() part from the UiApplication and configure autoApproval in auth server of the mentioned boot project.)
github issue
I also faced the error as you described and I saw a solution from question
Spring Boot OAuth2 Single Sign Off. I don't mean this is the only and global truth solution.
But in the scenario,
authentication server has login form and you'd authenticated from it
browser still maintain the session with authentication server
after you have finished logout process (revoke tokens,remove cookies...)
and try to re-login again
authentication server do not send login form and automatically sign in
You need to remove authentication informations from authentication server's session as this answer described.
Below snippets are how did I configure for solution
Client (UI Application in your case) application's WebSecurityConfig
...
#Value("${auth-server}/ssoLogout")
private String logoutUrl;
#Autowired
private CustomLogoutHandler logoutHandler;
...
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl(logoutUrl)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.addLogoutHandler(logoutHandler)
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
// #formatter:on
}
Custom logout handler for client application
#Component
public class CustomLogoutHandler implements LogoutHandler {
private static Logger logger = Logger.getLogger(CustomLogoutHandler.class);
#Value("${auth-server}/invalidateTokens")
private String logoutUrl;
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
logger.debug("Excution CustomLogoutHandler for " + authentication.getName());
Object details = authentication.getDetails();
if (details.getClass().isAssignableFrom(OAuth2AuthenticationDetails.class)) {
String accessToken = ((OAuth2AuthenticationDetails) details).getTokenValue();
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("access_token", accessToken);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "bearer " + accessToken);
HttpEntity<Object> entity = new HttpEntity<>(params, headers);
HttpMessageConverter<?> formHttpMessageConverter = new FormHttpMessageConverter();
HttpMessageConverter<?> stringHttpMessageConverternew = new StringHttpMessageConverter();
restTemplate.setMessageConverters(Arrays.asList(new HttpMessageConverter[] { formHttpMessageConverter, stringHttpMessageConverternew }));
try {
ResponseEntity<String> serverResponse = restTemplate.exchange(logoutUrl, HttpMethod.POST, entity, String.class);
logger.debug("Server Response : ==> " + serverResponse);
} catch (HttpClientErrorException e) {
logger.error("HttpClientErrorException invalidating token with SSO authorization server. response.status code: " + e.getStatusCode() + ", server URL: " + logoutUrl);
}
}
authentication.setAuthenticated(false);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
new SecurityContextLogoutHandler().logout(request, response, auth);
}
}
I used JDBC tokenStore, so I need to revoke tokens.At the authentication server side, I added a controller to handle logout processes
#Controller
public class AuthenticationController {
private static Logger logger = Logger.getLogger(AuthenticationController.class);
#Resource(name = "tokenStore")
private TokenStore tokenStore;
#Resource(name = "approvalStore")
private ApprovalStore approvalStore;
#RequestMapping(value = "/invalidateTokens", method = RequestMethod.POST)
public #ResponseBody Map<String, String> revokeAccessToken(HttpServletRequest request, HttpServletResponse response, #RequestParam(name = "access_token") String accessToken, Authentication authentication) {
if (authentication instanceof OAuth2Authentication) {
logger.info("Revoking Approvals ==> " + accessToken);
OAuth2Authentication auth = (OAuth2Authentication) authentication;
String clientId = auth.getOAuth2Request().getClientId();
Authentication user = auth.getUserAuthentication();
if (user != null) {
Collection<Approval> approvals = new ArrayList<Approval>();
for (String scope : auth.getOAuth2Request().getScope()) {
approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED));
}
approvalStore.revokeApprovals(approvals);
}
}
logger.info("Invalidating access token :- " + accessToken);
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
if (oAuth2AccessToken != null) {
if (tokenStore instanceof JdbcTokenStore) {
logger.info("Invalidating Refresh Token :- " + oAuth2AccessToken.getRefreshToken().getValue());
((JdbcTokenStore) tokenStore).removeRefreshToken(oAuth2AccessToken.getRefreshToken());
tokenStore.removeAccessToken(oAuth2AccessToken);
}
}
Map<String, String> ret = new HashMap<>();
ret.put("removed_access_token", accessToken);
return ret;
}
#GetMapping("/ssoLogout")
public void exit(HttpServletRequest request, HttpServletResponse response) throws IOException {
new SecurityContextLogoutHandler().logout(request, null, null);
// my authorization server's login form can save with remember-me cookie
Cookie cookie = new Cookie("my_rememberme_cookie", null);
cookie.setMaxAge(0);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);
response.sendRedirect(request.getHeader("referer"));
}
}
At authorization server's SecurityConfig, you may need to allow this url as
http
.requestMatchers()
.antMatchers(
"/login"
,"/ssoLogout"
,"/oauth/authorize"
,"/oauth/confirm_access");
I hope this may help a little for you.
As you are using JWT tokens, you can not really revoke them.
As a workaround, you can have a logout rest endpoint that would store the timestamp and userid for logout call.
Later, you can compare the logout time with JWT token issue time, and decide wether to allow an api call or not.
I have realized that redirecting to a controller when you logout from your client app and then programmatically logout on your authserver does the trick. This is my configuration on the client app:
#Configuration
#EnableOAuth2Sso
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${auth-server}/exit")
private String logoutUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutSuccessUrl(logoutUrl)
.and().authorizeRequests().anyRequest().authenticated();
}
}
and this is my configuration on my authserver (is just a controller handling the /exit endpoint):
#Controller
public class LogoutController {
public LogoutController() {
}
#RequestMapping({"/exit"})
public void exit(HttpServletRequest request, HttpServletResponse response) {
(new SecurityContextLogoutHandler()).logout(request, null, null);
try {
response.sendRedirect(request.getHeader("referer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here is a sample app that shows the full implementation using JWT. Check it out and let us know if it helps you.

Resources