I use API oauth/token to get JWT token in spring sercurity oauth2. I try to add some additional information in the response by using ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo) of enhance method of TokenEnhancer interface. But these additionalInfo added to JWT too, so It is too big. Is there any way to add additionalInfo to the body of oauth/token request, but not in JWT.
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
WebUser webUser = (WebUser) authentication.getUserAuthentication().getPrincipal();
additionalInfo.put("user_name", authentication.getName());
additionalInfo.put("roles", authentication.getAuthorities());
if(webUser.getFunctions() != null) {
additionalInfo.put("functions", webUser.getFunctions().toString());
}else {
additionalInfo.put("functions", null);
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
When you modify a token, it is said to be "enhancing the token" in Spring context. Logically, you should first convert the token into JWT then add the other properties so that they do not contribute to you payload of JWT.
Here is a snippet from my project built using spring boot
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(jwtAccessTokenConverter())
.tokenStore(tokenStore());
}
In here, I'm adding few properties using the tokenEnhancer() to my token and then enhancing that token to become a JWT using jwtAccessTokenEnhancer(). If I reverse the order here, I get what you are desire.
Related
I am trying to upgrade to spring security 5.5.1 on a WebClient call.
I found out that the oauth2 clientId and secret are now URL encoded in AbstractWebClientReactiveOAuth2AccessTokenResponseClient, but my token provider does not support this (for example if the secret contains a + character it works only when it is sent as a + not as %2B).
I understand this is seen as a bug fix from spring-security side ), but I cannot make the token provider change its behavior easily.
So I tried to find a way to work around this.
The [documentation] (https://docs.spring.io/spring-security/site/docs/current/reference/html5/#customizing-the-access-token-request) on how to customize the access token request does not seem to apply when you use a WebClient configuration (which is my case).
In order to remove the clientid/secret encoding I had to extend and copy most of the existing code from AbstractWebClientReactiveOAuth2AccessTokenResponseClient to customize the WebClientReactiveClientCredentialsTokenResponseClient because most of it has private/default visibility.
I traced this in an enhancement issue in the spring-security project.
Is there an easier way to customize the Authorization header of the token request, in order to skip the url encoding ?
There is definitely room for improvement in some of the APIs around customization, and for sure these types of questions/requests/issues from the community will continue to help highlight those areas.
Regarding the AbstractWebClientReactiveOAuth2AccessTokenResponseClient in particular, there is currently no way to override the internal method to populate basic auth credentials in the Authorization header. However, you can customize the WebClient that is used to make the API call. If it's acceptable in your use case (temporarily, while the behavior change is being addressed and/or a customization option is added) you should be able to intercept the request in the WebClient.
Here's a configuration that will create a WebClient capable of using an OAuth2AuthorizedClient:
#Configuration
public class WebClientConfiguration {
#Bean
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
// #formatter:off
ServerOAuth2AuthorizedClientExchangeFilterFunction exchangeFilterFunction =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
exchangeFilterFunction.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder()
.filter(exchangeFilterFunction)
.build();
// #formatter:on
}
#Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
// #formatter:off
WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.setWebClient(createAccessTokenResponseWebClient());
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(consumer ->
consumer.accessTokenResponseClient(accessTokenResponseClient)
.build())
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// #formatter:on
return authorizedClientManager;
}
protected WebClient createAccessTokenResponseWebClient() {
// #formatter:off
return WebClient.builder()
.filter((clientRequest, exchangeFunction) -> {
HttpHeaders headers = clientRequest.headers();
String authorizationHeader = headers.getFirst("Authorization");
Assert.notNull(authorizationHeader, "Authorization header cannot be null");
Assert.isTrue(authorizationHeader.startsWith("Basic "),
"Authorization header should start with Basic");
String encodedCredentials = authorizationHeader.substring("Basic ".length());
byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials);
String credentialsString = new String(decodedBytes, StandardCharsets.UTF_8);
Assert.isTrue(credentialsString.contains(":"), "Decoded credentials should contain a \":\"");
String[] credentials = credentialsString.split(":");
String clientId = URLDecoder.decode(credentials[0], StandardCharsets.UTF_8);
String clientSecret = URLDecoder.decode(credentials[1], StandardCharsets.UTF_8);
ClientRequest newClientRequest = ClientRequest.from(clientRequest)
.headers(httpHeaders -> httpHeaders.setBasicAuth(clientId, clientSecret))
.build();
return exchangeFunction.exchange(newClientRequest);
})
.build();
// #formatter:on
}
}
This test demonstrates that the credentials are decoded for the internal access token response WebClient:
#ExtendWith(MockitoExtension.class)
public class WebClientConfigurationTests {
private WebClientConfiguration webClientConfiguration;
#Mock
private ExchangeFunction exchangeFunction;
#Captor
private ArgumentCaptor<ClientRequest> clientRequestCaptor;
#BeforeEach
public void setUp() {
webClientConfiguration = new WebClientConfiguration();
}
#Test
public void exchangeWhenBasicAuthThenDecoded() {
WebClient webClient = webClientConfiguration.createAccessTokenResponseWebClient()
.mutate()
.exchangeFunction(exchangeFunction)
.build();
when(exchangeFunction.exchange(any(ClientRequest.class)))
.thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK).build()));
webClient.post()
.uri("/oauth/token")
.headers(httpHeaders -> httpHeaders.setBasicAuth("aladdin", URLEncoder.encode("open sesame", StandardCharsets.UTF_8)))
.retrieve()
.bodyToMono(Void.class)
.block();
verify(exchangeFunction).exchange(clientRequestCaptor.capture());
ClientRequest clientRequest = clientRequestCaptor.getValue();
String authorizationHeader = clientRequest.headers().getFirst("Authorization");
assertThat(authorizationHeader).isNotNull();
String encodedCredentials = authorizationHeader.substring("Basic ".length());
byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials);
String credentialsString = new String(decodedBytes, StandardCharsets.UTF_8);
String[] credentials = credentialsString.split(":");
assertThat(credentials[0]).isEqualTo("aladdin");
assertThat(credentials[1]).isEqualTo("open sesame");
}
}
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);
}
I'm writing a filter that would intercept an Restful API call , extract a Bearer token and make a call to an Authorization Server for validation.
I couldn't find one in Spring Boot that does it out of the box, but I'm sure there is a cleaner way to do this.
here is what I have (pseudo code):
public class SOOTokenValidationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String xAuth = request.getHeader("Authorization");
// validate the value in xAuth
if(isValid(xAuth) == false){
throw new SecurityException();
}
// Create our Authentication and set it in Spring
Authentication auth = new Authentication ();
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
}
private boolean isValid (String token){
// make a call to SSO passing the access token and
// return true if validated
return true;
}
}
Lessons learned, Spring Security Oauth2 documentation is woefully inadequate, forget about trying to use the framework without fully combing through the source code. On the flip side the code is well written and easy to follow kudos to Dave Syer.
Here is my config:
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/")
.permitAll()
.and()
.addFilterBefore(getOAuth2AuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling();
}
Here is my getOAuth2AuthenticationProcessingFilter method:
private OAuth2AuthenticationProcessingFilter getOAuth2AuthenticationProcessingFilter() {
// configure token Extractor
BearerTokenExtractor tokenExtractor = new BearerTokenExtractor();
// configure Auth manager
OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager();
// configure RemoteTokenServices with your client Id and auth server endpoint
manager.setTokenServices(remoteTokenServices);
OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter();
filter.setTokenExtractor(tokenExtractor);
filter.setAuthenticationManager(manager);
return filter;
}
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.
I am trying to find a way to revoke Oauth2 JWT Refresh Token with vanilla Spring implementation and JwtTokenStore.
First: can somebody confirm that there is no API similar to /oauth/token that allows me to revoke a refresh token?
I wanted to add a custom API that would delete the refresh token along the folowing lines:
OAuth2RefreshToken oauth2RefreshToken=tokenStore.readRefreshToken(refreshToken);
tokenStore.removeRefreshToken(oauth2RefreshToken);
Now, looking at the JwtTokenStore, I noticed that it uses an ApprovalStore. So I went ahead and provided an InMemoryApprovalStore to my JwtTokenStore. My JwtTokenStore instantiation this look as follows:
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123456");
return converter;
}
#Bean
public JwtTokenStore getTokenStore(){
tokenStore= new JwtTokenStore(jwtTokenEnhancer());
tokenStore.setApprovalStore(new InMemoryApprovalStore());
tokenStore.setTokenEnhancer(jwtTokenEnhancer());
return tokenStore;
};
Results: with no InMemoryApprovalStore, I can authenticate users and refresh tokens without problems. However, as soon as I add InMemoryApprovalStore to the token store, I start getting the following error message:
{"error":"invalid_grant","error_description":"Invalid refresh token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NDUwMjQ2MTcsInVzZXJfbmFtZSI6IjYzZjIyYjZlLWU5MGUtNDFjYS1iYzJlLTBmZTgzNmY3MTQ2NyIsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiMjgwMDgwNWQtMjk1Zi00ZDQzLWI2NTYtMDNlZWYwMWFkMjg0IiwiY2xpZW50X2lkIjoid2ViLWNsaWVudCIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSIsInRydXN0Il19.BPC0HqLYjWGM0IFjvsUGGKQ9dyIXSXwMhraCVFIxD0U"}
My second question is thus what is the proper way to revoke a refresh token?
Edit: I found the following thread that suggests that ApprovalStore is indeed the way to revoke JWT tokens. I now just need to find out how to use them properly.
First: can somebody confirm that there is no API similar to /oauth/token that allows me to revoke a refresh token?
Confirmed.
You don't need to define JwtTokenStore bean, spring will create it for you using AuthorizationServerEndpointsConfigurer
private TokenStore tokenStore() {
if (tokenStore == null) {
if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
}
else {
this.tokenStore = new InMemoryTokenStore();
}
}
return this.tokenStore;
}
private ApprovalStore approvalStore() {
if (approvalStore == null && tokenStore() != null && !isApprovalStoreDisabled()) {
TokenApprovalStore tokenApprovalStore = new TokenApprovalStore();
tokenApprovalStore.setTokenStore(tokenStore());
this.approvalStore = tokenApprovalStore;
}
return this.approvalStore;
}
My second question is thus what is the proper way to revoke a refresh token?
revoke the approval for the token, this was used by JwtTokenStore
private void remove(String token) {
if (approvalStore != null) {
OAuth2Authentication auth = readAuthentication(token);
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);
}
}
}