From 9304aef2a974de7d4621e2378be94cd7bc3131a3 Mon Sep 17 00:00:00 2001 From: Matej Hrica <mtjhrc@gmail.com> Date: Sat, 27 May 2023 16:53:09 +0200 Subject: [PATCH] Add token-grabber - a confidential client for getting an access token --- pom.xml | 1 + token-grabber/pom.xml | 77 ++++++++++++++++++ .../pa165/oauth2/client/MainController.java | 60 ++++++++++++++ .../cz/muni/pa165/oauth2/client/WebApp.java | 63 ++++++++++++++ .../src/main/resources/application.yml | 45 ++++++++++ .../src/main/resources/messages.properties | 3 + .../src/main/resources/static/favicon.ico | Bin 0 -> 1150 bytes .../src/main/resources/static/robots.txt | 2 + .../src/main/resources/static/style.css | 4 + .../src/main/resources/templates/index.html | 14 ++++ .../src/main/resources/templates/layout.html | 24 ++++++ .../src/main/resources/templates/success.html | 31 +++++++ 12 files changed, 324 insertions(+) create mode 100644 token-grabber/pom.xml create mode 100644 token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java create mode 100644 token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java create mode 100644 token-grabber/src/main/resources/application.yml create mode 100644 token-grabber/src/main/resources/messages.properties create mode 100644 token-grabber/src/main/resources/static/favicon.ico create mode 100644 token-grabber/src/main/resources/static/robots.txt create mode 100644 token-grabber/src/main/resources/static/style.css create mode 100644 token-grabber/src/main/resources/templates/index.html create mode 100644 token-grabber/src/main/resources/templates/layout.html create mode 100644 token-grabber/src/main/resources/templates/success.html diff --git a/pom.xml b/pom.xml index 903a024..4fb280b 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ <module>user-client</module> <module>report-client</module> <module>weather-client</module> + <module>token-grabber</module> </modules> <packaging>pom</packaging> <name>airport manager parent</name> diff --git a/token-grabber/pom.xml b/token-grabber/pom.xml new file mode 100644 index 0000000..5d195d9 --- /dev/null +++ b/token-grabber/pom.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>cz.muni.fi.pa165</groupId> + <artifactId>airport-manager</artifactId> + <version>1.0-SNAPSHOT</version> + </parent> + + <artifactId>token-grabber</artifactId> + <packaging>jar</packaging> + <name>OAuth 2/OIDC Confidential Client for grabbing the token</name> + + <build> + <defaultGoal>spring-boot:run</defaultGoal> + <finalName>>token-grabber</finalName> + + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <!-- https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment.installing --> + <executable>true</executable> + </configuration> + </plugin> + <!-- run integration tests in "mvn verify" phase --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + </plugin> + </plugins> + </build> + + <dependencies> + <!-- Spring MVC --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <!-- Thymeleaf for HTML pages templates --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-thymeleaf</artifactId> + </dependency> + <!-- Thymeleaf layout --> + <dependency> + <groupId>nz.net.ultraq.thymeleaf</groupId> + <artifactId>thymeleaf-layout-dialect</artifactId> + </dependency> + <!-- for Spring application.yml properties handling --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <!-- OAuth2/OIDC client --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-oauth2-client</artifactId> + </dependency> + <!-- web jars for Bootstrap --> + <dependency> + <groupId>org.webjars</groupId> + <artifactId>webjars-locator-core</artifactId> + </dependency> + <dependency> + <groupId>org.webjars</groupId> + <artifactId>bootstrap</artifactId> + <version>5.2.3</version> + </dependency> + + </dependencies> + +</project> diff --git a/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java new file mode 100644 index 0000000..105f56d --- /dev/null +++ b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java @@ -0,0 +1,60 @@ +package cz.muni.pa165.oauth2.client; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + + +/** + * Spring MVC Controller. + * Handles HTTP requests by preparing data in model and passing it to Thymeleaf HTML templates. + */ +@Controller +public class MainController { + + private static final Logger log = LoggerFactory.getLogger(MainController.class); + + /** + * Home page accessible even to non-authenticated users. Displays user personal data. + */ + @GetMapping("/") + public String index(Model model, @AuthenticationPrincipal OidcUser user) { + log.debug("index() user {}", user == null ? "is anonymous" : user.getSubject()); + + // user is logged in + if (user != null) { + return "redirect:/success"; + } + + return "index"; + } + + @GetMapping("/success") + public String loggedIn(Model model, @AuthenticationPrincipal OidcUser user, + @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient oauth2Client) { + OAuth2AccessToken accessToken = oauth2Client.getAccessToken(); + + log.info("/success :"); + + var subject = user.getSubject(); + model.addAttribute("subject", subject); + log.info("subject: " + subject); + + var scopes = accessToken.getScopes(); + model.addAttribute("scopes", scopes); + log.info("scopes: " + scopes); + + String accessTokenValue = accessToken.getTokenValue(); + model.addAttribute("accessToken", accessTokenValue); + log.info("accessToken : " + accessTokenValue); + + return "success"; + } +} \ No newline at end of file diff --git a/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java new file mode 100644 index 0000000..63bd80b --- /dev/null +++ b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java @@ -0,0 +1,63 @@ +package cz.muni.pa165.oauth2.client; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.event.EventListener; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; + + +@SpringBootApplication +public class WebApp { + + private static final Logger log = LoggerFactory.getLogger(WebApp.class); + + public static void main(String[] args) { + SpringApplication.run(WebApp.class, args); + } + + /** + * Configuration of Spring Security. Sets up OAuth2/OIDC authentication + * for all URLS except a list of public ones. + */ + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .authorizeHttpRequests(x -> x + // allow anonymous access to listed URLs + .requestMatchers("/", "/error", "/robots.txt", "/style.css", "/favicon.ico", "/webjars/**").permitAll() + // all other requests must be authenticated + .anyRequest().authenticated() + ) + .oauth2Login(x -> x + .defaultSuccessUrl("/success") + ) + .logout(x -> x + .logoutSuccessUrl("/") + ) + .csrf(c -> c + //set CSRF token cookie "XSRF-TOKEN" with httpOnly=false that can be read by JavaScript + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + //replace the default XorCsrfTokenRequestAttributeHandler with one that can use value from the cookie + .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()) + ); + return httpSecurity.build(); + } + + /** + * Display a hint in the log. + */ + @EventListener + public void onApplicationEvent(final ServletWebServerInitializedEvent event) { + log.info("**************************"); + log.info("visit http://localhost:{}/", event.getWebServer().getPort()); + log.info("**************************"); + } + +} diff --git a/token-grabber/src/main/resources/application.yml b/token-grabber/src/main/resources/application.yml new file mode 100644 index 0000000..bafecd5 --- /dev/null +++ b/token-grabber/src/main/resources/application.yml @@ -0,0 +1,45 @@ +# TCP port for HTTP requests +server: + port: 8081 + +# OAuth client config +spring: + security: + oauth2: + client: + registration: + muni: + client-id: 7e02a0a9-446a-412d-ad2b-90add47b0fdd + client-secret: 48a2b2e3-4b2b-471e-b7b7-b81a85b6eeef22f347f2-3fc9-4e16-8698-3e2492701a89 + client-name: "MUNI Unified Login" + provider: muni + scope: + - openid + - test_1 + - test_2 + - test_3 + provider: + muni: + # URL to which .well-know/openid-configuration will be added to download metadata + issuer-uri: https://oidc.muni.cz/oidc/ + +# logging config to see interesting things happening +logging: + pattern: + console: '%clr(%d{HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(%logger){blue} %clr(:){red} %m %n' + level: + root: info + cz.muni: debug + org.springframework.web.client.RestTemplate: debug + org.springframework.security: debug + org.springframework.security.web.DefaultSecurityFilterChain: warn + org.springframework.security.web.context.HttpSessionSecurityContextRepository: info + org.springframework.security.web.FilterChainProxy: info + org.springframework.security.web.authentication.AnonymousAuthenticationFilter: info + org.springframework.security.config.annotation.authentication.configuration: info + org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext: warn + org.springframework.boot.web.embedded.tomcat: warn + org.apache.catalina.core: warn + + + diff --git a/token-grabber/src/main/resources/messages.properties b/token-grabber/src/main/resources/messages.properties new file mode 100644 index 0000000..3b98c76 --- /dev/null +++ b/token-grabber/src/main/resources/messages.properties @@ -0,0 +1,3 @@ +index.title=Airport Manager Login +index.body.annon=You are not logged in. Please log in using your IS MUNI account. +index.body.authuser=You are logged in now. \ No newline at end of file diff --git a/token-grabber/src/main/resources/static/favicon.ico b/token-grabber/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..43c88af640c084c0aa91fcdae47e5124bca566f2 GIT binary patch literal 1150 zcmaiz!7GDt7{?#IN;xnoSB`R1ZVpZk{sbwm%S0>TqPe+fXeM!RS}Bo}q84&+adMF& z)5@f<BD2h1pJ(s;+g@+8ZGHAUzwh(?{`TAZz7uKS9S8`YT@q{*=@5}_KtgeJiU_m+ zURE^U14&qf7Qat5n1C&4uQG2#e*_zHFl#*YgKz}zp!1NKc`JI%ovTOrf*r`aYt@P{ z;?0>Ject0Ud-&@Io*|R*R`lsoewFXuhjoa-4s3vPrU<pun=?In7>btWui+W);03n9 z$sZWMw=xUDZppKP_X@HuPt;9t@><7DmfnJ4cm>;9h9ta!$!pc~=)RRbCUp<iQ;;s} zdiF!O1U<KAZ{Q5{dQ|e-Ct%N~IUYU7IoyNp`8Q|Q-{`#%f*H{J{{VWAZ7U9ma`p!; z1IM8Ec@DznI+$vml&;4pv&uP~evO$v!%fCptKz#Y?-9+X=*MdL{WJXfo6PTw(>MD) z_10uQLH|qr^}N;Q<oaN#+RSK~oJjgYB*j(9Dy>e{^{FMY;2L#Z;HfVRh!mBGhz-94 CZI5IC literal 0 HcmV?d00001 diff --git a/token-grabber/src/main/resources/static/robots.txt b/token-grabber/src/main/resources/static/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/token-grabber/src/main/resources/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/token-grabber/src/main/resources/static/style.css b/token-grabber/src/main/resources/static/style.css new file mode 100644 index 0000000..439f08b --- /dev/null +++ b/token-grabber/src/main/resources/static/style.css @@ -0,0 +1,4 @@ +h1#title a { + text-decoration: none; + color: black; +} diff --git a/token-grabber/src/main/resources/templates/index.html b/token-grabber/src/main/resources/templates/index.html new file mode 100644 index 0000000..1c0324f --- /dev/null +++ b/token-grabber/src/main/resources/templates/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org/" + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" + layout:decorate="~{layout.html}" th:with="title=${#messages.msg('index.title')}"> +<body> +<th:block layout:fragment="body"> + <p th:text="#{index.body.annon}">text about anonymous user</p> + <p> + <form method="post" th:action="@{/oauth2/authorization/muni}"> + <button class="btn btn-outline-primary" type="submit">Login MUNI</button> + </form> +</th:block> +</body> +</html> \ No newline at end of file diff --git a/token-grabber/src/main/resources/templates/layout.html b/token-grabber/src/main/resources/templates/layout.html new file mode 100644 index 0000000..a982cc3 --- /dev/null +++ b/token-grabber/src/main/resources/templates/layout.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html lang="en" th:lang="${#locale.language}" + xmlns:th="http://www.thymeleaf.org/" + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="SHORTCUT ICON" th:href="@{/favicon.ico}"> + <link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}"/> + <link rel="stylesheet" type="text/css" th:href="@{/style.css}"/> + <script th:src="@{/webjars/jquery/jquery.min.js}"></script> + <script th:src="@{/webjars/js-cookie/js.cookie.min.js}"></script> + <script th:src="@{/webjars/bootstrap/js/bootstrap.bundle.min.js}"></script> + <title th:text="${title}">Page title</title> +</head> +<body> +<div class="container"> + <h1 id="title"><a th:href="@{/}" th:text="${title}">Page title</a></h1> + <th:block layout:fragment="body"> + some placeholder text in the page body + </th:block> +</div> +</body> +</html> \ No newline at end of file diff --git a/token-grabber/src/main/resources/templates/success.html b/token-grabber/src/main/resources/templates/success.html new file mode 100644 index 0000000..c527b1a --- /dev/null +++ b/token-grabber/src/main/resources/templates/success.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org/" + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" + layout:decorate="~{layout.html}" th:with="title=${#messages.msg('index.title')}"> +<body> +<th:block layout:fragment="body"> + <p>You have successfully logged in! Here is your information: + <p> + <table class="table"> + <tbody> + <tr> + <th scope="row">subject</th> + <td th:text="${subject}"></td> + </tr> + <tr> + <th scope="row">scopes</th> + <td th:text="${scopes}"></td> + </tr> + <tr> + <th scope="row">accessToken</th> + <td> <textarea readonly th:text="${accessToken}"></textarea></td> + </tr> + </tbody> + </table> + <p> + <form method="post" th:action="@{/logout}"> + <button class="btn btn-outline-primary" type="submit">Logout</button> + </form> +</th:block> +</body> +</html> \ No newline at end of file -- GitLab