i am new to spring and i'm working on spring boot REST with spring security and currently I implemented JWT token. I have some questions but can't seem to find an answer to them. I tried adding a refresh token.
At first i thought i will store it in database with user, but spring security does everything automatically and i can't seem to find how to store it at a given field of table user.
So, moving on i decided i will try sticking with spring security automation and I set refresh token expiration time to 10 seconds to test if it expires, but sadly it does not work as intended - I can use refresh token for as long as I want and generate new tokens with it.
So here I have a couple of questions:
1. How do i make refresh token expire after given time? Here's my security config
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${security.signing-key}")
private String signingKey;
@Value("${security.encoding-strength}")
private Integer encodingStrength;
@Value("${security.security-realm}")
private String securityRealm;
@Autowired
private UserDetailsService userDetailsService;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic()
.realmName(securityRealm).and().csrf().disable();
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setRefreshTokenValiditySeconds(10);
return defaultTokenServices;
}
}
I did find an answer, just forgot to update my ticket. So here it goes, by default JwtTokenStore does not support refresh tokens. Here's JwtTokenStore source code. So what this means, is that enabling token in settings, won't actually make it work. What i did, was create my own JWT token store that extends JwtTokenStore and write my own refresh token logic.
public class MyJwtTokenStore extends JwtTokenStore {
@Autowired
UserRepository userRepository;
public MyJwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
super(jwtTokenEnhancer);
}
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
String username = authentication.getUserAuthentication().getName();
User user = userRepository.findByEmail(username);
user.setToken(refreshToken.getValue());
userRepository.save(user);
}
@Override
public OAuth2RefreshToken readRefreshToken(String token) {
OAuth2Authentication authentication = super.readAuthentication(token);
String username = authentication.getUserAuthentication().getName();
User user = userRepository.findByEmail(username);
if (!token.equals(user.getToken())) {
return null;
}
OAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken(token);
return refreshToken;
}
@Override
public void removeRefreshToken(OAuth2RefreshToken token) {
OAuth2Authentication authentication = super.readAuthentication(token.getValue());
String username = authentication.getUserAuthentication().getName();
User user = userRepository.findByEmail(username);
user.setToken(null);
userRepository.save(user);
}
}
After this, i just updated my TokenStore Bean
@Bean
public TokenStore tokenStore() {
MyJwtTokenStore jwtTokenStore = new MyJwtTokenStore(accessTokenConverter());
return jwtTokenStore;
// return new JwtTokenStore(accessTokenConverter());
}
And at the end I added remaining settings to my AuthorizationServerConfigurerAdapter class to support refresh token and give it time of validity.
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Value("${security.jwt.client-id}")
private String clientId;
@Value("${security.jwt.client-secret}")
private String clientSecret;
@Value("${security.jwt.grant-type}")
private String grantType;
@Value("${security.jwt.grant-type-other}")
private String grantTypeRefresh;
@Value("${security.jwt.scope-read}")
private String scopeRead;
@Value("${security.jwt.scope-write}")
private String scopeWrite = "write";
@Value("${security.jwt.resource-ids}")
private String resourceIds;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AppUserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.authorizedGrantTypes(grantType, grantTypeRefresh).scopes(scopeRead, scopeWrite)
.resourceIds(resourceIds).autoApprove(false).accessTokenValiditySeconds(1800) // 30min
.refreshTokenValiditySeconds(86400); //24 hours
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
endpoints.tokenStore(tokenStore).accessTokenConverter(accessTokenConverter).tokenEnhancer(enhancerChain)
.reuseRefreshTokens(false).authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*"); // http://localhost:4200
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}