Add basic token based authentication to users module

master
esensar 2017-06-01 21:49:14 +02:00
parent 18684754ac
commit 281930e3a0
8 changed files with 114 additions and 124 deletions

View File

@ -5,15 +5,13 @@ import ba.steleks.model.AuthRequest;
import ba.steleks.model.User; import ba.steleks.model.User;
import ba.steleks.repository.UsersJpaRepository; import ba.steleks.repository.UsersJpaRepository;
import ba.steleks.security.SessionIdentifierGenerator; import ba.steleks.security.SessionIdentifierGenerator;
import ba.steleks.security.UserRoleFactory;
import ba.steleks.security.token.TokenStore; import ba.steleks.security.token.TokenStore;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -62,4 +60,31 @@ public class AuthenticationController {
"Invalid password!"); "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<String, Object> 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();
}
}
} }

View File

@ -2,6 +2,9 @@ package ba.steleks.security;/**
* Created by ensar on 28/05/17. * 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.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.GenericFilterBean;
@ -13,12 +16,24 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException; 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 @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException { throws IOException, ServletException {
Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request); Authentication authentication = TokenAuthenticationService.getAuthentication(
(HttpServletRequest) request,
tokenStore,
usersJpaRepository
);
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@ -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<UserRole> userRoles = UserRoleFactory.fromGrantedAuthorities(authResult.getAuthorities());
TokenAuthenticationService.addAuthenticationHeader(response, authResult.getName(), userRoles);
}
}

View File

@ -2,6 +2,8 @@ package ba.steleks.security;/**
* Created by ensar on 16/05/17. * 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.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;
@ -25,6 +27,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
private UserDetailsService userDetailsService; 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 {
@ -37,9 +45,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/accesstoken").permitAll() .antMatchers("/accesstoken").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
.and() .and()
// .addFilterBefore(new JWTLoginFilter("/accesstoken", authenticationManager()), .addFilterBefore(new AuthenticationFilter(tokenStore, usersJpaRepository), CustomUrlUsernamePasswordAuthenticationFilter.class);
// CustomUrlUsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), CustomUrlUsernamePasswordAuthenticationFilter.class);
} }
} }

View File

@ -1,16 +1,12 @@
package ba.steleks.security; package ba.steleks.security;
import ba.steleks.model.UserRole; import ba.steleks.model.User;
import io.jsonwebtoken.Claims; import ba.steleks.repository.UsersJpaRepository;
import io.jsonwebtoken.Jwts; import ba.steleks.security.token.TokenStore;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest; 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. * Created by ensar on 28/05/17.
@ -19,53 +15,22 @@ import java.util.Set;
public class TokenAuthenticationService { 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 HEADER_STRING = "Authorization";
static final String ROLES = "roles";
public static void addAuthenticationHeader(HttpServletResponse res, String username, Set<UserRole> userRoleSet) { public static Authentication getAuthentication(HttpServletRequest request,
String JWT = Jwts.builder() TokenStore tokenStore,
.setSubject(username) UsersJpaRepository usersJpaRepository) {
.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) {
String token = request.getHeader(HEADER_STRING); String token = request.getHeader(HEADER_STRING);
if (token != null) { if (token != null && tokenStore.isValidToken(token)) {
// parse the token. Long userId = tokenStore.getTokenInfo(token);
Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replaceFirst(TOKEN_PREFIX, ""))
.getBody();
String userName = claims.getSubject(); User user = usersJpaRepository.findOne(userId);
Set<UserRole> userRoles = UserRoleFactory.fromString(claims.get(ROLES, String.class)); if(user != null) {
return new UsernamePasswordAuthenticationToken(user.getUsername(), null,
if (userName != null) { UserRoleFactory.toGrantedAuthorities(user.getUserRoles()));
boolean access = false; } else {
return null;
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);
}
} }
} }
return null; return null;

View File

@ -27,6 +27,13 @@ public class UserRoleFactory {
return userRoles; return userRoles;
} }
public static Set<String> toStringSet(Set<UserRole> userRoleSet) {
return userRoleSet
.stream()
.map(UserRole::getRoleName)
.collect(Collectors.toSet());
}
public static List<GrantedAuthority> toGrantedAuthorities(Collection<UserRole> userRoleSet) { public static List<GrantedAuthority> toGrantedAuthorities(Collection<UserRole> userRoleSet) {
return userRoleSet return userRoleSet
.stream() .stream()

View File

@ -1,10 +1,12 @@
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;
/** /**
@ -18,24 +20,19 @@ public class BasicTokenStore implements TokenStore {
public static final long DEFAULT_TTL = public static final long DEFAULT_TTL =
TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
private KeyValueStore<Long, BasicToken> tokenStore; private KeyValueStore<String, TokenInfo> tokenStore;
private long ttl = DEFAULT_TTL; private long ttl = DEFAULT_TTL;
@Autowired @Autowired
public BasicTokenStore(KeyValueStore<Long, BasicToken> tokenStore) { public BasicTokenStore(KeyValueStore<String, TokenInfo> tokenStore) {
this.tokenStore = tokenStore; this.tokenStore = tokenStore;
} }
@Override @Override
public boolean isValidToken(Long id, String token) { public boolean isValidToken(String token) {
if (tokenStore.contains(id)) { if (tokenStore.contains(token)) {
// Find token in store // Find token in store
BasicToken basicToken = tokenStore.get(id); TokenInfo basicToken = tokenStore.get(token);
// Token is invalid, there is different token saved in store
if (!basicToken.token.equals(token)) {
return false;
}
// Token is invalid, it has expired // Token is invalid, it has expired
if(basicToken.saveTime + ttl < CalendarUtils.getUTCCalendar().getTimeInMillis()) { if(basicToken.saveTime + ttl < CalendarUtils.getUTCCalendar().getTimeInMillis()) {
@ -45,26 +42,39 @@ public class BasicTokenStore implements TokenStore {
// Token valid! // Token valid!
return true; return true;
} else { } else {
// No id in store, there is no token // No token in store
return false; return false;
} }
} }
@Override @Override
public void saveToken(Long id, String token) { public Long getTokenInfo(String token) {
BasicToken basicToken = new BasicToken(); if (isValidToken(token)) {
basicToken.token = token; // Find token in store
basicToken.saveTime = CalendarUtils.getUTCCalendar().getTimeInMillis(); TokenInfo basicToken = tokenStore.get(token);
tokenStore.save(id, basicToken);
return basicToken.userId;
} else {
// No token in store
return null;
}
} }
@Override @Override
public void removeToken(Long id, String token) { public void saveToken(Long id, String token) {
tokenStore.remove(id); TokenInfo tokenInfo = new TokenInfo();
tokenInfo.userId = id;
tokenInfo.saveTime = CalendarUtils.getUTCCalendar().getTimeInMillis();
tokenStore.save(token, tokenInfo);
} }
private static class BasicToken { @Override
String token; public void removeToken(String token) {
tokenStore.remove(token);
}
private static class TokenInfo {
Long userId;
Long saveTime; Long saveTime;
} }
} }

View File

@ -5,10 +5,16 @@ package ba.steleks.security.token;
*/ */
public interface TokenStore { 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 saveToken(Long id, String token);
void removeToken(Long id, String token); void removeToken(String token);
} }