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 extends GrantedAuthority> authorities) {
+ public JwtAuthenticationToken(UserContext userContext, SafeJwtToken token, Collection extends GrantedAuthority> 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