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