I have a controller that is autowired with many services. These services are HTTP restful calls that retrieve data from various data sources, but these services are protected with OAuth2.0.
I am trying to use Spring Security to implement a client-credentials flow that will allow these services to securely retrieve data from these protected data sources, but am having some difficulty in resolving the OAuth2AuthorizedClient data object at the service layer.
I've been trying to resolve the authorized client via the #RegisteredOAuth2AuthorizedClient annotation:
public void setAuthorizedClient(
#RegisteredOAuth2AuthorizedClient("azure") OAuth2AuthorizedClient authorizedClient) {
ClientRegistration clientRegistration =
this.clientRegistrationRepository.findByRegistrationId("azure");
System.out.println(clientRegistration);
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
jwtToken = accessToken.getTokenValue();
}
Is it possible to resolve the OAuth2AuthorizedClient as a Spring bean that can then be injected into another bean?
Or is there a better way of architecting such a system?
Thanks!
Bit of an old question but I just solved this for myself so here goes:
You can create a #Component that returns the OAuth2AuthorizedClient for you, and inject that where you need it. Here is an example approach:
Create a provider Component class
Inject the readily available OAuth2AuthorizedClientService bean to your class
Create a method that uses the service in order to return the OAuth2AuthorizedClient
Inject your provider class to your Controller
Example:
#Component
public class OAuth2AuthorizedClientProvider {
#Autowired
private OAuth2AuthorizedClientService clientService;
public OAuth2AuthorizedClient getClient() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
return clientService.loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName());
}
and then OAuth2AuthorizedClientProvider is used in a controller like so:
#RestController
public class Endpoint {
#Autowired
private final OAuth2AuthorizedClientProvider oauth2AuthorizedClientProvider;
#GetMapping("/mymethod")
public String mymethod() {
return oauth2AuthorizedClientProvider.getClient().getAccessToken();
}
}
Related
I am new to Spring Security and I want to implement a client for a OAUTH2 secured service that only accepts password grant.
Obtaining the access_token from the auth server is done using data in the http body like this:
client_id={{clientId}}&client_secret={{client_secret}}&grant_type=password&username={{username}}&password={{password}}
Afterwards the access_token must be used in the header field Authorization to access the actual service. (e.g. Authorization=Bearer <access_token>)
My goal is to use the provided features from Spring Security OAuth2 to request an access_token from the auth service, and use it for accessing the service endpoints until token expiration. I also like to have that my access_token is automatically refreshed using the refresh_token value from the auth server. I want to achieve this while fully utilizing Spring's features.
I found that I can use OAuth2RestTemplate with ResourceOwnerPasswordResourceDetails for the grant_type password.
The StackOverflow post oAuth2 client with password grant in Spring Security was very helpful for me, but I have not got it to work.
I also found the post Authentication is required to obtain an access token (anonymous not allowed) where a user encountered the same exception, but uses client_credentials and AuthorizationCodeResourceDetails.
At the moment my code looks like this.
#Service
public class MyClient {
#Autowired
private OAuth2RestTemplate restTemplate;
#Value("${authServer.accessTokenUri}")
private String accessTokenUri;
#Value("${authServer.clientId}")
private String clientId;
#Value("${authServer.clientSecret}")
private String clientSecret;
#Value("${authServer.username}")
private String username;
#Value("${authServer.password}")
private String password;
#Value("${serviceUrl}")
private String serviceUrl;
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), oauth2ClientContext);
template.setAccessTokenProvider(accessTokenProvider());
return template;
}
#Bean
public AccessTokenProvider accessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return new AccessTokenProviderChain(
Arrays.<AccessTokenProvider>asList(tokenProvider)
);
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(clientId);
resource.setAccessTokenUri(accessTokenUri);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType("password");
resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
resource.setUsername(username);
resource.setPassword(password);
return resource;
}
public void getDataFromService() {
String response = restTemplate.getForObject(serviceUrl, String.class);
}
}
An exception is thrown in AccessTokenProviderChain, because of this block.
if (auth instanceof AnonymousAuthenticationToken) {
if (!resource.isClientOnly()) {
throw new InsufficientAuthenticationException("Authentication is required to obtain an access token (anonymous not allowed)");
}
}
Here is the exception stack trace.
org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:91) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:731) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:311) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
As you can see I cannot request an access_token. I do not understand why I get this exception, because if I directly request an access_token from the auth server using the curl command, I am able to authenticate using only the provided data as stated.
I manually obtained an access_token successfully like this, when adding the following code before invoking restTemplate.getForObject(...).
ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken token = accessTokenProvider.obtainAccessToken(resource(), new DefaultAccessTokenRequest());
restTemplate.getOAuth2ClientContext().setAccessToken(token);
String token = restTemplate.getAccessToken();
But, manually obtaining the access_token is not that what I want. Is there something I am missing? Is it possible to automatically obtain an access_token and refresh it using Spring Security with password grant?
Although checking code multiple hours on Github, StackOverflow etc. ... I have not been able to get my code to work.
UPDATE:
I found that my ResourceOwnerPasswordResourceDetails instance inside my OAuth2RestTemplate instance is not initialized, when I want to make use of it inside getDataFromService(). (i.e. the fields like username are null). After clarification and help from #JoeGrandja, my question now does not really target Spring Security, but rather Spring.
What can I do to make use of the #Value annotations inside a #Bean annotated method. At the moment, when the restTemplate is constructed using the #Bean annotated method resource(), the values from the application.yml are obviously not available yet.
I found a solution with the help and support of #JoeGrandja. Thank you very much! :)
If anyone else has problems, here is my working solution. I also recommend reading the comments from #JoeGrandja above.
#Configuration
#ConfigurationProperties(prefix = "authserver")
public class AuthServerConfigProperties {
private String accessTokenUri;
private String clientId;
private String grantType;
private String clientSecret;
private String username;
private String password;
// Getter & Setter for all properties ...
}
#Configuration
public class CommConfig {
#Autowired
AuthServerConfigProperties configProperties;
#Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource(), oauth2ClientContext);
oAuth2RestTemplate.setAccessTokenProvider(new ResourceOwnerPasswordAccessTokenProvider());
return oAuth2RestTemplate;
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(configProperties.getClientId()); // not necessary
resource.setAccessTokenUri(configProperties.getAccessTokenUri());
resource.setClientId(configProperties.getClientId());
resource.setClientSecret(configProperties.getClientSecret());
resource.setGrantType(configProperties.getGrantType());
resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
resource.setUsername(configProperties.getUsername());
resource.setPassword(configProperties.getPassword());
return resource;
}
}
#RestController
public class MyController {
#Autowired
private OAuth2RestOperations restTemplate;
#Value("${serviceUrl}")
private String serviceUrl;
#RequestMapping(value = "/getData", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<String> getData() {
String response = restTemplate.getForObject(serviceUrl, String.class);
return new ResponseEntity(response, HttpStatus.OK);
}
}
I had a similar problem: rest request was anonymous, but internal processing required oauth2 authorization, resolved with a simple extend:
public class CustomResourceOwnerPasswordResourceDetails extends ResourceOwnerPasswordResourceDetails {
#Override
public boolean isClientOnly() {
return true;
}
}
Suppose I develop a ticket order web service. There are some steps to order a ticket and need to keep some users data between the steps.
Suppose I use Spring (Boot) technology stack and MVC
How is better to implement it?
Use stateless REST and move the date back and forth from step to step using cookies?
Store it in session context?
Use stateful beans (what are they like in Spring? Prototype? )
Use some stateful protocol, like SOAP (is it stateful?)
It depends.
1 If you want to use multiple instances of your web service (for balance load, etc) then your choice is a stateless REST and token-based authentication
2 If you don't need this functionality you can store your session information in MVC Model (It will put it in session, anyway)
#RestController
#SessionAttributes("armUserSession")
public class SessionController {
#Autowired
private LoginService loginService;
#ModelAttribute("armUserSession")
public ArmUserSession getArmUserSession() {
return new ArmUserSession();
}
#CrossOrigin
#RequestMapping({"/login"})
public ArmUserSession login(#ModelAttribute("armUserSession") ArmUserSession userSession,
Model model,
#RequestParam(required = false) String login,
#RequestParam(required = false) String password) {
if (!userSession.isLoggedIn()) {
userSession = loginService.login(login, password);
model.addAttribute("armUserSession", userSession);
}
return userSession;
}
#CrossOrigin
#RequestMapping({"/logout"})
public ArmUserSession logout(SessionStatus status) {
status.setComplete();
return new ArmUserSession();
}
}
3 You can use session scoped beans too, but it is a little more complicated.
By default Spring beans are singletons. When you want to use session scoped bean (they are not singletons) in singleton your need a proxy.
#Service
public class LoginServiceImpl implements LoginService {
#Autowired
private ArmUserSessionProxy armUserSessionProxy;
#Override
public ArmUserSession login(String login, String password) {
ArmUserSession armUserSession = armUserSessionProxy.getArmUserSession();
...................................
}
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ArmUserSessionProxy {
private ArmUserSession armUserSession = new ArmUserSession();
public ArmUserSession getArmUserSession() {
return armUserSession;
}
}
I want to consume a REST webservice from a server which protects his resources using oauth2.
I use Spring boot (JHipster).
To do this i have in SecurityConfiguration class this :
#Value("${oauth.resource:http://sercverUsingOAuth2}")
private String baseUrl;
#Value("${oauth.authorize:http://sercverUsingOAuth2/rest/oauth/token}")
private String authorizeUrl;
#Value("${oauth.token:http://sercverUsingOAuth2/rest/oauth/token}")
private String tokenUrl;
#Bean
public OAuth2RestOperations oauth2RestTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(resource(),
new DefaultOAuth2ClientContext(atr));
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(tokenUrl);
resource.setUserAuthorizationUri(authorizeUrl);
resource.setClientId("client_id");
resource.setClientSecret("client_secret");
resource.setGrantType("grant_type");
return resource;
}
This class (SecurityConfiguration) is annoted using :
#Configuration
#EnableWebSecurity
#EnableOAuth2Client
And this is my controller (Spring MVC) :
#RestController
#RequestMapping("/consume")
public class MyContrtoller {
#Inject
private OAuth2RestOperations oauth2RestTemplate;
#RequestMapping(value = "/oauth2", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<DataModel> getProducts() {
ResponseEntity<MyModel> forEntity = oauth2RestTemplate
.getForEntity("http://sercverUsingOAuth2/rest/resourceToConsume",
MyModel.class);
return forEntity.getBody().getData();
}
}
However when i want to consume my webservice (http://myHost/consume/oauth2) i get this Exception :
org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException:
Unable to obtain a new access token for resource 'null'. The provider manager
is not configured to support it.
I have googled and i found this :
github
stackoverflow
But it doesn't help me.
Thanks.
You are using the same URL for the authorization url and the token url. That was my first clue, then I saw your comments.
Even though you are changing the grant type, you are still using "AuthorizationCodeResourceDetails" when you should be using "ClientCredentialsResourceDetails" instead. This type of ResourceDetails is meant to be used for the case you are explaining.
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(TOKEN_URL);
resource.setClientId(CLIENT_ID);
resource.setClientSecret(CLIENT_SECRET);
resource.setClientAuthenticationScheme(AuthenticationScheme.form); //This line isn't always needed
return resource;
We want to setup a microservice which provides a REST API so it is configured as a OAuth2 resource server. This service should also act as a OAuth2 client with the client credential grant. Here is the configuration:
spring.oauth2.client.id=clientCredentialsResource
spring.oauth2.client.accessTokenUri=http://localhost:9003/oauth/token
spring.oauth2.client.userAuthorizationUri=http://localhost:9003/oauth/authorize
spring.oauth2.client.grantType=client_credentials
spring.oauth2.client.clientId=<service-id>
spring.oauth2.client.clientSecret=<service-pw>
The resource server part works fine. For the client part we want to use Feign, Ribbon and Eureka:
#FeignClient("user")
public interface UserClient
{
#RequestMapping( method = RequestMethod.GET, value = "/user/{uid}")
Map<String, String> getUser(#PathVariable("uid") String uid);
}
Based on the gist in issue https://github.com/spring-cloud/spring-cloud-security/issues/56 I created a feign request intercepter which sets the access token from the autowired OAuth2RestOperations template in the feign request header
#Autowired
private OAuth2RestOperations restTemplate;
template.header(headerName, String.format("%s %s", tokenTypeName, restTemplate.getAccessToken().toString()));
But this gives me the error on calling the user service:
error="access_denied", error_description="Unable to obtain a new access token for resource 'clientCredentialsResource'. The provider manager is not configured to support it.
As I can see the OAuth2ClientAutoConfiguration creates always an instance of AuthorizationCodeResourceDetails for an web application but not the required ClientCredentialsResourceDetails which is only used for non-web applications. In the end the no access token privider is responsible for the resource details and the call failed in
AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:146)
I tried to overwrite the auto configuration but failed. Can somebody please give me a hint how to do it?
To switch off this piece of autoconfiguration you can set spring.oauth2.client.clientId= (empty), (per the source code), otherwise you have to "exclude" it in the #EnableAutoConfiguration. If you do that you can just set up your own OAuth2RestTemplate and fill in the "real" client ID from your own configuration, e.g.
#Configuration
#EnableOAuth2Client
public class MyConfiguration {
#Value("myClientId")
String myClientId;
#Bean
#ConfigurationProperties("spring.oauth2.client")
#Primary
public ClientCredentialsResourceDetails oauth2RemoteResource() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setClientId(myClientId);
return details;
}
#Bean
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
}
#Bean
#Primary
public OAuth2RestTemplate oauth2RestTemplate(
OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
OAuth2RestTemplate template = new OAuth2RestTemplate(details,
oauth2ClientContext);
return template;
}
}
servletApi() support of Spring Security is great.
I want to inject custom Principal as this:
public interface UserPrincipal extends Principal {
public Integer getId();
}
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipal user){
// implementation
}
or
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipalImpl user){
// implementation
}
Spring has support for injecting Principal instances with the help of ServletRequestMethodArgumentResolver.
It is injecting principal as this:
else if (Principal.class.isAssignableFrom(paramType)) {
return request.getUserPrincipal();
}
Here is the place where the problem begins. request is here an instance of SecurityContextHolderAwareRequestWrapper. It has an implementation of:
#Override
public Principal getUserPrincipal() {
Authentication auth = getAuthentication();
if ((auth == null) || (auth.getPrincipal() == null)) {
return null;
}
return auth;
}
Because an Authentication is also an Principal. (The only part of spring security I did not like so far. I will ask this a separate question as well.)
This is causing a problem. Because Authentication is a Principal not a UserPrincipal.
How can I resolve this problem? Do I need to implement an authentication which is a UserPrincipal as well? Or should I change HandlerMethodArgumentResolver order a create a custom resolver? (This is not easy for Spring MVC because internal handlers has higher priority.)
As a extra information:
I am using Spring Security M2 and my configuration for AuthenticationManagerBuilder is simply:
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(detailsService);
}
Any help?
Fundamentally this seems like trouble integrating with Spring MVC and not a Spring Security issue. Spring Security has no way of knowing that Authentication#getPrinicpal() implements Principal since the API returns an Object.
I see a few options for you. Each has some pros and cons, but I think the best is using #ModelAttribute and #ControllerAdvice
#ModelAttribute and #ControllerAdvice
The easiest option is annotate a method with #ModelAttribute on custom #ControllerAdvice. You can find details in the Spring Reference.
#ControllerAdvice
public class SecurityControllerAdvice {
#ModelAttribute
public UserPrincipal customPrincipal(Authentication a) {
return (UserPrincipal) a == null ? null : a.getPrincipal();
}
}
Now in your controller you can do something like this:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(#ModelAttribute UserPrincipal user){
// implementation
}
Note that the #ModelAttribute is necessary only to ensure the #ModelAttribute is used over the HttpServletRequest#getPrincipal(). If it did not implement Principal, #ModelAttribute is not required.
#Value and ExpressionValueMethodArgumentResolver
You can also do something like this:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(
#Value("#{request.userPrincipal.principal}") UserPrincipal user){
// implementation
}
This works because the HttpServletRequest is available as an attribute to the ExpressionValueMethodArgumentResolver (added by default by Spring MVC) which allows accessing things via SpEL. I find this less attractive than #ModelAttribute due to the constant that must be in the #Value annotation. It will be nicer when SPR-10760 is resolved which would allow for your own custom annotation to be used like:
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Value("#{request.userPrincipal.principal}")
public #interface CurrentUser { }
#Autowire RequestMappingHandlerAdapter
This is a bit sloppy because the RequestMappingHandlerAdapter has already been initialized, but you can change the ordering of the HandlerMethodArgumentResolvers as shown here:
#EnableWebMvc
#Configuration
public class WebMvcConfiguration
extends WebMvcConfigurerAdapter {
...
#Autowired
public void setArgumentResolvers(RequestMappingHandlerAdapter adapter) {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new CustomPrincipalArgumentResolver());
resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
adapter.setArgumentResolvers(resolvers);
}
}
Subclass WebMvcConfigurationSupport
You can also extend WebMvcConfigurationSupport instead of using #EnableWebMvc to ensure your HandlerMethodArgumentResolver is used first. For example:
#Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
...
#Bean
#Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter()();
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new CustomPrincipalArgumentResolver());
resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
adapter.setArgumentResolvers(resolvers);
return adapter;
}
}
I know this is an old question, but as it does come up on top on Google when searching for injecting a Principal, I'll post a 2020 update:
Since Spring Security 4.0 you can just simply inject an #AuthenticationPrincipal into your controller methods:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(#AuthenticationPrincipal UserPrincipal user){
// implementation
}
This will work out of the box, no additional config required.