diff --git a/.changeset/soft-stingrays-beam.md b/.changeset/soft-stingrays-beam.md new file mode 100644 index 00000000000..8afcdf71d42 --- /dev/null +++ b/.changeset/soft-stingrays-beam.md @@ -0,0 +1,5 @@ +--- +"@logto/console": patch +--- + +Add Java Spring Boot web integration guide to the application creation page diff --git a/packages/console/src/assets/docs/guides/index.ts b/packages/console/src/assets/docs/guides/index.ts index fd12ff800c5..39b9e0e765f 100644 --- a/packages/console/src/assets/docs/guides/index.ts +++ b/packages/console/src/assets/docs/guides/index.ts @@ -24,6 +24,7 @@ import webDotnetCoreMvc from './web-dotnet-core-mvc/index'; import webExpress from './web-express/index'; import webGo from './web-go/index'; import webGptPlugin from './web-gpt-plugin/index'; +import webJavaSpringBoot from './web-java-spring-boot/index'; import webNext from './web-next/index'; import webNextAppRouter from './web-next-app-router/index'; import webNextServerActions from './web-next-server-actions/index'; @@ -105,6 +106,13 @@ const guides: Readonly = Object.freeze([ Component: lazy(async () => import('./web-go/README.mdx')), metadata: webGo, }, + { + order: 1.4, + id: 'web-java-spring-boot', + Logo: lazy(async () => import('./web-java-spring-boot/logo.svg')), + Component: lazy(async () => import('./web-java-spring-boot/README.mdx')), + metadata: webJavaSpringBoot, + }, { order: 1.5, id: 'web-gpt-plugin', diff --git a/packages/console/src/assets/docs/guides/web-java-spring-boot/README.mdx b/packages/console/src/assets/docs/guides/web-java-spring-boot/README.mdx new file mode 100644 index 00000000000..7019654e233 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-java-spring-boot/README.mdx @@ -0,0 +1,330 @@ +import UriInputField from '@/mdx-components/UriInputField'; +import Steps from '@/mdx-components/Steps'; +import Step from '@/mdx-components/Step'; + + + + + This tutorial will show you how to integrate Logto into your Java Spring Boot web application. + +
    +
  • + The sample was created using the Spring Boot [securing web + starter](https://spring.io/guides/gs/securing-web). Following the instructions to bootstrap a + new web application. +
  • +
  • + The sample uses the [Spring Security + OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2) library to handle OIDC + authentication and integrate with Logto. +
  • +
+ +Before we begin, make sure you have went through the spring boot guides linked above. + +
+ + + Include the following dependencies in your `build.gradle` file: + +```gradle +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' +} +``` + +The sample uses [gradle](https://spring.io/guides/gs/gradle) as the build tool. You can use +maven or any other build tool as well. The configurations might be slightly different. + +For maven, include the following dependencies in your `pom.xml` file: + +```maven + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-client + +``` + + + + + +Register your application with Logto to get the client credentials and IdP configurations. +Add the following configuration to your `application.properties` file: + +
+  
+    {`spring.security.oauth2.client.registration.logto.client-name=logto
+spring.security.oauth2.client.registration.logto.client-id=${props.app.id}
+spring.security.oauth2.client.registration.logto.client-secret=${props.app.secret}
+spring.security.oauth2.client.registration.logto.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
+spring.security.oauth2.client.registration.logto.authorization-grant-type=authorization_code
+spring.security.oauth2.client.registration.logto.scope=openid,profile,email,offline_access
+spring.security.oauth2.client.registration.logto.provider=logto
+
+spring.security.oauth2.client.provider.logto.issuer-uri=${props.endpoint}oidc
+spring.security.oauth2.client.provider.logto.authorization-uri=${props.endpoint}oidc/auth
+spring.security.oauth2.client.provider.logto.jwk-set-uri=${props.endpoint}oidc/jwks
+  `}
+  
+
+ +
+ + + +In order to redirect users back to your application after they sign in, you need to set the redirect URI using the `client.registration.logto.redirect-uri` property in the previous step. + + + +e.g. In our example, the redirect URI is `http://localhost:8080/login/oauth2/code/logto`. + + + + + +#### Create a new class `WebSecurityConfig` in your project: + +```java +package com.example.securingweb; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@Configuration +@EnableWebSecurity + +public class WebSecurityConfig { + // ... +} +``` + +#### Create a idTokenDecoderFactory bean to set the JWS algorithm to `ES384`: + +This is required because Logto uses ES384 as the default algorithm, we need to update the OidcIdTokenDecoderFactory to use the same algorithm. + +```java +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; + +public class WebSecurityConfig { + // ... + + @Bean + public JwtDecoderFactory idTokenDecoderFactory() { + OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory(); + idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> SignatureAlgorithm.ES384); + return idTokenDecoderFactory; + } +} +``` + +#### Create a LoginSuccessHandler class to handle the login success event: + +Redirect the user to the user page after successful login: + +```java +package com.example.securingweb; + +import java.io.IOException; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class CustomSuccessHandler implements AuthenticationSuccessHandler { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + response.sendRedirect("/user"); + } +} +``` + +#### Create a LogoutSuccessHandler class to handle the logout success event: + +Clear the session and redirect the user to the home page. + +```java +package com.example.securingweb; + +import java.io.IOException; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +public class CustomLogoutHandler implements LogoutSuccessHandler { + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + HttpSession session = request.getSession(); + + if (session != null) { + session.invalidate(); + } + + response.sendRedirect("/home"); + } +} +``` + +#### Create a `securityFilterChain` bean to configure the security configuration: + +Add the following code to complete the `WebSecurityConfig` class: + +```java +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; + +public class WebSecurityConfig { + // ... + + @Bean + public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/", "/home").permitAll() // Allow access to the home page + .anyRequest().authenticated() // All other requests require authentication + ) + .oauth2Login(oauth2Login -> + oauth2Login + .successHandler(new CustomSuccessHandler()) + ) + .logout(logout -> + logout + .logoutSuccessHandler(new CustomLogoutHandler()) + ); + return http.build(); + } +} +``` + + + + + +(You may skip this step if you already have a home page in your project) + +HomeController.java: + +```java +package com.example.securingweb; + +import java.security.Principal; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class HomeController { + @GetMapping({ "/", "/home" }) + public String home(Principal principal) { + return principal != null ? "redirect:/user" : "home"; + } +} +``` + +This controller will redirect the user to the user page if the user is authenticated, otherwise, it will show the home page. + +home.html: + +```html + +

Welcome!

+ +

Login with Logto

+ +``` + +
+ + + +Create a new controller to handle the user page: + +```java +package com.example.securingweb; + +import java.security.Principal; +import java.util.Map; + +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/user") +public class UserController { + + @GetMapping + public String user(Model model, Principal principal) { + if (principal instanceof OAuth2AuthenticationToken) { + OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal; + OAuth2User oauth2User = token.getPrincipal(); + Map attributes = oauth2User.getAttributes(); + + model.addAttribute("username", attributes.get("username")); + model.addAttribute("email", attributes.get("email")); + model.addAttribute("sub", attributes.get("sub")); + } + + return "user"; + } +} +``` + +Read the user information from the `OAuth2User` object and pass it to the `user.html` template. + +user.html: + +```html + +

User Details

+
+

+

name:
+
email:
+
id:
+

+
+ +
+ +
+ +``` + +
+ +
diff --git a/packages/console/src/assets/docs/guides/web-java/config.json b/packages/console/src/assets/docs/guides/web-java-spring-boot/config.json similarity index 100% rename from packages/console/src/assets/docs/guides/web-java/config.json rename to packages/console/src/assets/docs/guides/web-java-spring-boot/config.json diff --git a/packages/console/src/assets/docs/guides/web-java-spring-boot/index.ts b/packages/console/src/assets/docs/guides/web-java-spring-boot/index.ts new file mode 100644 index 00000000000..cf1e468a2cb --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-java-spring-boot/index.ts @@ -0,0 +1,16 @@ +import { ApplicationType } from '@logto/schemas'; + +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = Object.freeze({ + name: 'Java Spring Boot Web', + description: + 'Spring Boot is a web framework for Java that enables developers to build secure, fast, and scalable server applications with the Java programming language.', + target: ApplicationType.Traditional, + sample: { + repo: 'spring-boot-sample', + path: '', + }, +}); + +export default metadata; diff --git a/packages/console/src/assets/docs/guides/web-java-spring-boot/logo.svg b/packages/console/src/assets/docs/guides/web-java-spring-boot/logo.svg new file mode 100644 index 00000000000..d7256ddcf41 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-java-spring-boot/logo.svg @@ -0,0 +1 @@ + diff --git a/packages/console/src/assets/docs/guides/web-java/index.ts b/packages/console/src/assets/docs/guides/web-java/index.ts deleted file mode 100644 index 61b56a2be49..00000000000 --- a/packages/console/src/assets/docs/guides/web-java/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApplicationType } from '@logto/schemas'; - -import { type GuideMetadata } from '../types'; - -const metadata: Readonly = Object.freeze({ - name: 'Java Web', - description: - 'Java Web is a web framework for Java that enables developers to build secure, fast, and scalable server applications with the Java programming language.', - target: ApplicationType.Traditional, -}); - -export default metadata; diff --git a/packages/console/src/assets/docs/guides/web-java/logo.svg b/packages/console/src/assets/docs/guides/web-java/logo.svg deleted file mode 100644 index cb4151622a5..00000000000 --- a/packages/console/src/assets/docs/guides/web-java/logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - -