From 7d05be32647e1c20ac8021920bf05ee538d043e0 Mon Sep 17 00:00:00 2001
From: Julian Horner <julianhorner@web.de>
Date: Tue, 17 Dec 2019 15:44:51 +0100
Subject: [PATCH] Add authentication to api gateway

---
 pom.xml                                       |   9 ++
 src/main/java/de/rtuni/ms/apig/JwtConfig.java |  72 ++++++++++++
 .../ms/apig/JwtTokenAuthenticationFilter.java | 110 ++++++++++++++++++
 .../rtuni/ms/apig/SecurityConfiguration.java  |  71 +++++++++++
 src/main/resources/application.yml            |   2 +-
 5 files changed, 263 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/de/rtuni/ms/apig/JwtConfig.java
 create mode 100644 src/main/java/de/rtuni/ms/apig/JwtTokenAuthenticationFilter.java
 create mode 100644 src/main/java/de/rtuni/ms/apig/SecurityConfiguration.java

diff --git a/pom.xml b/pom.xml
index 38864d7..6a90a39 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,15 @@
             <artifactId>spring-boot-devtools</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.0</version>
+        </dependency>
 	</dependencies>
 
     <dependencyManagement>
diff --git a/src/main/java/de/rtuni/ms/apig/JwtConfig.java b/src/main/java/de/rtuni/ms/apig/JwtConfig.java
new file mode 100644
index 0000000..c95b1ce
--- /dev/null
+++ b/src/main/java/de/rtuni/ms/apig/JwtConfig.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019 (C) by Julian Horner.
+ * All Rights Reserved.
+ */
+
+package de.rtuni.ms.apig;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Configuration class for json web token.
+ * 
+ * @author Julian
+ *
+ */
+public class JwtConfig {
+    //----------------------------------------------------------------------------------------------
+
+    @Value("${security.jwt.uri:/auth/**}")
+    private String Uri;
+
+    @Value("${security.jwt.header:Authorization}")
+    private String header;
+
+    @Value("${security.jwt.prefix:Bearer}")
+    private String prefix;
+
+    @Value("${security.jwt.expiration:#{24*60*60}}")
+    private int expiration;
+
+    @Value("${security.jwt.secret:JwtSecretKey}")
+    private String secret;
+
+    //----------------------------------------------------------------------------------------------
+
+    /**
+     * Get the uri.
+     * 
+     * @return The uri
+     */
+    public String getUri() { return Uri; }
+
+    /**
+     * Get the header.
+     * 
+     * @return The header
+     */
+    public String getHeader() { return header; }
+
+    /**
+     * Get the prefix.
+     * 
+     * @return The prefix
+     */
+    public String getPrefix() { return prefix; }
+
+    /**
+     * Get the expiration.
+     * 
+     * @return The expiration
+     */
+    public int getExpiration() { return expiration; }
+
+    /**
+     * Get the secret.
+     * 
+     * @return The secret
+     */
+    public String getSecret() { return secret; }
+
+    //----------------------------------------------------------------------------------------------
+}
diff --git a/src/main/java/de/rtuni/ms/apig/JwtTokenAuthenticationFilter.java b/src/main/java/de/rtuni/ms/apig/JwtTokenAuthenticationFilter.java
new file mode 100644
index 0000000..1e1f0c9
--- /dev/null
+++ b/src/main/java/de/rtuni/ms/apig/JwtTokenAuthenticationFilter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019 (C) by Julian Horner.
+ * All Rights Reserved.
+ */
+
+package de.rtuni.ms.apig;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+
+/**
+ * Class for
+ * 
+ * @author Julian
+ */
+public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
+    //----------------------------------------------------------------------------------------------
+
+    /** The configuration for the json web token. */
+    private final JwtConfig jwtConfig;
+
+    //----------------------------------------------------------------------------------------------
+
+    /**
+     * Set the given configuration for json web token.
+     * 
+     * @param config The stated configuration
+     */
+    public JwtTokenAuthenticationFilter(final JwtConfig config) { jwtConfig = config; }
+
+    //----------------------------------------------------------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+            FilterChain chain) throws ServletException, IOException {
+        
+        // 1. get the authentication header. Tokens are supposed to be passed in the 
+        // authentication header
+        String header = request.getHeader(jwtConfig.getHeader());
+        // 2. validate the header and check the prefix
+        if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
+            chain.doFilter(request, response); // If not valid, go to the next filter.
+            return;
+        }
+        
+        /*
+         * If there is no token provided and hence the user won't be authenticated.
+         * It's Ok. Maybe the user accessing a public path or asking for a token.
+         * All secured paths that needs a token are already defined and secured in config class.
+         * And if user tried to access without access token, then he won't be authenticated and
+         * an exception will be thrown.
+         */
+        
+        // 3. Get the token
+        String token = header.replace(jwtConfig.getPrefix(), "");
+        try { 
+            // exceptions might be thrown in creating the claims if for example the token is expired
+            
+            // 4. Validate the token
+            Claims claims = Jwts.parser().setSigningKey(jwtConfig.getSecret().getBytes())
+                    .parseClaimsJws(token).getBody();
+            String username = claims.getSubject();
+            if (username != null) {
+                @SuppressWarnings("unchecked")
+                List<String> authorities = (List<String>) claims.get("authorities");
+                
+                // 5. Create auth object
+                
+                /*
+                 * UsernamePasswordAuthenticationToken: A built-in object, used by spring to
+                 * represent the current authenticated / being authenticated user.
+                 * It needs a list of authorities, which has type of GrantedAuthority interface,
+                 * where SimpleGrantedAuthority is an implementation of that interface
+                 */
+                UsernamePasswordAuthenticationToken auth =
+                        new UsernamePasswordAuthenticationToken(
+                                username, null, authorities.stream().
+                                map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
+                
+                // 6. Authenticate the user
+                // Now, user is authenticated
+                SecurityContextHolder.getContext().setAuthentication(auth);
+            }
+        } catch (Exception e) {
+            // In case of failure. Make sure it's clear; so guarantee user won't be authenticated
+            SecurityContextHolder.clearContext();
+        }
+        // go to the next filter in the filter chain
+        chain.doFilter(request, response);
+    }
+    
+    //----------------------------------------------------------------------------------------------
+}
diff --git a/src/main/java/de/rtuni/ms/apig/SecurityConfiguration.java b/src/main/java/de/rtuni/ms/apig/SecurityConfiguration.java
new file mode 100644
index 0000000..c75541a
--- /dev/null
+++ b/src/main/java/de/rtuni/ms/apig/SecurityConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 (C) by Julian Horner.
+ * All Rights Reserved.
+ */
+
+package de.rtuni.ms.apig;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpMethod;
+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.UsernamePasswordAuthenticationFilter;
+
+/**
+ * Class that handles security configuration.
+ * 
+ * @author Julian
+ */
+@EnableWebSecurity
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+    //----------------------------------------------------------------------------------------------
+
+    /** The configuration for the json web token. */
+    @Autowired
+    private JwtConfig jwtConfig;
+
+    //----------------------------------------------------------------------------------------------
+
+    /**
+     * Overrides the default configuration.
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.csrf().disable()
+            // make sure we use stateless session; session won't be used to store user's state.
+            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+            // handle an authorized attempts 
+            .exceptionHandling().authenticationEntryPoint(
+                    (req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED)).and()
+            // Add a filter to validate the tokens with every request
+            .addFilterAfter(new JwtTokenAuthenticationFilter(jwtConfig),
+                    UsernamePasswordAuthenticationFilter.class)
+            // authorization requests config
+            .authorizeRequests()
+            // allow all who are accessing "auth" service
+            .antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
+            // must be an admin if trying to access secured page (authentication is also required)
+            .antMatchers("/securedPage/**").hasRole("ADMIN")
+            // Any other request must be authenticated
+            .anyRequest().authenticated();
+    }
+
+    //----------------------------------------------------------------------------------------------
+
+    /**
+     * Get a new <code>JwtConfig</code>.
+     * 
+     * @return The stated configuration
+     */
+    @Bean
+    public JwtConfig jwtConfig() {
+        return new JwtConfig();
+    }
+
+    //----------------------------------------------------------------------------------------------
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 761fb9c..d297533 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -26,4 +26,4 @@ zuul:
     auth-service:
       path: /auth/**
       service-id: AUTH-SERVICE # can we write this lowercase?
-      
+      sensitive-headers: Cookie,Set-Cookie      
-- 
GitLab