diff --git a/etc/blog.md b/etc/blog.md
new file mode 100644
index 0000000..2daa9b1
--- /dev/null
+++ b/etc/blog.md
@@ -0,0 +1,83 @@
+## Table of contents:
+1. Introduction
+2. Ajax authentication
+
+### Introduction
+
+Following are three scenarios that will be implemented in this tutorial:
+1. Ajax Authentication
+2. JWT Token
+3. URL Based Authentication with JWT Token
+
+### Prerequisites
+
+First step is to create empty Spring Boot project. Visit spring initializr website(https://start.spring.io/) to generate boilerplate.
+
+Lets start by creating base package structure for our sample code.
+
+```
++---main
+| +---java
+| | +---com
+| | | \---svlada
+| | | +---common
+| | | \---security
+| | | +---auth
+| | | | +---ajax
+| | | | \---jwt
+| | | +---config
+| | | +---exceptions
+| | | \---model
+| \---resources
+| +---static
+| \---templates
+\---test
+ \---java
+ \---com
+ \---svlada
+```
+
+### Ajax authentication
+
+Code for ajax authentication will reside in the following package: com/svlada/security/auth/ajax.
+
+In order to implement Ajax Login in Spring Boot we'll need to implement a couple of components.
+
+1. AjaxLoginProcessingFilter
+2. AjaxAuthenticationProvider
+3. AjaxAwareAuthenticationSuccessHandler
+4. AjaxAwareAuthenticationFailureHandler
+5. RestAuthenticationEntryPoint
+6. WebSecurityConfig
+
+Let's dive in the implementation details.
+
+#### AjaxLoginProcessingFilter
+
+#### Security Config
+
+Create WebSecurityConfig class and put it in the com.svlada.security.config package.
+
+WebSecurityConfig class needs to extend org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.
+
+#### Un-successufull access to protected resource
+
+Request
+```
+GET /api/me HTTP/1.1
+Host: localhost:9966
+Cache-Control: no-cache
+```
+
+Response
+```
+{
+ "timestamp": 1470301809962,
+ "status": 401,
+ "error": "Unauthorized",
+ "message": "Full authentication is required to access this resource",
+ "path": "/api/me"
+}
+```
+
+#### Successufull ajax authentication
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 013d095..7a8dd1d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,7 @@
UTF-8
UTF-8
1.8
+ 0.6.0
@@ -33,12 +34,44 @@
org.springframework.boot
spring-boot-starter-web
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+ org.apache.commons
+ commons-lang3
+ 3.3.2
+
+
+ joda-time
+ joda-time
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
org.springframework.boot
spring-boot-starter-test
test
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
diff --git a/src/main/java/com/svlada/SpringbootSecurityJwtApplication.java b/src/main/java/com/svlada/SpringbootSecurityJwtApplication.java
index a974601..e83232f 100644
--- a/src/main/java/com/svlada/SpringbootSecurityJwtApplication.java
+++ b/src/main/java/com/svlada/SpringbootSecurityJwtApplication.java
@@ -2,10 +2,18 @@ package com.svlada;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+/**
+ * Sample application for demonstrating security with JWT Tokens
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
@SpringBootApplication
+@EnableConfigurationProperties
public class SpringbootSecurityJwtApplication {
-
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityJwtApplication.class, args);
}
diff --git a/src/main/java/com/svlada/common/ErrorCode.java b/src/main/java/com/svlada/common/ErrorCode.java
new file mode 100644
index 0000000..995a337
--- /dev/null
+++ b/src/main/java/com/svlada/common/ErrorCode.java
@@ -0,0 +1,27 @@
+package com.svlada.common;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Enumeration of REST Error types.
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+public enum ErrorCode {
+ GLOBAL(2),
+
+ AUTHENTICATION(10), JWT_TOKEN_EXPIRED(11);
+
+ private int errorCode;
+
+ private ErrorCode(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @JsonValue
+ public int getErrorCode() {
+ return errorCode;
+ }
+}
diff --git a/src/main/java/com/svlada/common/ErrorResponse.java b/src/main/java/com/svlada/common/ErrorResponse.java
new file mode 100644
index 0000000..40fbb67
--- /dev/null
+++ b/src/main/java/com/svlada/common/ErrorResponse.java
@@ -0,0 +1,52 @@
+package com.svlada.common;
+
+import java.util.Date;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * Error model for interacting with client.
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+public class ErrorResponse {
+ // HTTP Response Status Code
+ private final HttpStatus status;
+
+ // General Error message
+ private final String message;
+
+ // Error code
+ private final ErrorCode errorCode;
+
+ private final Date timestamp;
+
+ protected ErrorResponse(final String message, final ErrorCode errorCode, HttpStatus status) {
+ this.message = message;
+ this.errorCode = errorCode;
+ this.status = status;
+ this.timestamp = new java.util.Date();
+ }
+
+ public static ErrorResponse of(final String message, final ErrorCode errorCode, HttpStatus status) {
+ return new ErrorResponse(message, errorCode, status);
+ }
+
+ public Integer getStatus() {
+ return status.value();
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ public Date getTimestamp() {
+ return timestamp;
+ }
+}
diff --git a/src/main/java/com/svlada/common/WebUtil.java b/src/main/java/com/svlada/common/WebUtil.java
new file mode 100644
index 0000000..aa1aff4
--- /dev/null
+++ b/src/main/java/com/svlada/common/WebUtil.java
@@ -0,0 +1,31 @@
+package com.svlada.common;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.web.savedrequest.SavedRequest;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+public class WebUtil {
+ private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
+ private static final String X_REQUESTED_WITH = "X-Requested-With";
+
+ private static final String CONTENT_TYPE = "Content-type";
+ private static final String CONTENT_TYPE_JSON = "application/json";
+
+ public static boolean isAjax(HttpServletRequest request) {
+ return XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH));
+ }
+
+ public static boolean isAjax(SavedRequest request) {
+ return request.getHeaderValues(X_REQUESTED_WITH).contains(XML_HTTP_REQUEST);
+ }
+
+ public static boolean isContentTypeJson(SavedRequest request) {
+ return request.getHeaderValues(CONTENT_TYPE).contains(CONTENT_TYPE_JSON);
+ }
+}
diff --git a/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java b/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java
new file mode 100644
index 0000000..985d3e7
--- /dev/null
+++ b/src/main/java/com/svlada/profile/endpoint/ProfileEndpoint.java
@@ -0,0 +1,24 @@
+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.model.UserContext;
+
+/**
+ * End-point for retrieving logged-in user details.
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 4, 2016
+ */
+@RestController
+public class ProfileEndpoint {
+ @RequestMapping(value="/api/me", method=RequestMethod.GET)
+ public @ResponseBody UserContext get() {
+ throw new NotImplementedException("Not implemented");
+ }
+}
diff --git a/src/main/java/com/svlada/security/RestAuthenticationEntryPoint.java b/src/main/java/com/svlada/security/RestAuthenticationEntryPoint.java
new file mode 100644
index 0000000..c13e47a
--- /dev/null
+++ b/src/main/java/com/svlada/security/RestAuthenticationEntryPoint.java
@@ -0,0 +1,27 @@
+package com.svlada.security;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 4, 2016
+ */
+@Component
+public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex)
+ throws IOException, ServletException {
+ response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized");
+ }
+}
diff --git a/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java b/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java
new file mode 100644
index 0000000..8ae063b
--- /dev/null
+++ b/src/main/java/com/svlada/security/auth/JwtAuthenticationToken.java
@@ -0,0 +1,74 @@
+package com.svlada.security.auth;
+
+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;
+
+/**
+ * An {@link org.springframework.security.core.Authentication} implementation that is designed for simple presentation
+ * of JwtToken.
+ *
+ * @author vladimir.stankovic
+ *
+ * May 23, 2016
+ */
+public class JwtAuthenticationToken extends AbstractAuthenticationToken {
+ private static final long serialVersionUID = 2877954820905567501L;
+
+ private JwtToken safeToken;
+ private UnsafeJwtToken unsafeToken;
+
+ private UserContext userContext;
+
+ public JwtAuthenticationToken(UnsafeJwtToken unsafeToken) {
+ super(null);
+ this.unsafeToken = unsafeToken;
+ this.
+ setAuthenticated(false);
+ }
+
+ public JwtAuthenticationToken(UserContext userContext, SafeJwtToken token, Collection extends GrantedAuthority> authorities) {
+ super(authorities);
+ this.safeToken = token;
+ this.userContext = userContext;
+ super.setAuthenticated(true);
+ }
+
+ @Override
+ public void setAuthenticated(boolean authenticated) {
+ if (authenticated) {
+ throw new IllegalArgumentException(
+ "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+ }
+ super.setAuthenticated(false);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return this.userContext;
+ }
+
+ public JwtToken getSafeToken() {
+ return this.safeToken;
+ }
+
+ public UnsafeJwtToken getUnsafeToken() {
+ return unsafeToken;
+ }
+
+ @Override
+ public void eraseCredentials() {
+ super.eraseCredentials();
+ }
+}
diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java b/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java
new file mode 100644
index 0000000..17c0755
--- /dev/null
+++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAuthenticationProvider.java
@@ -0,0 +1,52 @@
+package com.svlada.security.auth.ajax;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+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;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+@Component
+public class AjaxAuthenticationProvider implements AuthenticationProvider {
+ private final JwtTokenFactory tokenFactory;
+ private final UserService userService;
+
+ @Autowired
+ public AjaxAuthenticationProvider(final JwtTokenFactory tokenFactory, final UserService userService) {
+ this.tokenFactory = tokenFactory;
+ this.userService = userService;
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ Assert.notNull(authentication, "No authentication data provided.");
+
+ String username = (String) authentication.getPrincipal();
+ String password = (String) authentication.getCredentials();
+
+ UserContext userContext = userService.loadUser(username, password);
+
+ SafeJwtToken safeJwtToken = tokenFactory.createSafeToken(userContext, userContext.getAuthorities());
+
+ return new JwtAuthenticationToken(userContext, safeJwtToken, userContext.getAuthorities());
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
+ }
+}
diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationFailureHandler.java b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationFailureHandler.java
new file mode 100644
index 0000000..4781b40
--- /dev/null
+++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationFailureHandler.java
@@ -0,0 +1,55 @@
+package com.svlada.security.auth.ajax;
+
+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.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.svlada.common.ErrorCode;
+import com.svlada.common.ErrorResponse;
+import com.svlada.security.exceptions.AuthMethodNotSupportedException;
+import com.svlada.security.exceptions.JwtExpiredTokenException;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+@Component
+public class AjaxAwareAuthenticationFailureHandler implements AuthenticationFailureHandler {
+ private final ObjectMapper mapper;
+
+ @Autowired
+ public AjaxAwareAuthenticationFailureHandler(ObjectMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+ AuthenticationException e) throws IOException, ServletException {
+
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+
+ if (e instanceof BadCredentialsException) {
+ mapper.writeValue(response.getWriter(), ErrorResponse.of("Invalid username or password", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
+ } else if (e instanceof JwtExpiredTokenException) {
+ mapper.writeValue(response.getWriter(), ErrorResponse.of("Token has expired", ErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED));
+ } else if (e instanceof AuthMethodNotSupportedException) {
+ mapper.writeValue(response.getWriter(), ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
+ }
+
+ mapper.writeValue(response.getWriter(), ErrorResponse.of("Authentication failed", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
+ }
+}
diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java
new file mode 100644
index 0000000..7babfbd
--- /dev/null
+++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java
@@ -0,0 +1,63 @@
+package com.svlada.security.auth.ajax;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.WebAttributes;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.svlada.security.auth.JwtAuthenticationToken;
+import com.svlada.security.model.JwtToken;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+@Component
+public class AjaxAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
+ private final ObjectMapper mapper;
+
+ @Autowired
+ public AjaxAwareAuthenticationSuccessHandler(ObjectMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+ Authentication authentication) throws IOException, ServletException {
+ JwtToken token = ((JwtAuthenticationToken) authentication).getSafeToken();
+
+ response.setStatus(HttpStatus.OK.value());
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ mapper.writeValue(response.getWriter(), token);
+
+ clearAuthenticationAttributes(request);
+ }
+
+ /**
+ * Removes temporary authentication-related data which may have been stored
+ * in the session during the authentication process..
+ *
+ */
+ protected final void clearAuthenticationAttributes(HttpServletRequest request) {
+ HttpSession session = request.getSession(false);
+
+ if (session == null) {
+ return;
+ }
+
+ session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
+ }
+}
diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java b/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java
new file mode 100644
index 0000000..84cec43
--- /dev/null
+++ b/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java
@@ -0,0 +1,82 @@
+package com.svlada.security.auth.ajax;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+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.fasterxml.jackson.databind.ObjectMapper;
+import com.svlada.common.WebUtil;
+import com.svlada.security.exceptions.AuthMethodNotSupportedException;
+
+/**
+ * AjaxLoginProcessingFilter
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
+ private static Logger logger = LoggerFactory.getLogger(AjaxLoginProcessingFilter.class);
+
+ private final AuthenticationSuccessHandler successHandler;
+ private final AuthenticationFailureHandler failureHandler;
+
+ private final ObjectMapper objectMapper;
+
+ public AjaxLoginProcessingFilter(String defaultFilterProcessesUrl,
+ AuthenticationSuccessHandler successHandler,
+ AuthenticationFailureHandler failureHandler,
+ ObjectMapper mapper) {
+ super(defaultFilterProcessesUrl);
+ this.successHandler = successHandler;
+ this.failureHandler = failureHandler;
+ this.objectMapper = mapper;
+ }
+
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+ throws AuthenticationException, IOException, ServletException {
+ if (!HttpMethod.POST.name().equals(request.getMethod()) || !WebUtil.isAjax(request)) {
+ throw new AuthMethodNotSupportedException("Authentication method not supported");
+ }
+
+ LoginRequest loginRequest = objectMapper.readValue(request.getReader(), LoginRequest.class);
+
+ 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);
+ }
+
+ @Override
+ protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
+ Authentication authResult) throws IOException, ServletException {
+ successHandler.onAuthenticationSuccess(request, response, authResult);
+ }
+
+ @Override
+ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+ AuthenticationException failed) throws IOException, ServletException {
+ SecurityContextHolder.clearContext();
+ failureHandler.onAuthenticationFailure(request, response, failed);
+ }
+}
diff --git a/src/main/java/com/svlada/security/auth/ajax/LoginRequest.java b/src/main/java/com/svlada/security/auth/ajax/LoginRequest.java
new file mode 100644
index 0000000..d6cf162
--- /dev/null
+++ b/src/main/java/com/svlada/security/auth/ajax/LoginRequest.java
@@ -0,0 +1,31 @@
+package com.svlada.security.auth.ajax;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Model intended to be used for AJAX based authentication.
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+
+public class LoginRequest {
+ private String username;
+ private String password;
+
+ @JsonCreator
+ public LoginRequest(@JsonProperty("username") String username, @JsonProperty("password") String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+}
diff --git a/src/main/java/com/svlada/security/config/JwtSettings.java b/src/main/java/com/svlada/security/config/JwtSettings.java
new file mode 100644
index 0000000..b617f28
--- /dev/null
+++ b/src/main/java/com/svlada/security/config/JwtSettings.java
@@ -0,0 +1,46 @@
+package com.svlada.security.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "demo.security.jwt")
+public class JwtSettings {
+ /**
+ * {@link JwtToken} will expire after this time.
+ */
+ private Integer tokenExpirationTime;
+
+ /**
+ * Token issuer.
+ */
+ private String tokenIssuer;
+
+ /**
+ * Key is used to sign {@link JwtToken}.
+ */
+ private String tokenSigningKey;
+
+ public Integer getTokenExpirationTime() {
+ return tokenExpirationTime;
+ }
+
+ public void setTokenExpirationTime(Integer tokenExpirationTime) {
+ this.tokenExpirationTime = tokenExpirationTime;
+ }
+
+ public String getTokenIssuer() {
+ return tokenIssuer;
+ }
+ public void setTokenIssuer(String tokenIssuer) {
+ this.tokenIssuer = tokenIssuer;
+ }
+
+ public String getTokenSigningKey() {
+ return tokenSigningKey;
+ }
+
+ public void setTokenSigningKey(String tokenSigningKey) {
+ this.tokenSigningKey = tokenSigningKey;
+ }
+}
diff --git a/src/main/java/com/svlada/security/config/WebSecurityConfig.java b/src/main/java/com/svlada/security/config/WebSecurityConfig.java
new file mode 100644
index 0000000..40fa2e4
--- /dev/null
+++ b/src/main/java/com/svlada/security/config/WebSecurityConfig.java
@@ -0,0 +1,77 @@
+package com.svlada.security.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+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.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.ajax.AjaxAuthenticationProvider;
+import com.svlada.security.auth.ajax.AjaxLoginProcessingFilter;
+
+/**
+ * WebSecurityConfig
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+ public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
+ public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
+
+ @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint;
+ @Autowired private AuthenticationSuccessHandler successHandler;
+ @Autowired private AuthenticationFailureHandler failureHandler;
+ @Autowired private AjaxAuthenticationProvider ajaxAuthenticationProvider;
+
+ @Autowired private AuthenticationManager authenticationManager;
+
+ @Autowired private ObjectMapper objectMapper;
+
+ @Bean
+ protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter() throws Exception {
+ AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
+ filter.setAuthenticationManager(this.authenticationManager);
+ return filter;
+ }
+
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ protected void configure(AuthenticationManagerBuilder auth) {
+ auth.authenticationProvider(ajaxAuthenticationProvider);
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .csrf().disable() // We don't need CSRF for JWT based authentication
+ .exceptionHandling()
+ .authenticationEntryPoint(this.authenticationEntryPoint)
+
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+
+ .and()
+ .authorizeRequests()
+ .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
+ .and()
+ .addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
+ }
+}
diff --git a/src/main/java/com/svlada/security/exceptions/AuthMethodNotSupportedException.java b/src/main/java/com/svlada/security/exceptions/AuthMethodNotSupportedException.java
new file mode 100644
index 0000000..236d15a
--- /dev/null
+++ b/src/main/java/com/svlada/security/exceptions/AuthMethodNotSupportedException.java
@@ -0,0 +1,17 @@
+package com.svlada.security.exceptions;
+
+import org.springframework.security.authentication.AuthenticationServiceException;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 4, 2016
+ */
+public class AuthMethodNotSupportedException extends AuthenticationServiceException {
+ private static final long serialVersionUID = 3705043083010304496L;
+
+ public AuthMethodNotSupportedException(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/java/com/svlada/security/exceptions/JwtExpiredTokenException.java b/src/main/java/com/svlada/security/exceptions/JwtExpiredTokenException.java
new file mode 100644
index 0000000..0b1c18c
--- /dev/null
+++ b/src/main/java/com/svlada/security/exceptions/JwtExpiredTokenException.java
@@ -0,0 +1,30 @@
+package com.svlada.security.exceptions;
+
+import org.springframework.security.core.AuthenticationException;
+
+import com.svlada.security.model.JwtToken;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+public class JwtExpiredTokenException extends AuthenticationException {
+ private static final long serialVersionUID = -5959543783324224864L;
+
+ private JwtToken token;
+
+ public JwtExpiredTokenException(String msg) {
+ super(msg);
+ }
+
+ public JwtExpiredTokenException(JwtToken token, String msg, Throwable t) {
+ super(msg, t);
+ this.token = token;
+ }
+
+ public String token() {
+ return this.token.getToken();
+ }
+}
diff --git a/src/main/java/com/svlada/security/model/JwtToken.java b/src/main/java/com/svlada/security/model/JwtToken.java
new file mode 100644
index 0000000..d2afe7f
--- /dev/null
+++ b/src/main/java/com/svlada/security/model/JwtToken.java
@@ -0,0 +1,5 @@
+package com.svlada.security.model;
+
+public interface JwtToken {
+ String getToken();
+}
diff --git a/src/main/java/com/svlada/security/model/JwtTokenFactory.java b/src/main/java/com/svlada/security/model/JwtTokenFactory.java
new file mode 100644
index 0000000..6af378e
--- /dev/null
+++ b/src/main/java/com/svlada/security/model/JwtTokenFactory.java
@@ -0,0 +1,74 @@
+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;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.lang.Collections;
+
+/**
+ * Factory class that should be always used to create {@link JwtToken}.
+ *
+ * @author vladimir.stankovic
+ *
+ * May 31, 2016
+ */
+@Component
+public class JwtTokenFactory {
+ @Autowired private JwtSettings settings;
+
+ /**
+ * Factory method for issuing new JWT Tokens.
+ *
+ * @param username
+ * @param roles
+ * @return
+ */
+ public SafeJwtToken createSafeToken(UserContext userContext, final Collection roles) {
+ if (StringUtils.isBlank(userContext.getUsername())) {
+ throw new IllegalArgumentException("Cannot create JWT Token without username");
+ }
+
+ if (Collections.isEmpty(roles)) {
+ throw new IllegalArgumentException("Cannot create JWT Token without roles");
+ }
+
+ DateTime currentTime = new DateTime();
+
+ Claims claims = Jwts.claims();
+ claims.put("roles", AuthorityUtils.authorityListToSet(roles));
+
+ String token = Jwts.builder()
+ .setIssuer(settings.getTokenIssuer())
+ .setSubject(userContext.getUsername())
+ .setClaims(claims)
+ .setIssuedAt(currentTime.toDate())
+ .setExpiration(currentTime.plusMinutes(settings.getTokenExpirationTime()).toDate())
+ .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey())
+ .compact();
+
+ return new SafeJwtToken(token, claims);
+ }
+
+ /**
+ * Unsafe version of JWT token is created.
+ *
+ * WARNING: Token signature validation is not performed.
+ *
+ * @param tokenPayload
+ * @return unsafe version of JWT token.
+ */
+ public UnsafeJwtToken createUnsafeToken(String tokenPayload) {
+ return new UnsafeJwtToken(tokenPayload);
+ }
+}
diff --git a/src/main/java/com/svlada/security/model/SafeJwtToken.java b/src/main/java/com/svlada/security/model/SafeJwtToken.java
new file mode 100644
index 0000000..6150fc9
--- /dev/null
+++ b/src/main/java/com/svlada/security/model/SafeJwtToken.java
@@ -0,0 +1,30 @@
+package com.svlada.security.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import io.jsonwebtoken.Claims;
+
+/**
+ * Raw representation of JWT Token.
+ *
+ * @author vladimir.stankovic
+ *
+ * May 31, 2016
+ */
+public final class SafeJwtToken implements JwtToken {
+ private final String rawToken;
+ @JsonIgnore private Claims claims;
+
+ protected SafeJwtToken(final String token, Claims claims) {
+ this.rawToken = token;
+ this.claims = claims;
+ }
+
+ public String getToken() {
+ return this.rawToken;
+ }
+
+ public Claims getClaims() {
+ return claims;
+ }
+}
diff --git a/src/main/java/com/svlada/security/model/UnsafeJwtToken.java b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java
new file mode 100644
index 0000000..e4f2060
--- /dev/null
+++ b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java
@@ -0,0 +1,35 @@
+package com.svlada.security.model;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+
+public class UnsafeJwtToken implements JwtToken {
+ private String token;
+
+ public UnsafeJwtToken(String token) {
+ this.token = token;
+ }
+
+ /**
+ * Validates JWT Token signature.
+ *
+ */
+ public void validateToken(String signingKey) {
+ Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
+ }
+
+ /**
+ * Extract Claims object from the rawToken.
+ *
+ * @param signingKey
+ * @return
+ */
+ public Claims parseClaims(String signingKey) {
+ return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token).getBody();
+ }
+
+ @Override
+ public String getToken() {
+ return token;
+ }
+}
diff --git a/src/main/java/com/svlada/security/model/UserContext.java b/src/main/java/com/svlada/security/model/UserContext.java
new file mode 100644
index 0000000..ee126c8
--- /dev/null
+++ b/src/main/java/com/svlada/security/model/UserContext.java
@@ -0,0 +1,35 @@
+package com.svlada.security.model;
+
+import java.util.List;
+
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 4, 2016
+ */
+public class UserContext {
+ private final String username;
+ private final String email;
+ private final List authorities;
+
+ public UserContext(String username, String email, List authorities) {
+ this.username = username;
+ this.email = email;
+ this.authorities = authorities;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public List getAuthorities() {
+ return authorities;
+ }
+}
diff --git a/src/main/java/com/svlada/security/model/UserRole.java b/src/main/java/com/svlada/security/model/UserRole.java
new file mode 100644
index 0000000..ee243c9
--- /dev/null
+++ b/src/main/java/com/svlada/security/model/UserRole.java
@@ -0,0 +1,16 @@
+package com.svlada.security.model;
+
+/**
+ * Enumeration of user Roles.
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 3, 2016
+ */
+public enum UserRole {
+ ADMIN, INSTRUCTOR, PARTICIPANT, SUPERADMIN;
+
+ public String authority() {
+ return "ROLE_" + this.name();
+ }
+}
diff --git a/src/main/java/com/svlada/security/service/UserService.java b/src/main/java/com/svlada/security/service/UserService.java
new file mode 100644
index 0000000..ac8b7b6
--- /dev/null
+++ b/src/main/java/com/svlada/security/service/UserService.java
@@ -0,0 +1,27 @@
+package com.svlada.security.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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;
+
+/**
+ * Mock implementation.
+ *
+ * @author vladimir.stankovic
+ *
+ * Aug 4, 2016
+ */
+@Service
+public class UserService {
+ public UserContext loadUser(String username, String password) {
+ List authorities = new ArrayList();
+ authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority()));
+ return new UserContext(username, "svlada@gmail.com", authorities);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index 7c5b6a3..0000000
--- a/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-server.port=9966
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..e4498af
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+server.port: 9966
+spring.profiles: default
+demo.security.jwt:
+ tokenExpirationTime: 2 # Number of minutes
+ tokenIssuer: http://svlada.com
+ tokenSigningKey: xm8EV6Hy5RMFK4EEACIDAwQus
\ No newline at end of file
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..54a4b48
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file