├── .gitignore
├── README.md
├── api-gateway
├── .gitignore
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── github
│ │ └── akhuntsaria
│ │ └── apigateway
│ │ ├── ApiGatewayApplication.java
│ │ ├── config
│ │ ├── AuthenticationFilter.java
│ │ ├── RestTemplateConfig.java
│ │ └── WebSecurityConfiguration.java
│ │ ├── dto
│ │ ├── JwtParseRequestDto.java
│ │ └── JwtParseResponseDto.java
│ │ └── model
│ │ └── UserRole.java
│ └── resources
│ └── application.properties
├── auth-service
├── .gitignore
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── github
│ │ └── akhuntsaria
│ │ └── authservice
│ │ ├── AuthServiceApplication.java
│ │ ├── config
│ │ ├── JwtUsernamePasswordAuthenticationFilter.java
│ │ └── WebSecurityConfiguration.java
│ │ ├── controller
│ │ └── JwtController.java
│ │ ├── dto
│ │ ├── ErrorDto.java
│ │ ├── JwtParseRequestDto.java
│ │ ├── JwtParseResponseDto.java
│ │ └── LoginDto.java
│ │ ├── model
│ │ └── UserRole.java
│ │ └── service
│ │ └── JwtService.java
│ └── resources
│ └── application.properties
├── discovery-server
├── .gitignore
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── github
│ │ └── akhuntsaria
│ │ └── discoveryserver
│ │ └── DiscoveryServerApplication.java
│ └── resources
│ └── application.properties
├── protected-service
├── .gitignore
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── github
│ │ └── akhuntsaria
│ │ └── protectedservice
│ │ ├── ProtectedServiceApplication.java
│ │ └── controller
│ │ └── ProtectedController.java
│ └── resources
│ └── application.properties
└── request-examples.http
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**
5 | !**/src/test/**
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 |
30 | ### VS Code ###
31 | .vscode/
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Spring Boot API Gateway Demo
3 | This project demonstrates API gateway using microservices architecture, separate authentication service and service discovery.
4 |
5 | # Getting Started
6 | * Run discovery-server and other services
7 | * Run requests in request-examples.http
8 |
9 | # Architecture
10 | 
11 |
12 | # Services
13 | * **api-gateway**: Zuul edge service for routing
14 | * **discover-server**: Eureka server for service discovery
15 | * **auth-service**: JWT authentication service
16 | * **protected-service**: service with sensitive data
17 |
--------------------------------------------------------------------------------
/api-gateway/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/api-gateway/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.3.RELEASE
9 |
10 |
11 | com.github.akhuntsaria
12 | api-gateway
13 | 0.0.1-SNAPSHOT
14 | API Gateway
15 | API gateway for protected service
16 |
17 |
18 | 14
19 | 2.3.1
20 | 0.7.0
21 | 1.1.1.RELEASE
22 | 2.2.3.RELEASE
23 |
24 |
25 |
26 |
27 | io.jsonwebtoken
28 | jjwt
29 | ${jjwt.version}
30 |
31 |
32 | javax.xml.bind
33 | jaxb-api
34 | ${jaxb.version}
35 |
36 |
37 | org.glassfish.jaxb
38 | jaxb-runtime
39 | ${jaxb.version}
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-security
44 | ${spring-boot.version}
45 |
46 |
47 | org.springframework.cloud
48 | spring-cloud-starter-netflix-eureka-client
49 | ${spring-boot.version}
50 |
51 |
52 | org.springframework.cloud
53 | spring-cloud-starter-netflix-zuul
54 | ${spring-boot.version}
55 |
56 |
57 | org.springframework.security
58 | spring-security-jwt
59 | ${jwt.version}
60 |
61 |
62 |
63 |
64 |
65 |
66 | org.springframework.boot
67 | spring-boot-maven-plugin
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/ApiGatewayApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
6 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
7 |
8 | @EnableEurekaClient
9 | @EnableZuulProxy
10 | @SpringBootApplication
11 | public class ApiGatewayApplication {
12 |
13 | public static void main(String[] args) {
14 | SpringApplication.run(ApiGatewayApplication.class, args);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/config/AuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway.config;
2 |
3 | import com.github.akhuntsaria.apigateway.dto.JwtParseRequestDto;
4 | import com.github.akhuntsaria.apigateway.dto.JwtParseResponseDto;
5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
7 | import org.springframework.security.core.context.SecurityContextHolder;
8 | import org.springframework.stereotype.Component;
9 | import org.springframework.web.client.RestTemplate;
10 | import org.springframework.web.filter.OncePerRequestFilter;
11 |
12 | import javax.servlet.FilterChain;
13 | import javax.servlet.ServletException;
14 | import javax.servlet.http.HttpServletRequest;
15 | import javax.servlet.http.HttpServletResponse;
16 | import java.io.IOException;
17 | import java.util.Objects;
18 | import java.util.stream.Collectors;
19 |
20 | @Component
21 | public class AuthenticationFilter extends OncePerRequestFilter {
22 |
23 | public static final String HEADER = "Authorization";
24 |
25 | public static final String HEADER_VALUE_PREFIX = "Bearer";
26 |
27 | private static final String JWT_PARSE_URL = "http://auth-service/v1/jwt/parse";
28 |
29 | private final RestTemplate restTemplate;
30 |
31 | public AuthenticationFilter(RestTemplate restTemplate) {
32 | this.restTemplate = restTemplate;
33 | }
34 |
35 | @Override
36 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
37 | throws ServletException, IOException {
38 | String token = request.getHeader(HEADER);
39 |
40 | if (token != null) {
41 | token = token.replace(HEADER_VALUE_PREFIX + " ", "");
42 |
43 | try {
44 | JwtParseResponseDto responseDto = parseJwt(token);
45 |
46 | UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
47 | responseDto.getUsername(),
48 | null,
49 | responseDto.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
50 | );
51 | SecurityContextHolder.getContext().setAuthentication(auth);
52 | } catch (Exception ignore) {
53 | SecurityContextHolder.clearContext();
54 | }
55 | }
56 |
57 | filterChain.doFilter(request, response);
58 | }
59 |
60 | private JwtParseResponseDto parseJwt(String token) {
61 | JwtParseResponseDto responseDto = restTemplate.postForObject(JWT_PARSE_URL, new JwtParseRequestDto(token),
62 | JwtParseResponseDto.class);
63 |
64 | Objects.requireNonNull(responseDto);
65 | return responseDto;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/config/RestTemplateConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway.config;
2 |
3 | import org.springframework.cloud.client.loadbalancer.LoadBalanced;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.web.client.RestTemplate;
7 |
8 | @Configuration
9 | public class RestTemplateConfig {
10 |
11 | @Bean
12 | @LoadBalanced
13 | public RestTemplate restTemplate() {
14 | return new RestTemplate();
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/config/WebSecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway.config;
2 |
3 | import com.github.akhuntsaria.apigateway.model.UserRole;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
9 | import org.springframework.security.config.http.SessionCreationPolicy;
10 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
11 |
12 | import javax.servlet.http.HttpServletResponse;
13 |
14 | @EnableWebSecurity
15 | public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
16 |
17 | private final AuthenticationFilter authenticationFilter;
18 |
19 | public WebSecurityConfiguration(AuthenticationFilter authenticationFilter) {
20 | this.authenticationFilter = authenticationFilter;
21 | }
22 |
23 | @Override
24 | public void configure(final HttpSecurity http) throws Exception {
25 | http
26 | .csrf().disable()
27 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
28 | .and()
29 | .anonymous()
30 | .and()
31 | .exceptionHandling().authenticationEntryPoint((request, response, ex) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
32 | .and()
33 | .addFilterAfter(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
34 | .authorizeRequests()
35 | .antMatchers("/auth/**").permitAll()
36 | .anyRequest().hasRole(UserRole.USER.name());
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/dto/JwtParseRequestDto.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway.dto;
2 |
3 | public class JwtParseRequestDto {
4 |
5 | private String token;
6 |
7 | public JwtParseRequestDto() {
8 | }
9 |
10 | public JwtParseRequestDto(String token) {
11 | this.token = token;
12 | }
13 |
14 | public String getToken() {
15 | return token;
16 | }
17 |
18 | public void setToken(String token) {
19 | this.token = token;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/dto/JwtParseResponseDto.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway.dto;
2 |
3 | import java.util.List;
4 |
5 | public class JwtParseResponseDto {
6 |
7 | private String username;
8 |
9 | private List authorities;
10 |
11 | public JwtParseResponseDto() {
12 | }
13 |
14 | public JwtParseResponseDto(String username, List authorities) {
15 | this.username = username;
16 | this.authorities = authorities;
17 | }
18 |
19 | public String getUsername() {
20 | return username;
21 | }
22 |
23 | public void setUsername(String username) {
24 | this.username = username;
25 | }
26 |
27 | public List getAuthorities() {
28 | return authorities;
29 | }
30 |
31 | public void setAuthorities(List authorities) {
32 | this.authorities = authorities;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/api-gateway/src/main/java/com/github/akhuntsaria/apigateway/model/UserRole.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.apigateway.model;
2 |
3 | public enum UserRole {
4 | USER
5 | }
6 |
--------------------------------------------------------------------------------
/api-gateway/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | eureka.client.fetch-registry=true
2 | eureka.client.register-with-eureka=false
3 | server.port=8080
4 | spring.application.name=api-gateway
5 | zuul.routes.auth.path=/auth/**
6 | zuul.routes.auth.service-id=auth-service
7 | zuul.routes.api.path=/api/**
8 | zuul.routes.api.service-id=protected-service
9 | zuul.sensitive-headers=Cookie,Set-Cookie
--------------------------------------------------------------------------------
/auth-service/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/auth-service/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.3.RELEASE
9 |
10 |
11 | com.github.akhuntsaria
12 | auth-service
13 | 0.0.1-SNAPSHOT
14 | Auth Service
15 | Auth service for API gateway project
16 |
17 |
18 | 14
19 | 2.3.1
20 | 0.7.0
21 | 2.2.3.RELEASE
22 |
23 |
24 |
25 |
26 | io.jsonwebtoken
27 | jjwt
28 | ${jjwt.version}
29 |
30 |
31 | javax.xml.bind
32 | jaxb-api
33 | ${jaxb.version}
34 |
35 |
36 | org.glassfish.jaxb
37 | jaxb-runtime
38 | ${jaxb.version}
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-security
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-starter-web
47 |
48 |
49 | org.springframework.cloud
50 | spring-cloud-starter-netflix-eureka-client
51 | ${spring-boot.version}
52 |
53 |
54 |
55 |
56 |
57 |
58 | org.springframework.boot
59 | spring-boot-maven-plugin
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/AuthServiceApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
6 |
7 | @EnableEurekaClient
8 | @SpringBootApplication
9 | public class AuthServiceApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(AuthServiceApplication.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/config/JwtUsernamePasswordAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.config;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.github.akhuntsaria.authservice.dto.LoginDto;
5 | import io.jsonwebtoken.Jwts;
6 | import io.jsonwebtoken.SignatureAlgorithm;
7 | import org.springframework.security.authentication.AuthenticationManager;
8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9 | import org.springframework.security.core.Authentication;
10 | import org.springframework.security.core.AuthenticationException;
11 | import org.springframework.security.core.GrantedAuthority;
12 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
13 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
14 |
15 | import javax.servlet.FilterChain;
16 | import javax.servlet.http.HttpServletRequest;
17 | import javax.servlet.http.HttpServletResponse;
18 | import java.io.IOException;
19 | import java.time.Instant;
20 | import java.util.Collections;
21 | import java.util.Date;
22 | import java.util.stream.Collectors;
23 |
24 | public class JwtUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
25 |
26 | public static final String HEADER = "Authorization";
27 |
28 | public static final String HEADER_VALUE_PREFIX = "Bearer";
29 |
30 | private final String signingKey;
31 |
32 | public JwtUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager, String signingKey) {
33 | super(new AntPathRequestMatcher("/v1/login", "POST"));
34 | setAuthenticationManager(authenticationManager);
35 | this.signingKey = signingKey;
36 | }
37 |
38 | @Override
39 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
40 | throws AuthenticationException, IOException {
41 |
42 | LoginDto loginDto = new ObjectMapper().readValue(request.getInputStream(), LoginDto.class);
43 |
44 | return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(
45 | loginDto.getUsername(),
46 | loginDto.getPassword(),
47 | Collections.emptyList()
48 | ));
49 | }
50 |
51 | @Override
52 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
53 | Authentication auth) {
54 | Instant now = Instant.now();
55 |
56 | String token = Jwts.builder()
57 | .setSubject(auth.getName())
58 | .claim("authorities", auth.getAuthorities().stream()
59 | .map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
60 | .setIssuedAt(Date.from(now))
61 | .setExpiration(Date.from(now.plusSeconds(8 * 60 * 60))) // token expires in 8 hours
62 | .signWith(SignatureAlgorithm.HS256, signingKey.getBytes())
63 | .compact();
64 | response.addHeader(HEADER, HEADER_VALUE_PREFIX + " " + token);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/config/WebSecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.config;
2 |
3 | import com.github.akhuntsaria.authservice.model.UserRole;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10 | import org.springframework.security.config.http.SessionCreationPolicy;
11 | import org.springframework.security.crypto.factory.PasswordEncoderFactories;
12 | import org.springframework.security.crypto.password.PasswordEncoder;
13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
14 |
15 | import javax.servlet.http.HttpServletResponse;
16 |
17 | @EnableWebSecurity
18 | public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
19 |
20 | private final String signingKey;
21 |
22 | @Autowired
23 | public WebSecurityConfiguration(@Value("${security.jwt.signing-key}") String signingKey) {
24 | this.signingKey = signingKey;
25 | }
26 |
27 | @Autowired
28 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
29 | PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
30 | auth.inMemoryAuthentication()
31 | .withUser("username").password(encoder.encode("password")).roles(UserRole.USER.name());
32 | }
33 |
34 | @Override
35 | protected void configure(HttpSecurity httpSecurity) throws Exception {
36 | httpSecurity
37 | .csrf().disable()
38 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
39 | .and()
40 | .exceptionHandling()
41 | .authenticationEntryPoint((request, response, ex) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
42 | .and()
43 | .addFilterAfter(new JwtUsernamePasswordAuthenticationFilter(authenticationManager(), signingKey), UsernamePasswordAuthenticationFilter.class)
44 | .authorizeRequests()
45 | .antMatchers("/v1/login").permitAll()
46 | .antMatchers("/v1/jwt/parse").permitAll()
47 | .anyRequest().authenticated();
48 | }
49 | }
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/controller/JwtController.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.controller;
2 |
3 | import com.github.akhuntsaria.authservice.dto.ErrorDto;
4 | import com.github.akhuntsaria.authservice.dto.JwtParseRequestDto;
5 | import com.github.akhuntsaria.authservice.dto.JwtParseResponseDto;
6 | import com.github.akhuntsaria.authservice.service.JwtService;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | @RestController
14 | @RequestMapping("/v1/jwt")
15 | public class JwtController {
16 |
17 | private final Logger log = LoggerFactory.getLogger(JwtController.class);
18 |
19 | final JwtService jwtService;
20 |
21 | public JwtController(JwtService jwtService) {
22 | this.jwtService = jwtService;
23 | }
24 |
25 | /**
26 | * Validate and parse JWT
27 | */
28 | @RequestMapping(value = "/parse", method = RequestMethod.POST)
29 | public ResponseEntity> getSomeSensitiveData(@RequestBody JwtParseRequestDto requestDto) {
30 | try {
31 | JwtParseResponseDto jwtParseResponseDto = jwtService.parseJwt(requestDto.getToken());
32 | return new ResponseEntity<>(jwtParseResponseDto, HttpStatus.OK);
33 |
34 | } catch (Exception ex) {
35 | log.error("JWT parsing error: {}, token: {}", ex.getLocalizedMessage(), requestDto);
36 | ex.printStackTrace();
37 |
38 | return new ResponseEntity<>(new ErrorDto(ex.getLocalizedMessage()), HttpStatus.UNAUTHORIZED);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/dto/ErrorDto.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.dto;
2 |
3 | public class ErrorDto {
4 |
5 | private String message;
6 |
7 | public ErrorDto() {
8 | }
9 |
10 | public ErrorDto(String message) {
11 | this.message = message;
12 | }
13 |
14 | public String getMessage() {
15 | return message;
16 | }
17 |
18 | public void setMessage(String message) {
19 | this.message = message;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/dto/JwtParseRequestDto.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.dto;
2 |
3 | public class JwtParseRequestDto {
4 |
5 | private String token;
6 |
7 | public JwtParseRequestDto() {
8 | }
9 |
10 | public JwtParseRequestDto(String token) {
11 | this.token = token;
12 | }
13 |
14 | public String getToken() {
15 | return token;
16 | }
17 |
18 | public void setToken(String token) {
19 | this.token = token;
20 | }
21 |
22 | @Override
23 | public String toString() {
24 | return "JwtParseRequestDto{" +
25 | "token='" + token + '\'' +
26 | '}';
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/dto/JwtParseResponseDto.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.dto;
2 |
3 | import java.util.List;
4 |
5 | public class JwtParseResponseDto {
6 |
7 | private String username;
8 |
9 | private List authorities;
10 |
11 | public JwtParseResponseDto() {
12 | }
13 |
14 | public JwtParseResponseDto(String username, List authorities) {
15 | this.username = username;
16 | this.authorities = authorities;
17 | }
18 |
19 | public String getUsername() {
20 | return username;
21 | }
22 |
23 | public void setUsername(String username) {
24 | this.username = username;
25 | }
26 |
27 | public List getAuthorities() {
28 | return authorities;
29 | }
30 |
31 | public void setAuthorities(List authorities) {
32 | this.authorities = authorities;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/dto/LoginDto.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.dto;
2 |
3 | public class LoginDto {
4 |
5 | private String username;
6 |
7 | private String password;
8 |
9 | public LoginDto() {
10 | }
11 |
12 | public LoginDto(String username, String password) {
13 | this.username = username;
14 | this.password = password;
15 | }
16 |
17 | public String getUsername() {
18 | return username;
19 | }
20 |
21 | public void setUsername(String username) {
22 | this.username = username;
23 | }
24 |
25 | public String getPassword() {
26 | return password;
27 | }
28 |
29 | public void setPassword(String password) {
30 | this.password = password;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/model/UserRole.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.model;
2 |
3 | public enum UserRole {
4 | USER
5 | }
6 |
--------------------------------------------------------------------------------
/auth-service/src/main/java/com/github/akhuntsaria/authservice/service/JwtService.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.authservice.service;
2 |
3 | import com.github.akhuntsaria.authservice.dto.JwtParseResponseDto;
4 | import io.jsonwebtoken.Claims;
5 | import io.jsonwebtoken.Jwts;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.stereotype.Service;
9 |
10 | import java.util.List;
11 | import java.util.Objects;
12 | import java.util.Set;
13 |
14 | @Service
15 | public class JwtService {
16 |
17 | private final String signingKey;
18 |
19 | @Autowired
20 | public JwtService(@Value("${security.jwt.signing-key}") String signingKey) {
21 | this.signingKey = signingKey;
22 | }
23 |
24 | public JwtParseResponseDto parseJwt(String token) {
25 | Objects.requireNonNull(token);
26 |
27 | Claims claims = Jwts.parser()
28 | .setSigningKey(signingKey.getBytes())
29 | .parseClaimsJws(token)
30 | .getBody();
31 |
32 | String username = claims.getSubject();
33 | //noinspection unchecked
34 | List authorities = claims.get("authorities", List.class);
35 |
36 | return new JwtParseResponseDto(username, authorities);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/auth-service/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.application.name=auth-service
2 | security.jwt.signing-key=should-be-externally-configured
3 | server.port=8081
--------------------------------------------------------------------------------
/discovery-server/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**
5 | !**/src/test/**
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 |
30 | ### VS Code ###
31 | .vscode/
32 |
--------------------------------------------------------------------------------
/discovery-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.3.1.RELEASE
9 |
10 |
11 | com.github.akhuntsaria
12 | discovery-server
13 | 0.0.1-SNAPSHOT
14 | Discovery server
15 | Discovery server for Spring Boot microservices demo
16 |
17 |
18 | 14
19 | Hoxton.SR6
20 |
21 |
22 |
23 |
24 | org.springframework.cloud
25 | spring-cloud-starter-netflix-eureka-server
26 |
27 |
28 |
29 |
30 |
31 |
32 | org.springframework.cloud
33 | spring-cloud-dependencies
34 | ${spring-cloud.version}
35 | pom
36 | import
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-maven-plugin
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/discovery-server/src/main/java/com/github/akhuntsaria/discoveryserver/DiscoveryServerApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.discoveryserver;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
6 |
7 | @EnableEurekaServer
8 | @SpringBootApplication
9 | public class DiscoveryServerApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(DiscoveryServerApplication.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/discovery-server/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | eureka.client.register-with-eureka=false
2 | eureka.client.fetch-registry=false
3 | server.port=8761
4 | spring.application.name=discovery-server
--------------------------------------------------------------------------------
/protected-service/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**
5 | !**/src/test/**
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 |
30 | ### VS Code ###
31 | .vscode/
32 |
--------------------------------------------------------------------------------
/protected-service/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.3.RELEASE
9 |
10 |
11 | com.github.akhuntsaria
12 | protected-service
13 | 0.0.1-SNAPSHOT
14 | Protected Service
15 | Service which is protected by API gateway
16 |
17 |
18 | 14
19 | 2.2.3.RELEASE
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 | org.springframework.cloud
29 | spring-cloud-starter-netflix-eureka-client
30 | ${spring-boot.version}
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-maven-plugin
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/protected-service/src/main/java/com/github/akhuntsaria/protectedservice/ProtectedServiceApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.protectedservice;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
6 |
7 | @EnableEurekaClient
8 | @SpringBootApplication
9 | public class ProtectedServiceApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(ProtectedServiceApplication.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/protected-service/src/main/java/com/github/akhuntsaria/protectedservice/controller/ProtectedController.java:
--------------------------------------------------------------------------------
1 | package com.github.akhuntsaria.protectedservice.controller;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.http.MediaType;
5 | import org.springframework.http.ResponseEntity;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RequestMethod;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | import javax.servlet.http.HttpServletRequest;
11 |
12 | @RestController
13 | @RequestMapping("/v1/protected")
14 | public class ProtectedController {
15 |
16 | @RequestMapping(value = "", method = RequestMethod.GET)
17 | public ResponseEntity getSomeSensitiveData(HttpServletRequest request) {
18 | String linkWithSensitiveData = "https://youtu.be/s35rVw1zskA";
19 | String responseBody;
20 |
21 | // display link nicely in browser
22 | if (isRequestFromBrowser(request)) {
23 | responseBody = wrapLinkInHtmlTag(linkWithSensitiveData);
24 | } else {
25 | responseBody = linkWithSensitiveData;
26 | }
27 |
28 | return new ResponseEntity<>(responseBody, HttpStatus.OK);
29 | }
30 |
31 | // check if 'Accept' header in request contains passed content type
32 | private boolean acceptHeaderContainsContentType(HttpServletRequest request, String contentType) {
33 | String acceptHeader = request.getHeader("Accept");
34 |
35 | if (acceptHeader == null) {
36 | return false;
37 | }
38 |
39 | return acceptHeader.contains(contentType);
40 | }
41 |
42 | private boolean isRequestFromBrowser(HttpServletRequest request) {
43 | return acceptHeaderContainsContentType(request, MediaType.TEXT_HTML.toString());
44 | }
45 |
46 | private String wrapLinkInHtmlTag(String link) {
47 | return String.format("%s", link, link);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/protected-service/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.application.name=protected-service
2 | server.port=8082
--------------------------------------------------------------------------------
/request-examples.http:
--------------------------------------------------------------------------------
1 | ### Login
2 | POST http://localhost:8080/auth/v1/login
3 | Content-Type: application/json
4 |
5 | {
6 | "username": "username",
7 | "password": "password"
8 | }
9 |
10 | > {%
11 | client.global.set("authorizationHeaderValue", response.headers.valueOf("Authorization"));
12 | client.global.set("authorizationToken", response.headers.valueOf("Authorization").replace("Bearer ", ""));
13 | %}
14 |
15 | ### Access protected service
16 | GET http://localhost:8080/api/v1/protected
17 | Authorization: {{authorizationHeaderValue}}
18 |
19 | ### Parse JWT in auth-service
20 | POST http://localhost:8081/v1/jwt/parse
21 | Content-Type: application/json
22 |
23 | {
24 | "token": "{{authorizationToken}}"
25 | }
26 |
--------------------------------------------------------------------------------