From 281930e3a07642bc403d8b85f5e682c7f5959692 Mon Sep 17 00:00:00 2001 From: esensar Date: Thu, 1 Jun 2017 21:49:14 +0200 Subject: [PATCH] Add basic token based authentication to users module --- .../controller/AuthenticationController.java | 33 ++++++++-- ...nFilter.java => AuthenticationFilter.java} | 19 +++++- .../ba/steleks/security/JWTLoginFilter.java | 44 ------------- .../ba/steleks/security/SecurityConfig.java | 12 +++- .../security/TokenAuthenticationService.java | 63 +++++-------------- .../ba/steleks/security/UserRoleFactory.java | 7 +++ .../security/token/BasicTokenStore.java | 50 +++++++++------ .../ba/steleks/security/token/TokenStore.java | 10 ++- 8 files changed, 114 insertions(+), 124 deletions(-) rename users/src/main/java/ba/steleks/security/{JWTAuthenticationFilter.java => AuthenticationFilter.java} (56%) delete mode 100644 users/src/main/java/ba/steleks/security/JWTLoginFilter.java diff --git a/users/src/main/java/ba/steleks/controller/AuthenticationController.java b/users/src/main/java/ba/steleks/controller/AuthenticationController.java index 4b4d94d..30b6c50 100644 --- a/users/src/main/java/ba/steleks/controller/AuthenticationController.java +++ b/users/src/main/java/ba/steleks/controller/AuthenticationController.java @@ -5,15 +5,13 @@ import ba.steleks.model.AuthRequest; import ba.steleks.model.User; import ba.steleks.repository.UsersJpaRepository; import ba.steleks.security.SessionIdentifierGenerator; +import ba.steleks.security.UserRoleFactory; import ba.steleks.security.token.TokenStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @@ -62,4 +60,31 @@ public class AuthenticationController { "Invalid password!"); } } + + @RequestMapping(path = "/accesstoken/{token}", method = RequestMethod.GET) + public ResponseEntity validateToken(@PathVariable String token) { + if (tokenStore.isValidToken(token)) { + Long userId = tokenStore.getTokenInfo(token); + + User user = usersJpaRepository.findOne(userId); + if(user != null) { + Map response = new HashMap<>(); + response.put("userId", String.valueOf(userId)); + response.put("roles", + UserRoleFactory.toStringSet(user.getUserRoles()) + ); + return ResponseEntity + .ok() + .body(response); + } else { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .build(); + } + } else { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .build(); + } + } } diff --git a/users/src/main/java/ba/steleks/security/JWTAuthenticationFilter.java b/users/src/main/java/ba/steleks/security/AuthenticationFilter.java similarity index 56% rename from users/src/main/java/ba/steleks/security/JWTAuthenticationFilter.java rename to users/src/main/java/ba/steleks/security/AuthenticationFilter.java index 19eb439..854c5a2 100644 --- a/users/src/main/java/ba/steleks/security/JWTAuthenticationFilter.java +++ b/users/src/main/java/ba/steleks/security/AuthenticationFilter.java @@ -2,6 +2,9 @@ package ba.steleks.security;/** * Created by ensar on 28/05/17. */ +import ba.steleks.repository.UsersJpaRepository; +import ba.steleks.security.token.TokenStore; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.GenericFilterBean; @@ -13,12 +16,24 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -public class JWTAuthenticationFilter extends GenericFilterBean { +public class AuthenticationFilter extends GenericFilterBean { + + private TokenStore tokenStore; + private UsersJpaRepository usersJpaRepository; + + public AuthenticationFilter(TokenStore tokenStore, UsersJpaRepository usersJpaRepository) { + this.tokenStore = tokenStore; + this.usersJpaRepository = usersJpaRepository; + } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request); + Authentication authentication = TokenAuthenticationService.getAuthentication( + (HttpServletRequest) request, + tokenStore, + usersJpaRepository + ); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); diff --git a/users/src/main/java/ba/steleks/security/JWTLoginFilter.java b/users/src/main/java/ba/steleks/security/JWTLoginFilter.java deleted file mode 100644 index f1981fd..0000000 --- a/users/src/main/java/ba/steleks/security/JWTLoginFilter.java +++ /dev/null @@ -1,44 +0,0 @@ -package ba.steleks.security;/** - * Created by ensar on 28/05/17. - */ - -import ba.steleks.model.UserRole; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import java.io.IOException; -import java.util.Collections; -import java.util.Set; - -public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter { - - public JWTLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) { - super(defaultFilterProcessesUrl); - setAuthenticationManager(authenticationManager); - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { - AccountCredentials creds = new ObjectMapper().readValue(request.getInputStream(), AccountCredentials.class); - return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), - creds.getPassword(), Collections.emptyList())); - } - - @Override - protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { - super.successfulAuthentication(request, response, chain, authResult); - Set userRoles = UserRoleFactory.fromGrantedAuthorities(authResult.getAuthorities()); - - TokenAuthenticationService.addAuthenticationHeader(response, authResult.getName(), userRoles); - - } -} diff --git a/users/src/main/java/ba/steleks/security/SecurityConfig.java b/users/src/main/java/ba/steleks/security/SecurityConfig.java index 282f1bf..9dd9789 100644 --- a/users/src/main/java/ba/steleks/security/SecurityConfig.java +++ b/users/src/main/java/ba/steleks/security/SecurityConfig.java @@ -2,6 +2,8 @@ package ba.steleks.security;/** * Created by ensar on 16/05/17. */ +import ba.steleks.repository.UsersJpaRepository; +import ba.steleks.security.token.TokenStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -25,6 +27,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; + @Autowired + private TokenStore tokenStore; + + @Autowired + private UsersJpaRepository usersJpaRepository; + @Override protected void configure( AuthenticationManagerBuilder auth) throws Exception { @@ -37,9 +45,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers("/accesstoken").permitAll() .anyRequest().authenticated() .and() -// .addFilterBefore(new JWTLoginFilter("/accesstoken", authenticationManager()), -// CustomUrlUsernamePasswordAuthenticationFilter.class) - .addFilterBefore(new JWTAuthenticationFilter(), CustomUrlUsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new AuthenticationFilter(tokenStore, usersJpaRepository), CustomUrlUsernamePasswordAuthenticationFilter.class); } } \ No newline at end of file diff --git a/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java b/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java index 5e09332..d457448 100644 --- a/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java +++ b/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java @@ -1,16 +1,12 @@ package ba.steleks.security; -import ba.steleks.model.UserRole; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import ba.steleks.model.User; +import ba.steleks.repository.UsersJpaRepository; +import ba.steleks.security.token.TokenStore; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.sql.Date; -import java.util.Set; /** * Created by ensar on 28/05/17. @@ -19,53 +15,22 @@ import java.util.Set; public class TokenAuthenticationService { - static final long EXPIRATION_TIME = 864_000_000; // 10 days - static final String SECRET = "ASteleksSecret"; - static final String TOKEN_PREFIX = "Bearer"; static final String HEADER_STRING = "Authorization"; - static final String ROLES = "roles"; - public static void addAuthenticationHeader(HttpServletResponse res, String username, Set userRoleSet) { - String JWT = Jwts.builder() - .setSubject(username) - .claim(ROLES, UserRoleFactory.toString(userRoleSet)) - .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) - .signWith(SignatureAlgorithm.HS512, SECRET).compact(); - - res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT); - } - - public static Authentication getAuthentication(HttpServletRequest request) { + public static Authentication getAuthentication(HttpServletRequest request, + TokenStore tokenStore, + UsersJpaRepository usersJpaRepository) { String token = request.getHeader(HEADER_STRING); - if (token != null) { - // parse the token. - Claims claims = Jwts.parser() - .setSigningKey(SECRET) - .parseClaimsJws(token.replaceFirst(TOKEN_PREFIX, "")) - .getBody(); + if (token != null && tokenStore.isValidToken(token)) { + Long userId = tokenStore.getTokenInfo(token); - String userName = claims.getSubject(); - Set userRoles = UserRoleFactory.fromString(claims.get(ROLES, String.class)); - - if (userName != null) { - boolean access = false; - - UserRole theUserRole = new UserRole("theUser"); - - if(userRoles.contains(theUserRole)) { - access = true; - } - - if (access) { - return new UsernamePasswordAuthenticationToken(userName, - null, - UserRoleFactory.toGrantedAuthorities(userRoles)); - } - else { - return new UsernamePasswordAuthenticationToken(userName, - null); - } + User user = usersJpaRepository.findOne(userId); + if(user != null) { + return new UsernamePasswordAuthenticationToken(user.getUsername(), null, + UserRoleFactory.toGrantedAuthorities(user.getUserRoles())); + } else { + return null; } } return null; diff --git a/users/src/main/java/ba/steleks/security/UserRoleFactory.java b/users/src/main/java/ba/steleks/security/UserRoleFactory.java index 5e7868d..976ce44 100644 --- a/users/src/main/java/ba/steleks/security/UserRoleFactory.java +++ b/users/src/main/java/ba/steleks/security/UserRoleFactory.java @@ -27,6 +27,13 @@ public class UserRoleFactory { return userRoles; } + public static Set toStringSet(Set userRoleSet) { + return userRoleSet + .stream() + .map(UserRole::getRoleName) + .collect(Collectors.toSet()); + } + public static List toGrantedAuthorities(Collection userRoleSet) { return userRoleSet .stream() diff --git a/users/src/main/java/ba/steleks/security/token/BasicTokenStore.java b/users/src/main/java/ba/steleks/security/token/BasicTokenStore.java index 5608b19..0a47a3f 100644 --- a/users/src/main/java/ba/steleks/security/token/BasicTokenStore.java +++ b/users/src/main/java/ba/steleks/security/token/BasicTokenStore.java @@ -1,10 +1,12 @@ 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; /** @@ -18,24 +20,19 @@ public class BasicTokenStore implements TokenStore { public static final long DEFAULT_TTL = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); - private KeyValueStore tokenStore; + private KeyValueStore tokenStore; private long ttl = DEFAULT_TTL; @Autowired - public BasicTokenStore(KeyValueStore tokenStore) { + public BasicTokenStore(KeyValueStore tokenStore) { this.tokenStore = tokenStore; } @Override - public boolean isValidToken(Long id, String token) { - if (tokenStore.contains(id)) { + public boolean isValidToken(String token) { + if (tokenStore.contains(token)) { // Find token in store - BasicToken basicToken = tokenStore.get(id); - - // Token is invalid, there is different token saved in store - if (!basicToken.token.equals(token)) { - return false; - } + TokenInfo basicToken = tokenStore.get(token); // Token is invalid, it has expired if(basicToken.saveTime + ttl < CalendarUtils.getUTCCalendar().getTimeInMillis()) { @@ -45,26 +42,39 @@ public class BasicTokenStore implements TokenStore { // Token valid! return true; } else { - // No id in store, there is no token + // No token in store return false; } } @Override - public void saveToken(Long id, String token) { - BasicToken basicToken = new BasicToken(); - basicToken.token = token; - basicToken.saveTime = CalendarUtils.getUTCCalendar().getTimeInMillis(); - tokenStore.save(id, basicToken); + 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 removeToken(Long id, String token) { - tokenStore.remove(id); + public void saveToken(Long id, String token) { + TokenInfo tokenInfo = new TokenInfo(); + tokenInfo.userId = id; + tokenInfo.saveTime = CalendarUtils.getUTCCalendar().getTimeInMillis(); + tokenStore.save(token, tokenInfo); } - private static class BasicToken { - String token; + @Override + public void removeToken(String token) { + tokenStore.remove(token); + } + + private static class TokenInfo { + Long userId; Long saveTime; } } diff --git a/users/src/main/java/ba/steleks/security/token/TokenStore.java b/users/src/main/java/ba/steleks/security/token/TokenStore.java index d6e5702..eb9cd8c 100644 --- a/users/src/main/java/ba/steleks/security/token/TokenStore.java +++ b/users/src/main/java/ba/steleks/security/token/TokenStore.java @@ -5,10 +5,16 @@ package ba.steleks.security.token; */ public interface TokenStore { - boolean isValidToken(Long id, String token); + /** + * @param token token + * @return user id associated to the {@param token} or null if it does not exist + */ + Long getTokenInfo(String token); + + boolean isValidToken(String token); void saveToken(Long id, String token); - void removeToken(Long id, String token); + void removeToken(String token); }