diff --git a/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java b/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java index 985d3e7..9c271b2 100644 --- a/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java +++ b/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java @@ -1,11 +1,11 @@ package com.svlada.profile.endpoint; -import org.apache.commons.lang3.NotImplementedException; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.model.UserContext; /** @@ -18,7 +18,7 @@ import com.svlada.security.model.UserContext; @RestController public class ProfileEndpoint { @RequestMapping(value="/api/me", method=RequestMethod.GET) - public @ResponseBody UserContext get() { - throw new NotImplementedException("Not implemented"); + public @ResponseBody UserContext get(JwtAuthenticationToken token) { + return (UserContext) token.getPrincipal(); } } diff --git a/src/main/java/com/svlada/security/UserService.java b/src/main/java/com/svlada/security/UserService.java new file mode 100644 index 0000000..2aa78b2 --- /dev/null +++ b/src/main/java/com/svlada/security/UserService.java @@ -0,0 +1,14 @@ +package com.svlada.security; + +import com.svlada.security.model.UserContext; + +/** + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public interface UserService { + public UserContext getByUsername(String username); + public UserContext getByUsernameAndPassword(String username, String password); +} diff --git a/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java b/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java index 9d36e9a..7aec56c 100644 --- a/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java +++ b/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java @@ -5,8 +5,6 @@ import java.util.Collection; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; -import com.svlada.security.model.JwtToken; -import com.svlada.security.model.SafeJwtToken; import com.svlada.security.model.UnsafeJwtToken; import com.svlada.security.model.UserContext; @@ -21,9 +19,7 @@ import com.svlada.security.model.UserContext; public class JwtAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 2877954820905567501L; - private JwtToken safeToken; private UnsafeJwtToken unsafeToken; - private UserContext userContext; public JwtAuthenticationToken(UnsafeJwtToken unsafeToken) { @@ -32,9 +28,9 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken { this.setAuthenticated(false); } - public JwtAuthenticationToken(UserContext userContext, SafeJwtToken token, Collection authorities) { + public JwtAuthenticationToken(UserContext userContext, Collection authorities) { super(authorities); - this.safeToken = token; + this.unsafeToken = null; this.userContext = userContext; super.setAuthenticated(true); } @@ -50,7 +46,7 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken { @Override public Object getCredentials() { - return null; + return unsafeToken; } @Override @@ -58,16 +54,9 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken { return this.userContext; } - public JwtToken getSafeToken() { - return this.safeToken; - } - - public UnsafeJwtToken getUnsafeToken() { - return unsafeToken; - } - @Override - public void eraseCredentials() { + public void eraseCredentials() { super.eraseCredentials(); + this.unsafeToken = null; } } diff --git a/src/main/java/com/svlada/security/auth/RefreshTokenRequestMatcher.java b/src/main/java/com/svlada/security/auth/RefreshTokenRequestMatcher.java new file mode 100644 index 0000000..c0d1291 --- /dev/null +++ b/src/main/java/com/svlada/security/auth/RefreshTokenRequestMatcher.java @@ -0,0 +1,24 @@ +package com.svlada.security.auth; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.svlada.security.config.WebSecurityConfig; + +/** + * Skip processing of Refresh token URL endpoint. + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public class RefreshTokenRequestMatcher implements RequestMatcher { + private AntPathRequestMatcher matcher = new AntPathRequestMatcher(WebSecurityConfig.TOKEN_REFRESH_ENTRY_POINT); + + @Override + public boolean matches(HttpServletRequest request) { + return matcher.matches(request) ? false : true; + } +} diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java b/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java index 1a2150e..be1b3fc 100644 --- a/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java +++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java @@ -8,11 +8,8 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; import org.springframework.util.Assert; -import com.svlada.security.auth.JwtAuthenticationToken; -import com.svlada.security.model.JwtTokenFactory; -import com.svlada.security.model.SafeJwtToken; import com.svlada.security.model.UserContext; -import com.svlada.security.service.UserService; +import com.svlada.user.service.DatabaseUserService; /** * @@ -22,12 +19,11 @@ import com.svlada.security.service.UserService; */ @Component public class AjaxAuthenticationProvider implements AuthenticationProvider { - private final JwtTokenFactory tokenFactory; - private final UserService userService; + + private final DatabaseUserService userService; @Autowired - public AjaxAuthenticationProvider(final JwtTokenFactory tokenFactory, final UserService userService) { - this.tokenFactory = tokenFactory; + public AjaxAuthenticationProvider(final DatabaseUserService userService) { this.userService = userService; } @@ -38,11 +34,9 @@ public class AjaxAuthenticationProvider implements AuthenticationProvider { String username = (String) authentication.getPrincipal(); String password = (String) authentication.getCredentials(); - UserContext userContext = userService.loadUser(username, password); + UserContext userContext = userService.getByUsernameAndPassword(username, password); - SafeJwtToken safeJwtToken = tokenFactory.createSafeToken(userContext); - - return new JwtAuthenticationToken(userContext, safeJwtToken, userContext.getAuthorities()); + return new UsernamePasswordAuthenticationToken(userContext, null, userContext.getAuthorities()); } @Override diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java index 7babfbd..af5e530 100644 --- a/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java +++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java @@ -16,10 +16,12 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; -import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.model.JwtToken; +import com.svlada.security.model.JwtTokenFactory; +import com.svlada.security.model.UserContext; /** + * AjaxAwareAuthenticationSuccessHandler * * @author vladimir.stankovic * @@ -28,16 +30,21 @@ import com.svlada.security.model.JwtToken; @Component public class AjaxAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final ObjectMapper mapper; + private final JwtTokenFactory tokenFactory; @Autowired - public AjaxAwareAuthenticationSuccessHandler(ObjectMapper mapper) { + public AjaxAwareAuthenticationSuccessHandler(final ObjectMapper mapper, final JwtTokenFactory tokenFactory) { this.mapper = mapper; + this.tokenFactory = tokenFactory; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - JwtToken token = ((JwtAuthenticationToken) authentication).getSafeToken(); + UserContext userContext = (UserContext) authentication.getPrincipal(); + + JwtToken token = tokenFactory.createSafeToken(userContext); + JwtToken refreshToken = tokenFactory.createRefreshToken(userContext); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java b/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java index 84cec43..69ca4c3 100644 --- a/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java +++ b/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java @@ -53,6 +53,9 @@ public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingF public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (!HttpMethod.POST.name().equals(request.getMethod()) || !WebUtil.isAjax(request)) { + if(logger.isDebugEnabled()) { + logger.debug("Authentication method not supported. Request method: " + request.getMethod()); + } throw new AuthMethodNotSupportedException("Authentication method not supported"); } @@ -61,7 +64,7 @@ public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingF if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isBlank(loginRequest.getPassword())) { throw new AuthenticationServiceException("Username or Password not provided"); } - + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); return this.getAuthenticationManager().authenticate(token); diff --git a/src/main/java/com/svlada/security/auth/jwt/BloomFilterTokenVerifier.java b/src/main/java/com/svlada/security/auth/jwt/BloomFilterTokenVerifier.java new file mode 100644 index 0000000..0e58136 --- /dev/null +++ b/src/main/java/com/svlada/security/auth/jwt/BloomFilterTokenVerifier.java @@ -0,0 +1,15 @@ +package com.svlada.security.auth.jwt; + +/** + * BloomFilterTokenVerifier + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public class BloomFilterTokenVerifier implements TokenVerifier { + @Override + public boolean verify(String jti) { + return true; + } +} diff --git a/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java b/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java index 14ba1c4..4b216c6 100644 --- a/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java +++ b/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java @@ -6,14 +6,12 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; +import com.svlada.security.UserService; import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.config.JwtSettings; import com.svlada.security.model.JwtToken; -import com.svlada.security.model.JwtTokenFactory; -import com.svlada.security.model.SafeJwtToken; import com.svlada.security.model.UnsafeJwtToken; import com.svlada.security.model.UserContext; -import com.svlada.security.service.UserService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; @@ -30,27 +28,23 @@ import io.jsonwebtoken.Jws; public class JwtAuthenticationProvider implements AuthenticationProvider { private final UserService userService; private final JwtSettings jwtSettings; - private final JwtTokenFactory jwtTokenFactory; @Autowired - public JwtAuthenticationProvider(UserService userService, JwtSettings jwtSettings, JwtTokenFactory jwtTokenFactory) { + public JwtAuthenticationProvider(UserService userService, JwtSettings jwtSettings) { this.userService = userService; this.jwtSettings = jwtSettings; - this.jwtTokenFactory = jwtTokenFactory; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - UnsafeJwtToken unsafeToken = ((JwtAuthenticationToken) authentication).getUnsafeToken(); - + UnsafeJwtToken unsafeToken = (UnsafeJwtToken) authentication.getCredentials(); + Jws jwsClaims = unsafeToken.parseClaims(jwtSettings.getTokenSigningKey()); String subject = jwsClaims.getBody().getSubject(); - UserContext context = userService.loadUser(subject); - SafeJwtToken safeToken = jwtTokenFactory.createSafeToken(unsafeToken.getToken(), jwsClaims.getBody()); + UserContext context = userService.getByUsername(subject); - JwtAuthenticationToken authToken = new JwtAuthenticationToken(context, safeToken, context.getAuthorities()); - return authToken; + return new JwtAuthenticationToken(context, context.getAuthorities()); } @Override diff --git a/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java index 59d912b..45b3807 100644 --- a/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java +++ b/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -8,16 +8,17 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.auth.jwt.extractor.TokenExtractor; +import com.svlada.security.config.WebSecurityConfig; import com.svlada.security.model.JwtTokenFactory; import com.svlada.security.model.UnsafeJwtToken; @@ -36,9 +37,9 @@ public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticati @Autowired public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler, JwtTokenFactory tokenFactory, - @Qualifier("jwtHeaderTokenExtractor") TokenExtractor tokenExtractor, - String defaultFilterProcessesUrl) { - super(defaultFilterProcessesUrl); + TokenExtractor tokenExtractor, + RequestMatcher requestMatcher) { + super(requestMatcher); this.failureHandler = failureHandler; this.tokenExtractor = tokenExtractor; this.tokenFactory = tokenFactory; @@ -47,7 +48,7 @@ public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticati @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { - String tokenPayload = request.getHeader("X-Authorization"); + String tokenPayload = request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM); UnsafeJwtToken token = tokenFactory.createUnsafeToken(tokenExtractor.extract(tokenPayload)); return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token)); } diff --git a/src/main/java/com/svlada/security/auth/jwt/TokenVerifier.java b/src/main/java/com/svlada/security/auth/jwt/TokenVerifier.java new file mode 100644 index 0000000..1f8052f --- /dev/null +++ b/src/main/java/com/svlada/security/auth/jwt/TokenVerifier.java @@ -0,0 +1,11 @@ +package com.svlada.security.auth.jwt; + +/** + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public interface TokenVerifier { + public boolean verify(String jti); +} diff --git a/src/main/java/com/svlada/security/config/JwtSettings.java b/src/main/java/com/svlada/security/config/JwtSettings.java index de7d23a..3c3b3ea 100644 --- a/src/main/java/com/svlada/security/config/JwtSettings.java +++ b/src/main/java/com/svlada/security/config/JwtSettings.java @@ -24,14 +24,14 @@ public class JwtSettings { /** * {@link JwtToken} can be refreshed during this timeframe. */ - private Integer tokenValidationTimeframe; + private Integer refreshTokenExpTime; - public Integer getTokenValidationTimeframe() { - return tokenValidationTimeframe; + public Integer getRefreshTokenExpTime() { + return refreshTokenExpTime; } - public void setTokenValidationTimeframe(Integer tokenValidationTimeframe) { - this.tokenValidationTimeframe = tokenValidationTimeframe; + public void setRefreshTokenExpTime(Integer refreshTokenExpTime) { + this.refreshTokenExpTime = refreshTokenExpTime; } public Integer getTokenExpirationTime() { diff --git a/src/main/java/com/svlada/security/config/WebSecurityConfig.java b/src/main/java/com/svlada/security/config/WebSecurityConfig.java index 40fa2e4..f0f6dba 100644 --- a/src/main/java/com/svlada/security/config/WebSecurityConfig.java +++ b/src/main/java/com/svlada/security/config/WebSecurityConfig.java @@ -9,14 +9,20 @@ 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.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.fasterxml.jackson.databind.ObjectMapper; import com.svlada.security.RestAuthenticationEntryPoint; +import com.svlada.security.auth.RefreshTokenRequestMatcher; import com.svlada.security.auth.ajax.AjaxAuthenticationProvider; import com.svlada.security.auth.ajax.AjaxLoginProcessingFilter; +import com.svlada.security.auth.jwt.JwtAuthenticationProvider; +import com.svlada.security.auth.jwt.JwtTokenAuthenticationProcessingFilter; +import com.svlada.security.auth.jwt.extractor.TokenExtractor; +import com.svlada.security.model.JwtTokenFactory; /** * WebSecurityConfig @@ -28,13 +34,19 @@ import com.svlada.security.auth.ajax.AjaxLoginProcessingFilter; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; - + public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; + @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint; @Autowired private AuthenticationSuccessHandler successHandler; @Autowired private AuthenticationFailureHandler failureHandler; @Autowired private AjaxAuthenticationProvider ajaxAuthenticationProvider; + @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; + + @Autowired private TokenExtractor tokenExtractor; + @Autowired private JwtTokenFactory tokenFactory; @Autowired private AuthenticationManager authenticationManager; @@ -46,6 +58,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { filter.setAuthenticationManager(this.authenticationManager); return filter; } + + @Bean + protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { + JwtTokenAuthenticationProcessingFilter filter = new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenFactory, tokenExtractor, new RefreshTokenRequestMatcher()); + filter.setAuthenticationManager(this.authenticationManager); + return filter; + } @Bean @Override @@ -55,6 +74,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(ajaxAuthenticationProvider); + auth.authenticationProvider(jwtAuthenticationProvider); + } + + @Bean + protected BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); } @Override @@ -71,7 +96,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .and() .authorizeRequests() .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point + .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point .and() - .addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); } } diff --git a/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java b/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java new file mode 100644 index 0000000..b104e95 --- /dev/null +++ b/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java @@ -0,0 +1,60 @@ +package com.svlada.security.endpoint; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.svlada.security.UserService; +import com.svlada.security.auth.jwt.TokenVerifier; +import com.svlada.security.config.JwtSettings; +import com.svlada.security.config.WebSecurityConfig; +import com.svlada.security.exceptions.InvalidJwtToken; +import com.svlada.security.model.JwtToken; +import com.svlada.security.model.JwtTokenFactory; +import com.svlada.security.model.UnsafeJwtToken; +import com.svlada.security.model.UserContext; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; + +/** + * RefreshTokenEndpoint + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +@RestController +public class RefreshTokenEndpoint { + @Autowired private JwtTokenFactory tokenFactory; + @Autowired private JwtSettings jwtSettings; + @Autowired private UserService userService; + @Autowired private TokenVerifier tokenVerifier; + + @RequestMapping(value="/api/auth/token", method=RequestMethod.GET, produces={ MediaType.APPLICATION_JSON_VALUE }) + public @ResponseBody JwtToken refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + UnsafeJwtToken unsafeToken = this.tokenFactory.createUnsafeToken(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM)); + + Jws jwsClaims = unsafeToken.parseClaims(jwtSettings.getTokenSigningKey()); + + String subject = jwsClaims.getBody().getSubject(); + String jti = jwsClaims.getBody().getId(); + + if (!tokenVerifier.verify(jti)) { + throw new InvalidJwtToken(); + } + + UserContext userContext = userService.getByUsername(subject); + + return tokenFactory.createSafeToken(userContext); + } +} diff --git a/src/main/java/com/svlada/security/exceptions/InvalidJwtToken.java b/src/main/java/com/svlada/security/exceptions/InvalidJwtToken.java new file mode 100644 index 0000000..f896c57 --- /dev/null +++ b/src/main/java/com/svlada/security/exceptions/InvalidJwtToken.java @@ -0,0 +1,12 @@ +package com.svlada.security.exceptions; + +/** + * JwtTokenNotValid + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public class InvalidJwtToken extends RuntimeException { + private static final long serialVersionUID = -294671188037098603L; +} diff --git a/src/main/java/com/svlada/security/model/JwtTokenFactory.java b/src/main/java/com/svlada/security/model/JwtTokenFactory.java index 5b2afcf..1098ea7 100644 --- a/src/main/java/com/svlada/security/model/JwtTokenFactory.java +++ b/src/main/java/com/svlada/security/model/JwtTokenFactory.java @@ -1,5 +1,7 @@ package com.svlada.security.model; +import java.util.UUID; + import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; @@ -53,7 +55,7 @@ public class JwtTokenFactory { return new SafeJwtToken(token, claims); } - + public SafeJwtToken refreshToken(SafeJwtToken safeJwtToken) { return null; } @@ -74,4 +76,25 @@ public class JwtTokenFactory { public UnsafeJwtToken createUnsafeToken(String tokenPayload) { return new UnsafeJwtToken(tokenPayload); } + + public JwtToken createRefreshToken(UserContext userContext) { + if (StringUtils.isBlank(userContext.getUsername())) { + throw new IllegalArgumentException("Cannot create JWT Token without username"); + } + + DateTime currentTime = new DateTime(); + + Claims claims = Jwts.claims().setSubject(userContext.getUsername()); + + String token = Jwts.builder() + .setClaims(claims) + .setIssuer(settings.getTokenIssuer()) + .setId(UUID.randomUUID().toString()) + .setIssuedAt(currentTime.toDate()) + .setExpiration(currentTime.plusMinutes(settings.getRefreshTokenExpTime()).toDate()) + .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()) + .compact(); + + return new SafeJwtToken(token, claims); + } } diff --git a/src/main/java/com/svlada/security/model/UnsafeJwtToken.java b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java index 44bf147..9ee446c 100644 --- a/src/main/java/com/svlada/security/model/UnsafeJwtToken.java +++ b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java @@ -1,5 +1,7 @@ package com.svlada.security.model; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.BadCredentialsException; import com.svlada.security.exceptions.JwtExpiredTokenException; @@ -13,6 +15,8 @@ import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; public class UnsafeJwtToken implements JwtToken { + private static Logger logger = LoggerFactory.getLogger(UnsafeJwtToken.class); + private String token; public UnsafeJwtToken(String token) { @@ -30,8 +34,10 @@ public class UnsafeJwtToken implements JwtToken { try { return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { + logger.error("Invalid JWT Token", ex); throw new BadCredentialsException("Invalid JWT token: ", ex); } catch (ExpiredJwtException expiredEx) { + logger.info("JWT Token is expired", expiredEx); throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); } } diff --git a/src/main/java/com/svlada/security/repository/UserRepository.java b/src/main/java/com/svlada/user/repository/UserRepository.java similarity index 86% rename from src/main/java/com/svlada/security/repository/UserRepository.java rename to src/main/java/com/svlada/user/repository/UserRepository.java index 13e84df..389204a 100644 --- a/src/main/java/com/svlada/security/repository/UserRepository.java +++ b/src/main/java/com/svlada/user/repository/UserRepository.java @@ -1,4 +1,4 @@ -package com.svlada.security.repository; +package com.svlada.user.repository; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/svlada/security/service/UserService.java b/src/main/java/com/svlada/user/service/DatabaseUserService.java similarity index 73% rename from src/main/java/com/svlada/security/service/UserService.java rename to src/main/java/com/svlada/user/service/DatabaseUserService.java index bf1c5d6..fa4fdd5 100644 --- a/src/main/java/com/svlada/security/service/UserService.java +++ b/src/main/java/com/svlada/user/service/DatabaseUserService.java @@ -1,4 +1,4 @@ -package com.svlada.security.service; +package com.svlada.user.service; import java.util.ArrayList; import java.util.List; @@ -8,9 +8,10 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; +import com.svlada.security.UserService; import com.svlada.security.model.UserContext; import com.svlada.security.model.UserRole; -import com.svlada.security.repository.UserRepository; +import com.svlada.user.repository.UserRepository; /** * Mock implementation. @@ -20,21 +21,23 @@ import com.svlada.security.repository.UserRepository; * Aug 4, 2016 */ @Service -public class UserService { +public class DatabaseUserService implements UserService { private final UserRepository userRepository; @Autowired - public UserService(UserRepository userRepository) { + public DatabaseUserService(UserRepository userRepository) { this.userRepository = userRepository; } - public UserContext loadUser(String username, String password) { + @Override + public UserContext getByUsernameAndPassword(String username, String password) { List authorities = new ArrayList(); authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority())); return new UserContext(username, authorities); } - public UserContext loadUser(String username) { + @Override + public UserContext getByUsername(String username) { List authorities = new ArrayList(); authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority())); return new UserContext(username, authorities);