Add token hashing
parent
5a5c1548c1
commit
a0363cdaa8
|
@ -0,0 +1,47 @@
|
|||
package ba.steleks;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* Helper class which is able to autowire a specified class. It holds a static reference to the {@link org
|
||||
* .springframework.context.ApplicationContext}.
|
||||
*/
|
||||
public final class AutowireHelper implements ApplicationContextAware {
|
||||
|
||||
private static final AutowireHelper INSTANCE = new AutowireHelper();
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
private AutowireHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
|
||||
* are null.
|
||||
*
|
||||
* @param classToAutowire the instance of the class which holds @Autowire annotations
|
||||
* @param beansToAutowireInClass the beans which have the @Autowire annotation in the specified {#classToAutowire}
|
||||
*/
|
||||
public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
|
||||
for (Object bean : beansToAutowireInClass) {
|
||||
if (bean == null) {
|
||||
applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(final ApplicationContext applicationContext) {
|
||||
AutowireHelper.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the singleton instance.
|
||||
*/
|
||||
public static AutowireHelper getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -38,18 +38,6 @@ class ServiceInstanceRestController {
|
|||
this.discoveryClient=discoveryClient;
|
||||
}
|
||||
|
||||
@Value("${user.password}")
|
||||
private String password;
|
||||
|
||||
@RequestMapping(
|
||||
value = "/whoami/{username}",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public String whoami(@PathVariable("username") String username) {
|
||||
return String.format("Hello! You're %s and you'll become Developer, " +
|
||||
"but only if your password is '%s'!\n", username, password);
|
||||
}
|
||||
|
||||
@RequestMapping("/service-instances/{applicationName}")
|
||||
public List<ServiceInstance> serviceInstancesByApplicationName(
|
||||
@PathVariable String applicationName) {
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package ba.steleks;
|
||||
|
||||
import ba.steleks.security.SteleksUsersDetailsService;
|
||||
import ba.steleks.security.token.HashTokenEncoder;
|
||||
import ba.steleks.security.token.TokenEncoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
|
@ -11,7 +15,22 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||
@Configuration
|
||||
public class UsersConfig {
|
||||
@Bean
|
||||
public static PasswordEncoder providePasswordEncoder(){
|
||||
public static PasswordEncoder providePasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static TokenEncoder provideTokenEncoder() {
|
||||
return new HashTokenEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static AutowireHelper autowireHelper() {
|
||||
return AutowireHelper.getInstance();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService provideUserDetailsService() {
|
||||
return new SteleksUsersDetailsService();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ public class User {
|
|||
@NotNull
|
||||
private Timestamp registrationDate;
|
||||
@NotNull
|
||||
@Column(unique = true)
|
||||
private String email;
|
||||
@NotNull
|
||||
private String contactNumber;
|
||||
|
@ -40,6 +41,7 @@ public class User {
|
|||
private String password;
|
||||
|
||||
@NotNull
|
||||
@Column(unique = true)
|
||||
private String username;
|
||||
|
||||
private String profilePictureUrl;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
package ba.steleks.security;/**
|
||||
package ba.steleks.security;
|
||||
|
||||
/**
|
||||
* Created by ensar on 16/05/17.
|
||||
*/
|
||||
|
||||
|
@ -8,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
|
@ -19,20 +22,19 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
|||
@ComponentScan("org.baeldung.security")
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService provideUserDetailsService() {
|
||||
return new SteleksUsersDetailsService();
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
private final UsersJpaRepository usersJpaRepository;
|
||||
|
||||
@Autowired
|
||||
public SecurityConfig(UserDetailsService userDetailsService, TokenStore tokenStore, UsersJpaRepository usersJpaRepository) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.tokenStore = tokenStore;
|
||||
this.usersJpaRepository = usersJpaRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Autowired
|
||||
private TokenStore tokenStore;
|
||||
|
||||
@Autowired
|
||||
private UsersJpaRepository usersJpaRepository;
|
||||
|
||||
@Override
|
||||
protected void configure(
|
||||
AuthenticationManagerBuilder auth) throws Exception {
|
||||
|
@ -43,6 +45,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable().authorizeRequests()
|
||||
.antMatchers("/accesstoken", "/accesstoken/**", "/").permitAll()
|
||||
.antMatchers(HttpMethod.POST, "/users").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.addFilterBefore(new AuthenticationFilter(tokenStore, usersJpaRepository), CustomUrlUsernamePasswordAuthenticationFilter.class);
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package ba.steleks.security;
|
||||
|
||||
import ba.steleks.AutowireHelper;
|
||||
import ba.steleks.model.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
|
||||
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.persistence.PreUpdate;
|
||||
|
@ -11,6 +15,7 @@ import javax.persistence.PreUpdate;
|
|||
* Created by ensar on 30/05/17.
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class UserPasswordEntityListener {
|
||||
|
||||
@Autowired
|
||||
|
@ -19,6 +24,7 @@ public class UserPasswordEntityListener {
|
|||
@PrePersist
|
||||
@PreUpdate
|
||||
public void onUserUpdate(User user) {
|
||||
AutowireHelper.autowire(this, passwordEncoder);
|
||||
if(user.getPassword() != null) {
|
||||
user.setPasswordHash(passwordEncoder.encode(user.getPassword()));
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package ba.steleks.security.token;
|
||||
|
||||
import ba.steleks.model.UserRole;
|
||||
import ba.steleks.storage.store.KeyValueStore;
|
||||
import ba.steleks.util.CalendarUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
@ -21,17 +19,54 @@ public class BasicInMemoryTokenStore implements TokenStore {
|
|||
TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
|
||||
|
||||
private static KeyValueStore<String, TokenInfo> tokenStore;
|
||||
private TokenEncoder tokenEncoder;
|
||||
private long ttl = DEFAULT_TTL;
|
||||
|
||||
@Autowired
|
||||
public BasicInMemoryTokenStore(KeyValueStore<String, TokenInfo> tokenStore) {
|
||||
public BasicInMemoryTokenStore(KeyValueStore<String, TokenInfo> tokenStore, TokenEncoder tokenEncoder) {
|
||||
if(BasicInMemoryTokenStore.tokenStore == null) {
|
||||
BasicInMemoryTokenStore.tokenStore = tokenStore;
|
||||
}
|
||||
this.tokenEncoder = tokenEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidToken(String token) {
|
||||
return validateToken(encodeToken(token));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTokenInfo(String token) {
|
||||
token = encodeToken(token);
|
||||
System.out.println("token = " + token);
|
||||
if (validateToken(token)) {
|
||||
// Find token in store
|
||||
TokenInfo basicToken = tokenStore.get(token);
|
||||
|
||||
return basicToken.userId;
|
||||
} else {
|
||||
// No token in store
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToken(Long id, String tokenKey) {
|
||||
tokenKey = encodeToken(tokenKey);
|
||||
System.out.println("id = [" + id + "], tokenKey = [" + tokenKey + "]");
|
||||
TokenInfo tokenInfo = new TokenInfo();
|
||||
tokenInfo.userId = id;
|
||||
tokenInfo.saveTime = CalendarUtils.getUTCCalendar().getTimeInMillis();
|
||||
tokenStore.save(tokenKey, tokenInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeToken(String token) {
|
||||
token = encodeToken(token);
|
||||
tokenStore.remove(token);
|
||||
}
|
||||
|
||||
private boolean validateToken(String token) {
|
||||
if (tokenStore.contains(token)) {
|
||||
// Find token in store
|
||||
TokenInfo basicToken = tokenStore.get(token);
|
||||
|
@ -50,30 +85,8 @@ public class BasicInMemoryTokenStore implements TokenStore {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTokenInfo(String token) {
|
||||
if (isValidToken(token)) {
|
||||
// Find token in store
|
||||
TokenInfo basicToken = tokenStore.get(token);
|
||||
|
||||
return basicToken.userId;
|
||||
} else {
|
||||
// No token in store
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToken(Long id, String token) {
|
||||
TokenInfo tokenInfo = new TokenInfo();
|
||||
tokenInfo.userId = id;
|
||||
tokenInfo.saveTime = CalendarUtils.getUTCCalendar().getTimeInMillis();
|
||||
tokenStore.save(token, tokenInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeToken(String token) {
|
||||
tokenStore.remove(token);
|
||||
private String encodeToken(String token) {
|
||||
return tokenEncoder.encodeToken(token);
|
||||
}
|
||||
|
||||
private static class TokenInfo {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package ba.steleks.security.token;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
public class HashTokenEncoder implements TokenEncoder {
|
||||
|
||||
@Override
|
||||
public String encodeToken(String token) {
|
||||
return DigestUtils.sha256Hex(token);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package ba.steleks.security.token;
|
||||
|
||||
public interface TokenEncoder {
|
||||
|
||||
String encodeToken(String token);
|
||||
|
||||
}
|
|
@ -13,7 +13,7 @@ public interface TokenStore {
|
|||
|
||||
boolean isValidToken(String token);
|
||||
|
||||
void saveToken(Long id, String token);
|
||||
void saveToken(Long id, String tokenKey);
|
||||
|
||||
void removeToken(String token);
|
||||
|
||||
|
|
Reference in New Issue