Added H2 in memory as a sample db.
This commit is contained in:
parent
f8af4297bc
commit
717c3e35cc
27
etc/blog.md
27
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)
|
||||
### [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
11
pom.xml
@ -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>
|
||||
|
||||
16
src/main/java/com/svlada/common/InMemoryDatabaseConfig.java
Normal file
16
src/main/java/com/svlada/common/InMemoryDatabaseConfig.java
Normal 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;
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/svlada/entity/Role.java
Normal file
11
src/main/java/com/svlada/entity/Role.java
Normal 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
|
||||
}
|
||||
51
src/main/java/com/svlada/entity/User.java
Normal file
51
src/main/java/com/svlada/entity/User.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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> {
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
1
src/main/resources/data.sql
Normal file
1
src/main/resources/data.sql
Normal file
@ -0,0 +1 @@
|
||||
insert into APP_USER(ID, PASSWORD, ROLE, USERNAME) values(1, 'test', 'ADMIN', 'svlada@gmail.com');
|
||||
Loading…
Reference in New Issue
Block a user