0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

feat(console): add spring boot integration guide (#5740)

* feat(console): add spring boot integration guide

add spring boot integration guide

* chore: add changeset

add changeset

* chore: fix changeset typo

* fix(console): update the spring boot guide description

update the spring boot guide description

* chore(console): remove extra empty space

remove extra empty space
This commit is contained in:
simeng-li 2024-04-23 10:37:54 +08:00 committed by GitHub
parent 30aec5dd5f
commit 9cf03c8edb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 360 additions and 23 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/console": patch
---
Add Java Spring Boot web integration guide to the application creation page

View file

@ -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<Guide[]> = 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',

View file

@ -0,0 +1,330 @@
import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
<Steps>
<Step title="Get started">
This tutorial will show you how to integrate Logto into your Java Spring Boot web application.
<ul>
<li>
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.
</li>
<li>
The sample uses the [Spring Security
OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2) library to handle OIDC
authentication and integrate with Logto.
</li>
</ul>
Before we begin, make sure you have went through the spring boot guides linked above.
</Step>
<Step title="Add dependencies">
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
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
```
</Step>
<Step title="Configure Logto OAuth2 Client registration">
Register your application with Logto to get the client credentials and IdP configurations.
Add the following configuration to your `application.properties` file:
<pre>
<code className="language-properties">
{`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
`}
</code>
</pre>
</Step>
<Step title="Setup the redirect URI in Logto">
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.
<UriInputField name="redirectUris" />
e.g. In our example, the redirect URI is `http://localhost:8080/login/oauth2/code/logto`.
</Step>
<Step title="Implement the WebSecurityConfig">
#### 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<ClientRegistration> 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();
}
}
```
</Step>
<Step title="Create the home page">
(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
<body>
<h1>Welcome!</h1>
<p><a th:href="@{/oauth2/authorization/logto}">Login with Logto</a></p>
</body>
```
</Step>
<Step title="Get user info">
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<String, Object> 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
<body>
<h1>User Details</h1>
<div>
<p>
<div><strong>name:</strong> <span th:text="${username}"></span></div>
<div><strong>email:</strong> <span th:text="${email}"></span></div>
<div><strong>id:</strong> <span th:text="${sub}"></span></div>
</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
</form>
</body>
```
</Step>
</Steps>

View file

@ -0,0 +1,16 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = 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;

View file

@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 767.8 768" width="48" height="48"><style>.st0{fill:#77bc1f}</style><path class="st0" d="M698.3 40c-10.8 25.8-24.5 50.3-41 72.8C585.1 40.6 487.1 0 385 0 173.8 0 0 174 0 385.5 0 491 43.2 592 119.6 664.8l14.2 12.6c69.4 58.5 157.3 90.7 248 90.7 200.8 0 369.6-157.4 383.9-358 10.5-98.2-18.3-222.4-67.4-370.1zm-524 627c-6.2 7.7-15.7 12.2-25.6 12.2-18.1 0-32.9-14.9-32.9-33s14.9-33 32.9-33c7.5 0 14.9 2.6 20.7 7.4 14.1 11.4 16.3 32.3 4.9 46.4zm522.4-115.4c-95 126.7-297.9 84-428 90.1 0 0-23.1 1.4-46.3 5.2 0 0 8.7-3.7 20-8 91.3-31.8 134.5-38 190-66.5 104.5-53.2 207.8-169.6 229.3-290.7C621.9 398.2 501.3 498.3 391.4 539c-75.3 27.8-211.3 54.8-211.3 54.8l-5.5-2.9C82 545.8 79.2 345.1 247.5 280.3c73.7-28.4 144.2-12.8 223.8-31.8 85-20.2 183.3-84 223.3-167.2 44.8 133.1 98.7 341.5 2.1 470.3z"/></svg>

After

Width:  |  Height:  |  Size: 856 B

View file

@ -1,12 +0,0 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = 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;

View file

@ -1,11 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_129_35349)">
<path d="M19.3484 33.2862C19.3484 33.2862 18.0415 34.081 20.2568 34.3084C22.9237 34.6484 24.3443 34.5921 27.2959 34.0247C27.8891 34.388 28.5165 34.6922 29.1691 34.9331C22.5285 37.7721 14.1252 34.7631 19.3484 33.2862ZM18.4984 29.5962C18.4984 29.5962 17.0789 30.6746 19.2932 30.9031C22.1885 31.1867 24.459 31.2431 28.3775 30.4494C28.7586 30.8359 29.2252 31.1274 29.7397 31.3004C21.7348 33.6847 12.7652 31.5267 18.4984 29.5962ZM34.1671 36.0668C34.1671 36.0668 35.1319 36.8616 33.0887 37.4863C29.2849 38.6232 17.1363 38.9632 13.7299 37.4863C12.5378 36.9763 14.8083 36.2379 15.5362 36.1242C16.2746 35.9542 16.673 35.9542 16.673 35.9542C15.3662 35.0457 7.9871 37.8274 12.9267 38.6211C26.4948 40.8353 37.6787 37.6563 34.1586 36.0668H34.1671ZM19.9625 25.7351C19.9625 25.7351 13.7745 27.2119 17.7483 27.7219C19.4515 27.9493 22.8005 27.8919 25.9232 27.6656C28.4774 27.4382 31.0338 26.9856 31.0338 26.9856C31.0338 26.9856 30.1253 27.383 29.5006 27.7804C23.2 29.4272 11.1066 28.6888 14.5703 26.9856C17.522 25.5661 19.9636 25.7372 19.9636 25.7372L19.9625 25.7351ZM31.0338 31.9231C37.3918 28.6293 34.4402 25.4524 32.3959 25.8487C31.8859 25.9624 31.6575 26.0761 31.6575 26.0761C31.6575 26.0761 31.8275 25.7361 32.2248 25.6224C36.256 24.2029 39.435 29.8799 30.918 32.0931C30.918 32.0931 30.9743 32.0367 31.0317 31.9231H31.0338ZM20.5894 40.8927C26.72 41.2901 36.0881 40.6653 36.3144 37.7689C36.3144 37.7689 35.8607 38.9058 31.2622 39.7558C26.039 40.7206 19.5673 40.6058 15.7635 39.9832C15.7635 39.9832 16.5583 40.6642 20.5894 40.8927Z" fill="#4E7896"/>
<path d="M27.1834 7.00104C27.1834 7.00104 30.7034 10.5774 23.8344 15.9707C18.3274 20.3418 22.5859 22.8397 23.8344 25.6787C20.598 22.7834 18.2711 20.2281 19.8606 17.8449C22.1875 14.3238 28.6029 12.6312 27.1834 7.00104ZM25.3771 23.2945C27.024 25.1677 24.9234 26.8708 24.9234 26.8708C24.9234 26.8708 29.1245 24.714 27.194 22.045C25.4345 19.4907 24.0702 18.2412 31.4514 13.9838C31.4514 13.9838 19.8139 16.8791 25.3771 23.2945Z" fill="#F58219"/>
</g>
<defs>
<clipPath id="clip0_129_35349">
<rect width="34" height="34" fill="white" transform="translate(7 7)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB