Maybe I'm overlooking something, but I didn't find any example dealing with revocation of JWT access/refresh tokens. Is there any built-in way I can use to revoke such tokens? How would one go about setting up such revocation using some kind of caching mechanism (EHCache, Hazelcast, ...)? I'm using spring-security-jwt.
+1
You can use a physical ApprovalStore (that's the approach I normally take) with whatever back end you like. I'm not sure what the cache gives you, and using a cache as a key-value store is not a good idea IMO.
I am facing the similar issue. Are there any examples I could take a look? Thanks.
Hi,
I am facing similer problem. I tired to use InMemoryApprovalStore by adding it to JWTTokenStore.
jwtTokenStore.setApprovalStore(new InMemoryApprovalStore());
Any good examples that I can take a look ?
@MadhuraMaddali Using the InMemoryApprovalStore is not a very good idea, on restart all the approvals are gone :-( Use a persistent implementation like the JdbcApprovalStore.
It is important that the same approval store is also used in:
@Configuration
MyOAuthConfig extends org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter
{
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// ... other endpoints config ...
endpoints.approvalStore(jdbcApprovalStore())
.tokenStore(tokenStore());
}
@Bean
JdbcApprovalStore jdbcApprovalStore() {
return new JdbcApprovalStore(dataSource);
}
@Bean
public JwtTokenStore tokenStore() {
JWTTokenStore jwtTokenStore = new JWTTokenStore(jwtTokenEnhancer());
jwtTokenStore .setApprovalStore(jdbcApprovalStore());
return jwtTokenStore ;
}
...
}
I am also looking for default option in spring to revoke JWT token, How to achieve this?
To revoke tokens you use a persistence storage, not a JwtTokenStore because everything is in the token and you cannot revoke the token! The only thing you can do is revoke the approvals and on the next request for an access token with a refresh Token it is declined. When the user permits the access again, the old refresh tokens are also permitted.
If you want to revoke tokens itself, you need to use a JdbcTokenStore or any other persistence store and remove the token to revoke it. Normally access tokens expire very fast (minutes or hours maybe) and the refresh token much longer. When it needs a new access token with the refresh token, you can reject access. It depends on the use case if this is secure enough.
mbreevoort Thank you for your response.It is helpful.
Looking for some solution with out maintaining the tokens in the Persistence store. Any thoughts ?
Why refresh tokens are self signed JWT tokens when JwtTokenStore is used? It should be enough that refresh tokens are some random stuff which then can be revoked. When new access token is obtains using the refresh tokens we need to check that tokens is not revoked and then get scopes etc what is approved for that specific refresh token.
So use an ApprovalStore with a physical backend (as advised above). What鈥檚 the issue?
@dsyer is there an example on how to use JdbcApprovalStore in conjunction with the JWT?
The UAA from cloud foundry used to work that way. I don't know of any minimal sample. It's trivial to set up though, and there is no way we can cover all possible combinations of different options in the project.
@dsyer Thanks. I ended up going with my own implementation of JwtTokenStore storeAccessToken method which stores tokens in the database. This was needed by business in order to blacklist long term refresh tokens.
@makdeniss Can share with us?
@terziisk sorry, I left that company. I cannot share that code anymore. But the solution was pretty straightforward. You implement some spring default interfaces for this matter, inject them as beans and set the order.
@dsyer how could using the ApprovalStore to revoke a refresh token be a viable solution ? Would that not effectively logout the user across all browsers \ devices ? Shouldn't the right approach be to focus on removing a specific refresh token ?
If described issue is still valid for anybody here is quick solution that came into my mind:
public class ManageableJwtTokenStore extends JwtTokenStore {
@Autowired
private RefreshTokenStorage refreshTokenStorage;
public ManageableJwtTokenStore( JwtAccessTokenConverter jwtTokenEnhancer ) {
super( jwtTokenEnhancer );
}
@Override
public void storeRefreshToken( OAuth2RefreshToken refreshToken, OAuth2Authentication authentication ) {
super.storeRefreshToken( refreshToken, authentication );
// store token
parseTokenId( refreshToken )
.ifPresent( s -> refreshTokenStorage.store( s ) );
}
@Override
public OAuth2RefreshToken readRefreshToken( String tokenValue ) {
// check token validity
return parseTokenId( tokenValue )
.filter( tokenId -> refreshTokenStorage.isValid( tokenId ) )
.map( super::readRefreshToken )
.orElse( null );
}
@Override
public void removeRefreshToken( OAuth2RefreshToken token ) {
super.removeRefreshToken( token );
//remove token
parseTokenId( token )
.ifPresent( tokenId -> refreshTokenStorage.remove( tokenId ) );
}
private Optional<String> parseTokenId( OAuth2RefreshToken refreshToken ) {
return parseTokenId( refreshToken.getValue() );
}
private Optional<String> parseTokenId( String token ) {
return Optional.ofNullable( token )
.map( JwtHelper::decode )
.map( Jwt::getClaims )
.map( claimsStr -> objectMapper.parseMap( claimsStr ) )
.filter( claims -> claims.containsKey( JwtAccessTokenConverter.TOKEN_ID ) )
.map( claims -> claims.get( JwtAccessTokenConverter.TOKEN_ID ) )
.map( Object::toString );
}
}
So we just override 3 methods of JwtTokenStore and add custom storage for refresh_token
@denyskonakhevych why not just injecting the JdbcTokenStore into your ManageableJwtTokenStore and delegating reading and storing refresh tokens to the JdbcTokenStore.
public class ManageableJwtTokenStore extends JwtTokenStore {
@Autowired
private JdbcTokenStore jdbcTokenStore;
@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
return jdbcTokenStore.readRefreshToken(tokenValue);
}
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
jdbcTokenStore.storeRefreshToken(refreshToken, authentication);
}
@SourenaSahraian In my case I use DynamoDB. If RefreshTokenStorage is interface It is possible to make implementation to use jdbc.
public interface RefreshTokenStorage {
void store( String tokenId );
void remove( String tokenId );
boolean isValid( String tokenId );
}
I'm using the JwtTokenStore, and I'm not able to revoke the refresh token.
One could post an example. Every example I see on the internet is using jdbcTokenStore.
@omag0 What you need to do is something along the lines of:
@PostMapping(value = "/logout")
public ResponseEntity<String> userLogout(
@ApiParam(value = "The existing refresh token to be revoked") @RequestBody RefreshTokenParam token) {
tokenStore.removeRefreshToken(tokenStore.readRefreshToken(token.getToken()));
//TODO clear the cookie
return new ResponseEntity<String>( HttpStatus.OK);
}
And:
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
public class ManageableJwtTokenStore extends JwtTokenStore {
@Autowired
private JdbcTokenStore jdbcTokenStore;
public ManageableJwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
super(jwtTokenEnhancer);
}
@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
OAuth2RefreshToken token = jdbcTokenStore.readRefreshToken(tokenValue);
return token;
}
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
jdbcTokenStore.storeRefreshToken(refreshToken, authentication);
}
@Override
public void removeRefreshToken(OAuth2RefreshToken token) {
jdbcTokenStore.removeRefreshToken(token);
;
}
}
Of course you need to create the OAuth_refresh_token prior to all this : the JdbcTokenStore will take care of the rest , storing or removing the token .
It is I think the hash value(MD5) of the JWTtoken that you are storing as token_id.
create table oauth_refresh_token (
token_id VARCHAR(255),
token BLOB,
authentication BLOB
);
Closing this as questions are better suited on Stack Overflow. We prefer to use GitHub issues for bugs and enhancements.
Most helpful comment
If described issue is still valid for anybody here is quick solution that came into my mind:
So we just override 3 methods of JwtTokenStore and add custom storage for refresh_token