From 717c3e35cc0c3aa1d054021faa444d14442251cb Mon Sep 17 00:00:00 2001 From: svlada Date: Tue, 16 Aug 2016 17:17:56 +0200 Subject: [PATCH] Added H2 in memory as a sample db. --- etc/blog.md | 27 +++++++- pom.xml | 11 ++- .../svlada/common/InMemoryDatabaseConfig.java | 16 +++++ src/main/java/com/svlada/entity/Role.java | 11 +++ src/main/java/com/svlada/entity/User.java | 51 ++++++++++++++ .../security/auth/JwtAuthenticationToken.java | 3 +- .../auth/ajax/AjaxAuthenticationProvider.java | 2 +- .../auth/jwt/JwtAuthenticationProvider.java | 48 ++++--------- ...wtTokenAuthenticationProcessingFilter.java | 1 - .../auth/jwt/RefreshTokenAuthStrategy.java | 69 ------------------- .../security/auth/jwt/TokenAuthStrategy.java | 14 ---- .../security/model/JwtTokenFactory.java | 10 ++- .../svlada/security/model/UnsafeJwtToken.java | 23 ++++++- .../security/repository/UserRepository.java | 16 +++++ .../svlada/security/service/UserService.java | 13 ++++ src/main/resources/application.yml | 12 +++- src/main/resources/data.sql | 1 + 17 files changed, 191 insertions(+), 137 deletions(-) create mode 100644 src/main/java/com/svlada/common/InMemoryDatabaseConfig.java create mode 100644 src/main/java/com/svlada/entity/Role.java create mode 100644 src/main/java/com/svlada/entity/User.java delete mode 100644 src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java delete mode 100644 src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java create mode 100644 src/main/java/com/svlada/security/repository/UserRepository.java create mode 100644 src/main/resources/data.sql diff --git a/etc/blog.md b/etc/blog.md index a1488cd..03a50b5 100644 --- a/etc/blog.md +++ b/etc/blog.md @@ -474,8 +474,29 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - - ## References -[Spring Security Architecture - Dave Syer](https://github.com/dsyer/spring-security-architecture) \ No newline at end of file +### [Spring Security Architecture - Dave Syer](https://github.com/dsyer/spring-security-architecture) + +### [](http://stackoverflow.com/questions/21978658/invalidating-json-web-tokens/36884683#36884683) + +### [](http://stackoverflow.com/questions/38557379/secure-and-stateless-jwt-implementation) + +http://stackoverflow.com/questions/3487991/why-does-oauth-v2-have-both-access-and-refresh-tokens/12885823 + +https://tools.ietf.org/html/rfc6749#section-1.4 + +Keep user identity in the JWT but not user roles. + +Loosing a JWT token is like loosing your house keys. + +https://www.dinochiesa.net/?p=1388 + +http://by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html + +true statelessness and revocation are mutually exclusive + + +https://www.sslvpn.online/are-breaches-of-jwt-based-servers-more-damaging/ + +http://nordicapis.com/how-to-control-user-identity-within-microservices/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7a8dd1d..3e3e2e9 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,10 @@ joda-time joda-time - + + org.springframework.boot + spring-boot-starter-data-jpa + com.fasterxml.jackson.core jackson-databind @@ -72,8 +75,14 @@ spring-boot-configuration-processor true + + com.h2database + h2 + runtime + + diff --git a/src/main/java/com/svlada/common/InMemoryDatabaseConfig.java b/src/main/java/com/svlada/common/InMemoryDatabaseConfig.java new file mode 100644 index 0000000..936a754 --- /dev/null +++ b/src/main/java/com/svlada/common/InMemoryDatabaseConfig.java @@ -0,0 +1,16 @@ +package com.svlada.common; + +import org.h2.server.web.WebServlet; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class InMemoryDatabaseConfig { + @Bean + public ServletRegistrationBean h2servletRegistration() { + ServletRegistrationBean registration = new ServletRegistrationBean(new WebServlet()); + registration.addUrlMappings("/console/*"); + return registration; + } +} diff --git a/src/main/java/com/svlada/entity/Role.java b/src/main/java/com/svlada/entity/Role.java new file mode 100644 index 0000000..ca479cd --- /dev/null +++ b/src/main/java/com/svlada/entity/Role.java @@ -0,0 +1,11 @@ +package com.svlada.entity; + +/** + * Enumerated {@link User} roles. + * @author vladimir.stankovic + * + * Aug 16, 2016 + */ +public enum Role { + ADMIN, PREMIUM_MEMBER, MEMBER +} diff --git a/src/main/java/com/svlada/entity/User.java b/src/main/java/com/svlada/entity/User.java new file mode 100644 index 0000000..7cd0dbc --- /dev/null +++ b/src/main/java/com/svlada/entity/User.java @@ -0,0 +1,51 @@ +package com.svlada.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="APP_USER") +public class User { + @Id @Column(name="ID") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name="username") + private String username; + + @Column(name="password") + private String password; + + @Column(name="role") + @Enumerated(EnumType.STRING) + private Role role; + + public User(Long id, String username, String password, Role role) { + this.id = id; + this.username = username; + this.password = password; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public Role getRole() { + return role; + } +} diff --git a/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java b/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java index 0ef2d7f..9d36e9a 100644 --- a/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java +++ b/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java @@ -32,8 +32,7 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken { this.setAuthenticated(false); } - public JwtAuthenticationToken(UserContext userContext, SafeJwtToken token, - Collection authorities) { + public JwtAuthenticationToken(UserContext userContext, SafeJwtToken token, Collection authorities) { super(authorities); this.safeToken = token; this.userContext = userContext; 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 17c0755..1a2150e 100644 --- a/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java +++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java @@ -40,7 +40,7 @@ public class AjaxAuthenticationProvider implements AuthenticationProvider { UserContext userContext = userService.loadUser(username, password); - SafeJwtToken safeJwtToken = tokenFactory.createSafeToken(userContext, userContext.getAuthorities()); + SafeJwtToken safeJwtToken = tokenFactory.createSafeToken(userContext); return new JwtAuthenticationToken(userContext, safeJwtToken, userContext.getAuthorities()); } 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 dccd6fa..14ba1c4 100644 --- a/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java +++ b/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java @@ -1,36 +1,22 @@ package com.svlada.security.auth.jwt; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -import org.joda.time.DateTime; -import org.joda.time.Minutes; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.config.JwtSettings; -import com.svlada.security.exceptions.JwtExpiredTokenException; 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.ExpiredJwtException; import io.jsonwebtoken.Jws; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureException; -import io.jsonwebtoken.UnsupportedJwtException; /** * An {@link AuthenticationProvider} implementation that will use provided @@ -42,38 +28,28 @@ import io.jsonwebtoken.UnsupportedJwtException; */ @Component public class JwtAuthenticationProvider implements AuthenticationProvider { - private final TokenAuthStrategy tokenAuthStrategy; private final UserService userService; - private final JwtSettings jwtSettings; + private final JwtSettings jwtSettings; + private final JwtTokenFactory jwtTokenFactory; @Autowired - public JwtAuthenticationProvider(TokenAuthStrategy tokenAuthStrategy, UserService userService, JwtSettings jwtSettings) { - this.tokenAuthStrategy = tokenAuthStrategy; + public JwtAuthenticationProvider(UserService userService, JwtSettings jwtSettings, JwtTokenFactory jwtTokenFactory) { this.userService = userService; this.jwtSettings = jwtSettings; + this.jwtTokenFactory = jwtTokenFactory; } - + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { UnsafeJwtToken unsafeToken = ((JwtAuthenticationToken) authentication).getUnsafeToken(); - try { - Jws jwsClaims = unsafeToken.parse(jwtSettings.getTokenSigningKey()); - } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { - throw new BadCredentialsException("Invalid JWT token: ", ex); - } catch (ExpiredJwtException expiredEx) { - Date expDateTime = expiredEx.getClaims().getExpiration(); - - if (expDate != null && tokenAuthStrategy.isExpired(expDate)) { - - } - } - - SafeJwtToken safeToken = ; - Claims claims = safeToken.getClaims(); + Jws jwsClaims = unsafeToken.parseClaims(jwtSettings.getTokenSigningKey()); + String subject = jwsClaims.getBody().getSubject(); + UserContext context = userService.loadUser(subject); - JwtAuthenticationToken authToken = new JwtAuthenticationToken(userContext, safeToken, userContext.getAuthorities()); - + SafeJwtToken safeToken = jwtTokenFactory.createSafeToken(unsafeToken.getToken(), jwsClaims.getBody()); + + JwtAuthenticationToken authToken = new JwtAuthenticationToken(context, safeToken, context.getAuthorities()); return authToken; } 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 dfaeefc..59d912b 100644 --- a/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java +++ b/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -15,7 +15,6 @@ 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.authentication.AuthenticationSuccessHandler; import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.auth.jwt.extractor.TokenExtractor; diff --git a/src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java b/src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java deleted file mode 100644 index add36fd..0000000 --- a/src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.svlada.security.auth.jwt; - -import java.util.Date; - -import org.joda.time.DateTime; -import org.joda.time.Minutes; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.stereotype.Component; - -import com.svlada.security.config.JwtSettings; -import com.svlada.security.exceptions.JwtExpiredTokenException; -import com.svlada.security.model.JwtTokenFactory; -import com.svlada.security.model.SafeJwtToken; -import com.svlada.security.model.UnsafeJwtToken; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureException; -import io.jsonwebtoken.UnsupportedJwtException; - -/** - * - * @author vladimir.stankovic - * - * Aug 5, 2016 - */ -@Component -public class RefreshTokenAuthStrategy implements TokenAuthStrategy { - private final JwtSettings jwtSettings; - private final JwtTokenFactory tokenFactory; - - @Autowired - public RefreshTokenAuthStrategy(JwtSettings jwtSettings, JwtTokenFactory tokenFactory) { - this.jwtSettings = jwtSettings; - this.tokenFactory = tokenFactory; - } - - @Override - public SafeJwtToken authenticate(UnsafeJwtToken token) { - try { - Jws jwsClaims = token.parse(jwtSettings.getTokenSigningKey()); - return tokenFactory.createSafeToken(token.getToken(), jwsClaims.getBody()); - } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { - throw new BadCredentialsException("Invalid JWT token: ", ex); - } catch (ExpiredJwtException expiredEx) { - Date expDateTime = expiredEx.getClaims().getExpiration(); - - if (expDateTime == null) { - throw new BadCredentialsException("Expiry time is not set"); - } - - DateTime expirationTime = new DateTime(expiredEx.getClaims().getExpiration()); - DateTime currentTime = DateTime.now(); - - if (Minutes.minutesBetween(currentTime, expirationTime).isGreaterThan(Minutes.minutes(jwtSettings.getTokenValidationTimeframe()))) { - throw new JwtExpiredTokenException(token, "JWT token has expired", expiredEx); - } - - return refreshToken(); - } - } - - public SafeJwtToken refreshToken() { - return null; - } -} diff --git a/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java b/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java deleted file mode 100644 index b579f23..0000000 --- a/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.svlada.security.auth.jwt; - -import com.svlada.security.model.SafeJwtToken; -import com.svlada.security.model.UnsafeJwtToken; - -/** - * - * @author vladimir.stankovic - * - * Aug 5, 2016 - */ -public interface TokenAuthStrategy { - public SafeJwtToken authenticate(UnsafeJwtToken token); -} diff --git a/src/main/java/com/svlada/security/model/JwtTokenFactory.java b/src/main/java/com/svlada/security/model/JwtTokenFactory.java index b28d476..5b2afcf 100644 --- a/src/main/java/com/svlada/security/model/JwtTokenFactory.java +++ b/src/main/java/com/svlada/security/model/JwtTokenFactory.java @@ -1,12 +1,8 @@ package com.svlada.security.model; -import java.util.Collection; - import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.stereotype.Component; import com.svlada.security.config.JwtSettings; @@ -14,8 +10,6 @@ import com.svlada.security.config.JwtSettings; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.impl.DefaultClaims; -import io.jsonwebtoken.lang.Collections; /** * Factory class that should be always used to create {@link JwtToken}. @@ -60,6 +54,10 @@ public class JwtTokenFactory { return new SafeJwtToken(token, claims); } + public SafeJwtToken refreshToken(SafeJwtToken safeJwtToken) { + return null; + } + public SafeJwtToken createSafeToken(String token, Claims claims) { 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 9796edb..44bf147 100644 --- a/src/main/java/com/svlada/security/model/UnsafeJwtToken.java +++ b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java @@ -1,8 +1,16 @@ package com.svlada.security.model; +import org.springframework.security.authentication.BadCredentialsException; + +import com.svlada.security.exceptions.JwtExpiredTokenException; + import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.UnsupportedJwtException; public class UnsafeJwtToken implements JwtToken { private String token; @@ -12,11 +20,20 @@ public class UnsafeJwtToken implements JwtToken { } /** - * Validates JWT Token signature. + * Parses and validates JWT Token signature. + * + * @throws BadCredentialsException + * @throws JwtExpiredTokenException * */ - public Jws parse(String signingKey) { - return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); + public Jws parseClaims(String signingKey) { + try { + return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); + } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { + throw new BadCredentialsException("Invalid JWT token: ", ex); + } catch (ExpiredJwtException expiredEx) { + throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); + } } @Override diff --git a/src/main/java/com/svlada/security/repository/UserRepository.java b/src/main/java/com/svlada/security/repository/UserRepository.java new file mode 100644 index 0000000..13e84df --- /dev/null +++ b/src/main/java/com/svlada/security/repository/UserRepository.java @@ -0,0 +1,16 @@ +package com.svlada.security.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.svlada.entity.User; + +/** + * UserRepository + * + * @author vladimir.stankovic + * + * Aug 16, 2016 + */ +public interface UserRepository extends JpaRepository { + +} diff --git a/src/main/java/com/svlada/security/service/UserService.java b/src/main/java/com/svlada/security/service/UserService.java index e8ca314..bf1c5d6 100644 --- a/src/main/java/com/svlada/security/service/UserService.java +++ b/src/main/java/com/svlada/security/service/UserService.java @@ -3,12 +3,14 @@ package com.svlada.security.service; import java.util.ArrayList; import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import com.svlada.security.model.UserContext; import com.svlada.security.model.UserRole; +import com.svlada.security.repository.UserRepository; /** * Mock implementation. @@ -19,6 +21,13 @@ import com.svlada.security.model.UserRole; */ @Service public class UserService { + private final UserRepository userRepository; + + @Autowired + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + public UserContext loadUser(String username, String password) { List authorities = new ArrayList(); authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority())); @@ -30,4 +39,8 @@ public class UserService { authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority())); return new UserContext(username, authorities); } + + public UserRepository getUserRepository() { + return userRepository; + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e4498af..27e0a7d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,4 +3,14 @@ spring.profiles: default demo.security.jwt: tokenExpirationTime: 2 # Number of minutes tokenIssuer: http://svlada.com - tokenSigningKey: xm8EV6Hy5RMFK4EEACIDAwQus \ No newline at end of file + tokenSigningKey: xm8EV6Hy5RMFK4EEACIDAwQus + +spring.datasource: + url: "jdbc:h2:mem:testdb" + driverClassName: org.h2.Driver + username: sa + password: "" + data: "classpath*:data.sql" +spring.jpa: + database-platform: org.hibernate.dialect.H2Dialect +spring.h2.console.enabled: true \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..29469e6 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1 @@ +insert into APP_USER(ID, PASSWORD, ROLE, USERNAME) values(1, 'test', 'ADMIN', 'svlada@gmail.com'); \ No newline at end of file