Add basic token based authentication to users module
parent
18684754ac
commit
281930e3a0
|
@ -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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<UserRole> 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<UserRole> 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;
|
||||
|
|
|
@ -27,6 +27,13 @@ public class UserRoleFactory {
|
|||
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) {
|
||||
return userRoleSet
|
||||
.stream()
|
||||
|
|
|
@ -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<Long, BasicToken> tokenStore;
|
||||
private KeyValueStore<String, TokenInfo> tokenStore;
|
||||
private long ttl = DEFAULT_TTL;
|
||||
|
||||
@Autowired
|
||||
public BasicTokenStore(KeyValueStore<Long, BasicToken> tokenStore) {
|
||||
public BasicTokenStore(KeyValueStore<String, TokenInfo> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Reference in New Issue