Added H2 in memory as a sample db.

This commit is contained in:
svlada 2016-08-16 17:17:56 +02:00
parent f8af4297bc
commit 717c3e35cc
17 changed files with 191 additions and 137 deletions

View File

@ -474,8 +474,29 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
## References
[Spring Security Architecture - Dave Syer](https://github.com/dsyer/spring-security-architecture)
### [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/

11
pom.xml
View File

@ -48,7 +48,10 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@ -71,9 +74,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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());
}

View File

@ -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 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<Claims> 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();
Jws<Claims> jwsClaims = unsafeToken.parseClaims(jwtSettings.getTokenSigningKey());
String subject = jwsClaims.getBody().getSubject();
UserContext context = userService.loadUser(subject);
if (expDate != null && tokenAuthStrategy.isExpired(expDate)) {
}
}
SafeJwtToken safeToken = ;
Claims claims = safeToken.getClaims();
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;
}

View File

@ -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;

View File

@ -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<Claims> 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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<Claims> parse(String signingKey) {
public Jws<Claims> 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

View File

@ -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<User, Long> {
}

View File

@ -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<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
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;
}
}

View File

@ -4,3 +4,13 @@ demo.security.jwt:
tokenExpirationTime: 2 # Number of minutes
tokenIssuer: http://svlada.com
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

View File

@ -0,0 +1 @@
insert into APP_USER(ID, PASSWORD, ROLE, USERNAME) values(1, 'test', 'ADMIN', 'svlada@gmail.com');