My Spring WebClient is not sending Bearer Token, I found that it is because Spring is getting the token from "access_token" but the external server is sending "accessToken". TokenResponse.class:
public static TokenResponse parse(final JSONObject jsonObject)
throws ParseException{
if (jsonObject.containsKey("access_token"))
return AccessTokenResponse.parse(jsonObject);
else
return TokenErrorResponse.parse(jsonObject);
}
How can I force Spring to parse this token?
My config:
#Bean
public ReactiveClientRegistrationRepository clientRegistrations(
#Value("${rest.security.clientId}") final String clientId,
#Value("${rest.security.clientSecret}") final String clientSecret,
#Value("${rest.endpoint}") final String baseUrl) {
final var clientRegistration = ClientRegistration
.withRegistrationId(CLIENT_REGISTRATION_ID)
.tokenUri(baseUrl+ "/token")
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationGrantType(CLIENT_CREDENTIALS)
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
Related
How can i test an endpoint with authorization header with a user context?
mockMvc.perform(post("/endpoint")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationToken(APP_AUTHORIZATION)))
.andDo(print()).andExpect(status().is2xxSuccessful());
I tried to do this, but it didn't work.
private String buildToken(#Nonnull ApiClient.JwtData data) throws JsonProcessingException {
final var token = JWT.create()
.withSubject(data.getUserId())
.withIssuedAt(new Date())
.withClaim("name", data.getUsername())
.withClaim("fullname", data.getUserFullName())
.withClaim("userid", data.getUserId())
.withClaim("locale", Objects.requireNonNullElse(data.getLocale(), DEFAULT_LOCALE));
final var authsMap = new HashMap<String, String[]>();
token.withClaim("authorities", objectMapper.writeValueAsString(authsMap));
return token.sign(Algorithm.HMAC256(SECRET));
}
protected String authorizationToken(String... authorities) throws JsonProcessingException {
final var jwtData = new ApiClient.JwtData("USER_ID", "USER_NAME", "USER_FULLNAME"
, "locale", "USER_AUTHORITIES", "US-EN", authorities);
return buildToken(jwtData);
}
I'm using spring-boot-starter-oauth2-client to authenticate my user with Google. This works well and I can sign in and get valid access and refresh token as expected.
I'm creating the access token as such:
public class TokenServiceImpl implements TokenService {
private final OAuth2AuthorizedClientService clientService;
#Override
public GoogleCredentials credentials() {
final var accessToken = getAccessToken();
return getGoogleCredentials(accessToken);
}
private GoogleCredentials getGoogleCredentials(String accessToken) {
return GoogleCredentials
.newBuilder()
.setAccessToken(new AccessToken(accessToken, null))
.build();
}
private String getAccessToken() {
final var oauthToken = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
return clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName()).getAccessToken().getTokenValue();
}
}
The token is ultimately being used in the Google Photo API client as such
private PhotosLibraryClient getClient() {
final var settings =
PhotosLibrarySettings
.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(tokenService.credentials()))
.build();
return PhotosLibraryClient.initialize(settings);
}
The problem is that the token will expire after a short period and I'd like to refresh it to keep it active.
I'm unsure what pattern of methods I can use to do this, without having to write the entire OAuth flow (defeating the purpose of something like the Spring oauth2-client).
So far I have no other token/security/filter logic in my application.
Do I just need to write it all out manually, or is there another way I can do this?
The OAuth2AuthorizedClientManager will take care of refreshing your access token for you, assuming you get a refresh token along with your access token. The doco for OAuth2AuthorizedClientManager is at
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client
When configuring your OAuth2AuthorizedClientManager, make sure you have included refreshToken in the OAuth2AuthorizedClientProvider...
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
You then use the OAuth2AuthorizedClientManager to get the access token. The sample from the spring doco is below...
#Controller
public class OAuth2ClientController {
#Autowired
private OAuth2AuthorizedClientManager authorizedClientManager;
#GetMapping("/")
public String index(Authentication authentication,
HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
.principal(authentication)
.attributes(attrs -> {
attrs.put(HttpServletRequest.class.getName(), servletRequest);
attrs.put(HttpServletResponse.class.getName(), servletResponse);
})
.build();
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
...
return "index";
}
}
If the current accessToken has expired, this will automatically request a new accessToken using the previously obtained refreshToken.
I'm trying to get an Access Token from Keycloak over SpringBoot and did try the following example. But the KeycloakAuthenticationToken token is null.
Does someone know another approach to get an Access Token?
#GetMapping("/token")
public String getToken(HttpServletRequest request) throws IOException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) token.getAccount().getKeycloakSecurityContext();
KeycloakSecurityContext context = token.getAccount().getKeycloakSecurityContext();
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(session.getToken());
String idTokenPretty = JsonSerialization.writeValueAsPrettyString(session.getIdToken());
RefreshToken refreshToken;
try {
refreshToken = new JWSInput(session.getRefreshToken()).readJsonContent(RefreshToken.class);
} catch (JWSInputException e) {
throw new IOException(e);
}
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
return refreshTokenPretty;
}
Seems like I can get a token like this with ('org.keycloak:keycloak-admin-client'):
Keycloak keycloak = KeycloakBuilder.builder() //
.serverUrl(serverUrl) //
.realm(realm) //
.grantType(OAuth2Constants.PASSWORD) //
.clientId(clientId) //
.clientSecret(clientSecret) //
.username(userName) //
.password(password) //
.build();
AccessTokenResponse tok = keycloak.tokenManager().getAccessToken();
If someone knows a more elegant way, I would appreciate if you let me know :)
Thanks in advance!
Try the following:
HttpEntity<MultiValueMap<String, String>> request =
new TokenRequest.Builder(clientID, OAuth2Constants.PASSWORD)
.add("username", userName)
.add("password", password)
.build();
ResponseEntity<String> response = restTemplate.postForEntity( postUrl, request , String.class );
return response.getBody();
and the helper class:
public class TokenRequest {
public static class Builder{
MultiValueMap<String, String> data;
public Builder(String clientID, String grant_type){
data = new LinkedMultiValueMap<>();
data.put("client_id", Collections.singletonList(clientID));
data.put("grant_type", Collections.singletonList(grant_type));
}
public Builder add(String key, String value){
data.put(key, Collections.singletonList(value));
return this;
}
public HttpEntity<MultiValueMap<String, String>> build(){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
return new HttpEntity<>(data, headers);
}
}
private TokenRequest(){
}
}
Try this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
KeycloakAuthenticationToken keycloakAuthenticationToken = (KeycloakAuthenticationToken) request.getUserPrincipal();
KeycloakPrincipal<KeycloakSecurityContext> principal = (KeycloakPrincipal) keycloakAuthenticationToken.getPrincipal();
String token = principal.getKeycloakSecurityContext().getIdTokenString();
Hi Im trying to add a bearer token to a retrofit call in java, but i cant seem to pass it.
Currently Im logging in with one method and this creates a bearer token and im trying to add the token to the Get Call, but its just returning a 401 error, have I added the token to the call correctly?
#GET("diagnosis/configuration")
Call<ResponseBody> getFavourites (#Query("favourite") Boolean fave,#Header("Bearer Token") String authHeader);
#POST("auth/login")
Call<LoginResponse> postLogin (#Body LoginCredentialsBody body);
public class LoginApiStepDefinition extends TestBaseFix {
Retrofit retrofit = super.buildRetrofit(super.buildOkHttpClient());
RetrofitCallsLogin call = retrofit.create(RetrofitCallsLogin.class);
RetrofitCallsGetFavourites favecall = retrofit.create(RetrofitCallsGetFavourites.class);
private Response<LoginResponse> responseBody;
private String favouritesResponseBody;
String usernameValue;
String passwordValue;
#And("I login with {string} and {string} to return login token")
public void iLoginWithAndToReturnLoginToken(String username, String password) throws Exception {
LoginApi(username, password);
}
public String LoginApi(String username, String password) throws Exception {
usernameValue = username;
passwordValue = password;
//gets fixture ids for the dates
LoginCredentialsBody login = new LoginCredentialsBody();
login.setPassword(passwordValue);
login.setUsername(usernameValue);
String responseBody = call.postLogin(login).execute().body().toString();
String requiredString = responseBody.substring(responseBody.indexOf("=") + 1, responseBody.indexOf(","));
System.out.println(requiredString);
return token;
}
#Then("I get the list of favourites with {string} and {string}")
public void iGetTheListOfFavouritesWithAnd(String username, String password) throws Exception {
String favouritesResponseBody = favecall.getFavourites(true, LoginApi(username, password)).execute().body().toString();
System.out.println(favouritesResponseBody);
}
}
To add bearer token in retrofit, you have to create a class that implements Interceptor
public class TokenInterceptor implements Interceptor{
#Override
public Response intercept(Chain chain) throws IOException {
//rewrite the request to add bearer token
Request newRequest=chain.request().newBuilder()
.header("Authorization","Bearer "+ yourtokenvalue)
.build();
return chain.proceed(newRequest);
}
}
Now add your Interceptor class in OKHttpClient object and add that obejct in Retrofit object:
TokenInterceptor interceptor=new TokenInterceptor();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("add your url here")
.addConverterFactory(JacksonConverterFactory.create())
.build();
these three class will be your final setup for all types of call
for first call(Login) you do not need to pass token and after login pass jwt as bearer token to authenticate after authentication do not need to pass
public class ApiUtils {
private static final String BASE_URL="https://abcd.abcd.com/";
public ApiUtils() {
}
public static API getApiService(String token){
return RetrofitClient.getClient(BASE_URL,token).create(API.class);
}}
2.Using ApiUtils.getapiService you can get the client ,pass jwt or bearer token
public class RetrofitClient {
public static Retrofit retrofit=null;
public static Retrofit getClient(String baseUrl, String token){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60,TimeUnit.SECONDS)
.connectTimeout(60,TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(new Interceptor() {
#NotNull
#Override
public Response intercept(#NotNull Chain chain) throws IOException {
Request request=chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
}
}).build();
if(retrofit==null||token!=null){
retrofit= new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}}
3 In this Interface you can create methods for get or post requests
public interface API {
#POST("/Api/Authentication/Login")
Call<JsonObject> login(#Body Model userdata);
#POST("/api/Authentication/ValidateSession")
Call<JsonObject> validateSession(#Body MyToken myToken);
#POST("/api/master/abcd")
Call<JsonObject> phoneDir(#Body JsonObject jsonObject);
#Multipart
#POST("/api/dash/UploadProfilePic")
Call<JsonObject> uploadProfile(#Part MultipartBody.Part part);
#FormUrlEncoded
#POST("/api/dashboard/RulesAndPolicies")
Call<JsonObject> rulesAndProcess(#Field("ct") int city);
#FormUrlEncoded
#POST("/api/dashboard/RulesAndPolicies")
Call<JsonObject> rulesAndProcess(
#Field("city") int city,
#Field("department") String department,
#Field("ctype") String ctype
);
When I log into my account in my app, I save an auth token in my SharedPreferences, like this:
PreferenceUtils.setAuthToken(LoginActivity.this, authToken);
Here is my PreferenceUtils class:
public class PreferenceUtils {
public static SharedPreferences getSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
public static String getAuthToken(Context context) {
SharedPreferences sp = getSharedPreferences(context);
return sp.getString("auth_token", null);
}
public static void setAuthToken(Context context, final String token) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString("auth_token", token).apply();
}
}
When I log out of the account, I delete the auth token by calling the logOut() method in my UserUtils class:
public class UserUtils {
public static void logOut(Context context) {
SharedPreferences prefs = PreferenceUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.remove("auth_token");
editor.apply();
}
}
However, even after logging out of my account and removing the auth token from SharedPreferences, all Retrofit calls still somehow have the auth token saved and I'm not sure how.
In other words, when I log out of my account and Retrofit makes a new call, it will print out the auth token that I thought I had deleted when the user logged out.
Only when I restart my app does the auth token get fully removed.
Why is it doing this?
Here is my Retrofit client class (note the comment):
public class ApiClient {
public static final String API_BASE_URL = "https://www.example.com/";
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
private static HttpLoggingInterceptor logging =
new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
public static Retrofit getRetrofit() {
return retrofit;
}
public static <S> S createService(Class<S> serviceClass) {
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit.create(serviceClass);
}
public static <S> S createService(Class<S> serviceClass, final String authToken) {
if (authToken != null) {
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// THIS STILL PRINTS THE AUTH TOKEN EVEN AFTER I'VE
// REMOVED IT FROM THE SHARED PREFERENCES
Log.d("AUTH TOKEN", authToken);
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "Bearer " + authToken)
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
}
How do I fix this??
Most likely the reason you still are seeing the token is because while you delete the token from the share preferences, you never reset the variable in the program. You need to make sure that you set the variable to null or empty, not just delete it from shared preferences.
As you requested, here is my previous comment as an answer:
It's because the interceptor you added is still alive in the httpClient. Once you've removed the token (or pass a null to the createService() method, you need to remove the interceptor as well.