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;
|
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}")
|
@RequestMapping("/service-instances/{applicationName}")
|
||||||
public List<ServiceInstance> serviceInstancesByApplicationName(
|
public List<ServiceInstance> serviceInstancesByApplicationName(
|
||||||
@PathVariable String applicationName) {
|
@PathVariable String applicationName) {
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package ba.steleks;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
@ -14,4 +18,19 @@ public class UsersConfig {
|
||||||
public static PasswordEncoder providePasswordEncoder() {
|
public static PasswordEncoder providePasswordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
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
|
@NotNull
|
||||||
private Timestamp registrationDate;
|
private Timestamp registrationDate;
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Column(unique = true)
|
||||||
private String email;
|
private String email;
|
||||||
@NotNull
|
@NotNull
|
||||||
private String contactNumber;
|
private String contactNumber;
|
||||||
|
@ -40,6 +41,7 @@ public class User {
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Column(unique = true)
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
private String profilePictureUrl;
|
private String profilePictureUrl;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
package ba.steleks.security;/**
|
package ba.steleks.security;
|
||||||
|
|
||||||
|
/**
|
||||||
* Created by ensar on 16/05/17.
|
* 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.Bean;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
@ -19,20 +22,19 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
@ComponentScan("org.baeldung.security")
|
@ComponentScan("org.baeldung.security")
|
||||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
@Bean
|
private final UserDetailsService userDetailsService;
|
||||||
public UserDetailsService provideUserDetailsService() {
|
|
||||||
return new SteleksUsersDetailsService();
|
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
|
@Override
|
||||||
protected void configure(
|
protected void configure(
|
||||||
AuthenticationManagerBuilder auth) throws Exception {
|
AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
@ -43,6 +45,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http.csrf().disable().authorizeRequests()
|
http.csrf().disable().authorizeRequests()
|
||||||
.antMatchers("/accesstoken", "/accesstoken/**", "/").permitAll()
|
.antMatchers("/accesstoken", "/accesstoken/**", "/").permitAll()
|
||||||
|
.antMatchers(HttpMethod.POST, "/users").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
.addFilterBefore(new AuthenticationFilter(tokenStore, usersJpaRepository), CustomUrlUsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(new AuthenticationFilter(tokenStore, usersJpaRepository), CustomUrlUsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package ba.steleks.security;
|
package ba.steleks.security;
|
||||||
|
|
||||||
|
import ba.steleks.AutowireHelper;
|
||||||
import ba.steleks.model.User;
|
import ba.steleks.model.User;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
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.PrePersist;
|
||||||
import javax.persistence.PreUpdate;
|
import javax.persistence.PreUpdate;
|
||||||
|
@ -11,6 +15,7 @@ import javax.persistence.PreUpdate;
|
||||||
* Created by ensar on 30/05/17.
|
* Created by ensar on 30/05/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@Component
|
||||||
public class UserPasswordEntityListener {
|
public class UserPasswordEntityListener {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -19,6 +24,7 @@ public class UserPasswordEntityListener {
|
||||||
@PrePersist
|
@PrePersist
|
||||||
@PreUpdate
|
@PreUpdate
|
||||||
public void onUserUpdate(User user) {
|
public void onUserUpdate(User user) {
|
||||||
|
AutowireHelper.autowire(this, passwordEncoder);
|
||||||
if(user.getPassword() != null) {
|
if(user.getPassword() != null) {
|
||||||
user.setPasswordHash(passwordEncoder.encode(user.getPassword()));
|
user.setPasswordHash(passwordEncoder.encode(user.getPassword()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package ba.steleks.security.token;
|
package ba.steleks.security.token;
|
||||||
|
|
||||||
import ba.steleks.model.UserRole;
|
|
||||||
import ba.steleks.storage.store.KeyValueStore;
|
import ba.steleks.storage.store.KeyValueStore;
|
||||||
import ba.steleks.util.CalendarUtils;
|
import ba.steleks.util.CalendarUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,17 +19,54 @@ public class BasicInMemoryTokenStore implements TokenStore {
|
||||||
TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
|
TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
|
||||||
|
|
||||||
private static KeyValueStore<String, TokenInfo> tokenStore;
|
private static KeyValueStore<String, TokenInfo> tokenStore;
|
||||||
|
private TokenEncoder tokenEncoder;
|
||||||
private long ttl = DEFAULT_TTL;
|
private long ttl = DEFAULT_TTL;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public BasicInMemoryTokenStore(KeyValueStore<String, TokenInfo> tokenStore) {
|
public BasicInMemoryTokenStore(KeyValueStore<String, TokenInfo> tokenStore, TokenEncoder tokenEncoder) {
|
||||||
if(BasicInMemoryTokenStore.tokenStore == null) {
|
if(BasicInMemoryTokenStore.tokenStore == null) {
|
||||||
BasicInMemoryTokenStore.tokenStore = tokenStore;
|
BasicInMemoryTokenStore.tokenStore = tokenStore;
|
||||||
}
|
}
|
||||||
|
this.tokenEncoder = tokenEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidToken(String token) {
|
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)) {
|
if (tokenStore.contains(token)) {
|
||||||
// Find token in store
|
// Find token in store
|
||||||
TokenInfo basicToken = tokenStore.get(token);
|
TokenInfo basicToken = tokenStore.get(token);
|
||||||
|
@ -50,30 +85,8 @@ public class BasicInMemoryTokenStore implements TokenStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private String encodeToken(String token) {
|
||||||
public Long getTokenInfo(String token) {
|
return tokenEncoder.encodeToken(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 static class TokenInfo {
|
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);
|
boolean isValidToken(String token);
|
||||||
|
|
||||||
void saveToken(Long id, String token);
|
void saveToken(Long id, String tokenKey);
|
||||||
|
|
||||||
void removeToken(String token);
|
void removeToken(String token);
|
||||||
|
|
||||||
|
|
Reference in New Issue