I have a Spring application that acts as an OAuth2 client. I implemented a JdbcClientTokenServices to persist the tokens for each user that succesfully authenticates.
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), clientContext);
AccessTokenProviderChain accessTokenProvider = new AccessTokenProviderChain(
Collections.<AccessTokenProvider>singletonList(
new AuthorizationCodeAccessTokenProvider()
));
accessTokenProvider.setClientTokenServices(clientTokenServices());
accessTokenProvider.supportsRefresh(resource());
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
#Bean
public JdbcClientTokenServices clientTokenServices() {
return new JdbcClientTokenServices(dataSource);
}
This code makes use of a oauth_client_token table. When I looked for the content in this table, I noticed it only saves the access token? I was wondering why the refresh token does not get saved aswell since the refresh token should be longer lived than the access token anyway.
Related
I'm trying to develop my first java Spring Boot app that calls Strava API and gets my activities for the given period of time.
I've registered my app on Strava's website and got client_id and client secret. I've generated spring-swagger-codegen-api-client and awtowired the client to the app.
#Configuration
public class StravaIntegrationConfiguration {
#Bean
public ActivitiesApi stravaApi(){
return new ActivitiesApi(apiClient());
}
#Bean
public ApiClient apiClient(){
ApiClient apiClient = new ApiClient();
OAuth strava_oauth = (OAuth) apiClient.getAuthentication("strava_oauth");
strava_oauth.setAccessToken("X");
return new ApiClient();
}
}
I'm sure that I use the valid access_Token, my request with this token works fine in Postman. But when I try to call the method loggedInAthleteActivities I get 401 Unauthorized: [{"message":"Authorization Error","errors":[{"resource":"Athlete","field":"access_token","code":"invalid"}]}]
I tried to change the scope to activity: read or activity: read_all but nothing is changed.
May be anybody knows how to fix it?
#Component
public class Adapter {
#Autowired
private ActivitiesApi activitiesApi;
public void getActivities(Integer before, Integer after, Integer page, Integer perPage) {
final List<SummaryActivity> loggedInAthleteActivities = activitiesApi.getLoggedInAthleteActivities(before, after, page, perPage);
System.out.println("All activities from ___ to ___"+ loggedInAthleteActivities.size());
}
}
EDIT
I've found the problem and it had nothing to do with Strava api. There was a mistake in my apiClient, I created new ApiClient after the authentication process:
#Bean
public ApiClient apiClient(){
ApiClient apiClient = new ApiClient();
OAuth strava_oauth = (OAuth) apiClient.getAuthentication("strava_oauth");
strava_oauth.setAccessToken("X");
**return new ApiClient();**
}
It should be return apiClient, not return new ApiClient.
I have a keycloak instance which I use for CRUD operations with roles. Is there any way to get role permissions? I ve tried to search eveerything about it, but I cant find how to get permissions assigned to a role...
Here is an example of my code:
#RestController
#RequestMapping("/roles")
public class RolesController {
// must use "master" realm and "admin-cli" to connect to the instance
// although other realms and clients can be modified
private Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8437/auth")
.realm("master")
.clientId("admin-cli")
.username("admin")
.password("admin")
.build();
#GetMapping
public ResponseEntity<List<RoleRepresentation>> getRoles() throws IOException {
return new ResponseEntity<>(keycloak.realm("dashing-data").roles().list(), HttpStatus.OK);
}
#PostMapping
public ResponseEntity<RoleRepresentation> createRole(#RequestBody RoleRepresentation role) throws IOException {
List<RoleRepresentation> roleList = keycloak.realm("dashing-data").roles().list();
boolean roleAlreadyExist = roleList.stream().anyMatch(r -> r.getName().contains(role.getName()));
RoleRepresentation newRole = new RoleRepresentation();
if (!roleAlreadyExist){
newRole.setName(role.getName());
newRole.setDescription(role.getDescription());
keycloak.realm("dashing-data").roles().create(newRole);
}
return new ResponseEntity<>(newRole, HttpStatus.OK);
}
#DeleteMapping("/{id}")
public ResponseEntity<String> deleteRole(#PathVariable String id){
RoleByIdResource role = keycloak.realm("dashing-data").rolesById();
if (role == null){
return new ResponseEntity<>("Could not find the role!", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>("Role successfully deleted!", HttpStatus.OK);
}
}
I think you are adding complexity where you don't need it. A keycloak Role should be enough for what you are looking for. If you have a CRUD operation that only managers can do (like a DELETE) you simply create a Role in keycloak called "manager" and assign it to the user that you want. In your backend application, you just have to compare the role name to the required role. On the other hand for a Read operation, you can match if the user has the manager or read Role from keycloak.
I have WebClient in my Spring Boot application that connects to the external service via OAuth2, and the configuration of it looks like following:
#Configuration
#RequiredArgsConstructor
public class OAuth2ClientConfiguration {
private final OAuth2ClientProperties properties;
#Bean
ReactiveClientRegistrationRepository clientRegistration() {
ClientRegistration registration = ClientRegistration
.withRegistrationId(properties.getClientRegistrationId())
.tokenUri(properties.getTokenUri())
.clientId(properties.getClientId())
.clientSecret(properties.getClientSecret())
.authorizationGrantType(new AuthorizationGrantType(properties.getAuthorizationGrantType()))
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistration) {
var clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistration);
var authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistration, clientService);
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId(properties.getClientRegistrationId());
return WebClient.builder()
.filter(oauth)
.build();
}
}
and here is an access token:
{
"access_token": "some_generated_access_token",
"token_type": "bearer",
"expires_in": 82822,
"scope": "api",
"jti": "6e1a8d7c-3909-4acf-9168-cf912fcd0c8a"
}
It is working and everything is Ok, but... it is not storing the access token after it gets it, it is getting new access token each time it is called. I figured it out when launching my integration tests and verifying Authorization Server calls. However in configuration shown above it should store in memory.
I found out in internet this kind of problem can occur with SpringBoot version up to 2.2.3 and "org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE"
But I am using newest version of the Spring Boot 2.4.9, and it uses org.springframework.security:spring-security-oauth2-client:5.4.7
How can this issue be solved?
What I wanna achieve
So I have a client application in java (JavaFX + Spring-boot hybrid-application). You can have a look at it here https://github.com/FAForever/downlords-faf-client . So till now we stored username/ password if the user wished to be kept logged in which is obviously a pretty bad idea. So now I wanna store the refreshtoken and then log the user in with that.
What it looks like now
See here
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setClientId(apiProperties.getClientId());
details.setClientSecret(apiProperties.getClientSecret());
details.setClientAuthenticationScheme(AuthenticationScheme.header);
details.setAccessTokenUri(apiProperties.getBaseUrl() + OAUTH_TOKEN_PATH);
details.setUsername(username);
details.setPassword(password);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restOperations = templateBuilder
// Base URL can be changed in login window
.rootUri(apiProperties.getBaseUrl())
.configure(restTemplate);
What I found so far
I found out that restTemplate.getAccessToken().getRefreshToken() will give me the refreshtoken I want to save and later so to keep the user logged in.
What I can not figure out
I can not find a way to create a OAuth2RestTemplate with an refresh token only. Is that even possible? Can someone point me in the right direction? Maybe link me some articles to read? Is this the right place to read?
I do not think this is possible with an OAuth2RestTemplate, but you can reimplement the desired parts yourself. I'd like to share an example with your for OAuth password login to Microsofts flavour of OAuth2 (Azure Active Directory). It does miss the piece of fetching a new token from an existing refresh token yet, but I added a comment where you need to add it.
A simple way to mimic OAuthRestTemplates behavior is a custom ClientHttpRequestInterceptor which delegates the token fetching to a dedicated Spring service component, that you append to your RestTemplate:
#RequiredArgsConstructor
#Slf4j
public class OAuthTokenInterceptor implements ClientHttpRequestInterceptor {
private final TokenService tokenService;
#NotNull
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + tokenService.getRefreshedToken().getValue());
return execution.execute(request, body);
}
}
This interceptor can be added to your primary RestTemplate:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(globalOAuthTokenInterceptor);
restTemplate.setInterceptors(interceptors);
The token service used in the interceptor holds the token in a cache and on request checks for the expiry of the token and if required queries a new one.
#Service
#Slf4j
public class TokenService {
private final TokenServiceProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate;
private OAuth2AccessToken tokenCache;
public TokenService(TokenServiceProperties tokenServiceProperties) {
this.tokenServiceProperties = tokenServiceProperties;
simpleRestTemplate = new RestTemplateBuilder().
build();
}
public OAuth2AccessToken getRefreshedToken() {
if (tokenCache == null || tokenCache.isExpired()) {
log.debug("Token expired, fetching new token");
tokenCache = refreshOAuthToken();
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
}
return tokenCache;
}
public OAuth2AccessToken loginWithCredentials(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("resource", tokenServiceProperties.getAadB2bResource());
map.add("client_id", tokenServiceProperties.getAadB2bClientId());
map.add("username", username);
map.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return simpleRestTemplate.postForObject(
tokenServiceProperties.getAadB2bUrl(),
request,
OAuth2AccessToken.class
);
}
private OAuth2AccessToken refreshOAuthToken() {
return loginWithRefreshToken(tokenCache.getRefreshToken().getValue());
}
public OAuth2AccessToken loginWithRefreshToken(String refreshToken) {
// add code for fetching OAuth2 token from refresh token here
return null;
}
}
In this code example you would once login using username and password and afterwards all further logins would be using the refresh token. If you want to use the refresh token directly, you use the public method, otherwise it will be done internally.
Since the login code is specifically written for login to Microsoft AAD, you should recheck the MultiValueMap parameters.
TokenServiceProperties are straightforward:
#Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
Adapt them if needed.
The whole solution has one minor drawback: Instead of one RestTemplate that you usually fetch via depency injection, you now need a second one (a "simple" one) to fetch the OAuth token. In this example we create it in the constructor of the TokenService. However this is in general bad style as it makes it harder for unit testing etc. You could also think about using qualified beans or using a more basic http client in the TokenService.
Another important thing to note: I am using the spring-security-oauth2 package here. If you did not configure Spring Security in your project, this will trigger Spring Security auto-configuration which might not be desired - you can solve this by excluding undesired packages, e.g. in gradle:
implementation("org.springframework.security.oauth:spring-security-oauth2") {
because "We only want the OAuth2AccessToken interface + implementations without activating Spring Security"
exclude group: "org.springframework.security", module: "spring-security-web"
exclude group: "org.springframework.security", module: "spring-security-config"
exclude group: "org.springframework.security", module: "spring-security-core"
}
I'm developing an OAuth2.0 "CLIENT" application which call some APIs(secured by oauth2.0).
I'm using OAuth2.0RestTemplate which contains CLIENT_ID, CLIENT_SECRET, username and password. The code for calling OAuth2.0 secured APIs looks like this:
#Bean
OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
List<String> Scopes = new ArrayList<String>(2);
Scopes.add("read");
Scopes.add("write");
resource.setClientAuthenticationScheme(AuthenticationScheme.header);
resource.setId("*****");
resource.setAccessTokenUri(tokenUrl);
resource.setClientId("*****");
resource.setClientSecret("*****");
resource.setGrantType("password");
resource.setScope(Scopes);
resource.setUsername("*****");
resource.setPassword("*****");
return resource;
}
#Autowired
private OAuth2RestTemplate restTemplate;
Map<String, String> allCredentials = new HashMap<>();
allCredentials.put("username", "***");
allCredentials.put("password", "***");
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAll(allCredentials);
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() { };
ResponseEntity<List<MyObject>> response = restTemplate.exchange("https://***.*****.com/api/*****/*****",
HttpMethod.GET,
null,
responseType);
AllCities all = new AllCities();
all.setAllCities(response.getBody());
As you can see everytime I want to call a service the code get a new ACCESS TOKEN which is wildly wrong!!! My question is how can I automatically receive and store the issued token in my application an use it until it expires and then automatically get a new one?
On the other hand my token only contains access token and doesn't contain refresh token(I don't know why!!! this is so weird!!!)
Hello you can design like google client library.
First step you need to create the datastore for store the token in your directory like C:/User/soyphea/.token/datastore.
Before you load your function retrieve access_token_store. Your access token should have expired_in.
if(access_token_store from your datastore !=null && !expired){
access_token = access_token_store.
} else {
access_token = Your RestTemplate function for retrieve access_token.
}
finally you can retrieve access_token.
In spring security oauth2 if you want to support refresh_token you need to set,
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("resource-serv")
.scopes("read")
.resourceIds("my-resource")
.secret("secret123")
.and()
.withClient("app")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.scopes("read")
.resourceIds("my-resource")
.secret("appclientsecret");
}
First of all you have define that your app is a Oaut2App for this in Spring boot you can use the annotation #EnableOAuth2Client in your code and configure the client application metadata in your applicaition.yml. A skeleton client app can be like below:
#EnableOAuth2Client
#SpringBootApplication
public class HelloOauthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HelloOauthServiceApplication.class, args);
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource){
return new OAuth2RestTemplate(resource);
}
}
application.yml
security:
oauth2:
client:
clientId: client
clientSecret: secret
accessTokenUri: http://localhost:9090/oauth/token
userAuthorizationUri: http://localhost:9090/oauth/authorize
auto-approve-scopes: '.*'
registered-redirect-uri: http://localhost:9090/login
clientAuthenticationScheme: form
grant-type: passwordR
resource:
token-info-uri: http://localhost:9090/oauth/check_token
in this way you have guarantee that the OAuth2RestTemplate of spring will use and upgrade the token