Skip to content

Commit 42859a4

Browse files
committedJul 16, 2021
first commit
0 parents  commit 42859a4

29 files changed

+1624
-0
lines changed
 

‎.gitignore

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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/
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2007-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import java.net.*;
18+
import java.io.*;
19+
import java.nio.channels.*;
20+
import java.util.Properties;
21+
22+
public class MavenWrapperDownloader {
23+
24+
private static final String WRAPPER_VERSION = "0.5.6";
25+
/**
26+
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
27+
*/
28+
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
29+
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
30+
31+
/**
32+
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
33+
* use instead of the default one.
34+
*/
35+
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
36+
".mvn/wrapper/maven-wrapper.properties";
37+
38+
/**
39+
* Path where the maven-wrapper.jar will be saved to.
40+
*/
41+
private static final String MAVEN_WRAPPER_JAR_PATH =
42+
".mvn/wrapper/maven-wrapper.jar";
43+
44+
/**
45+
* Name of the property which should be used to override the default download url for the wrapper.
46+
*/
47+
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
48+
49+
public static void main(String args[]) {
50+
System.out.println("- Downloader started");
51+
File baseDirectory = new File(args[0]);
52+
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
53+
54+
// If the maven-wrapper.properties exists, read it and check if it contains a custom
55+
// wrapperUrl parameter.
56+
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
57+
String url = DEFAULT_DOWNLOAD_URL;
58+
if (mavenWrapperPropertyFile.exists()) {
59+
FileInputStream mavenWrapperPropertyFileInputStream = null;
60+
try {
61+
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
62+
Properties mavenWrapperProperties = new Properties();
63+
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
64+
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
65+
} catch (IOException e) {
66+
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
67+
} finally {
68+
try {
69+
if (mavenWrapperPropertyFileInputStream != null) {
70+
mavenWrapperPropertyFileInputStream.close();
71+
}
72+
} catch (IOException e) {
73+
// Ignore ...
74+
}
75+
}
76+
}
77+
System.out.println("- Downloading from: " + url);
78+
79+
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
80+
if (!outputFile.getParentFile().exists()) {
81+
if (!outputFile.getParentFile().mkdirs()) {
82+
System.out.println(
83+
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
84+
}
85+
}
86+
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
87+
try {
88+
downloadFileFromURL(url, outputFile);
89+
System.out.println("Done");
90+
System.exit(0);
91+
} catch (Throwable e) {
92+
System.out.println("- Error downloading");
93+
e.printStackTrace();
94+
System.exit(1);
95+
}
96+
}
97+
98+
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
99+
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
100+
String username = System.getenv("MVNW_USERNAME");
101+
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
102+
Authenticator.setDefault(new Authenticator() {
103+
@Override
104+
protected PasswordAuthentication getPasswordAuthentication() {
105+
return new PasswordAuthentication(username, password);
106+
}
107+
});
108+
}
109+
URL website = new URL(urlString);
110+
ReadableByteChannel rbc;
111+
rbc = Channels.newChannel(website.openStream());
112+
FileOutputStream fos = new FileOutputStream(destination);
113+
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
114+
fos.close();
115+
rbc.close();
116+
}
117+
118+
}

‎.mvn/wrapper/maven-wrapper.jar

49.5 KB
Binary file not shown.

‎.mvn/wrapper/maven-wrapper.properties

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
2+
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

‎README.md

+391
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
[TOC]
2+
3+
# 在Spring Boot应用中集成Keycloak
4+
5+
6+
7+
## 前言
8+
9+
本文描述了在Spring Boot应用中通过Spring Security集成Keycloak来实现用认证和鉴权。
10+
11+
12+
13+
工具和环境:
14+
15+
- Spring Boot 2.4.0
16+
- Spring Security
17+
- Spring Boot Thymeleaf
18+
- Keycloak 12.0.1
19+
20+
21+
22+
## 引入依赖
23+
24+
25+
26+
### Spring Security依赖
27+
28+
```xml
29+
<dependency>
30+
<groupId>org.springframework.security</groupId>
31+
<artifactId>spring-security-test</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.keycloak</groupId>
36+
<artifactId>keycloak-spring-boot-starter</artifactId>
37+
</dependency>
38+
```
39+
40+
41+
42+
### Keycloak依赖
43+
44+
```xml
45+
<dependency>
46+
<groupId>org.keycloak</groupId>
47+
<artifactId>keycloak-spring-boot-starter</artifactId>
48+
</dependency>
49+
50+
<dependencyManagement>
51+
<dependencies>
52+
<dependency>
53+
<groupId>org.keycloak.bom</groupId>
54+
<artifactId>keycloak-adapter-bom</artifactId>
55+
<version>12.0.1</version>
56+
<type>pom</type>
57+
<scope>import</scope>
58+
</dependency>
59+
</dependencies>
60+
</dependencyManagement>
61+
```
62+
63+
64+
65+
### Thymeleaf依赖
66+
67+
```xml
68+
<dependency>
69+
<groupId>org.springframework.boot</groupId>
70+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
71+
</dependency>
72+
<dependency>
73+
<groupId>org.thymeleaf.extras</groupId>
74+
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
75+
</dependency>
76+
```
77+
78+
79+
80+
81+
## 安装Keycloak
82+
83+
84+
85+
以Docker方式安装Keycloak:
86+
87+
88+
89+
```bash
90+
#!/bin/bash
91+
92+
# Create a user defined network
93+
docker network create keycloak-network
94+
95+
# Start a MySQL instance
96+
docker run --name keycloak-mysql \
97+
-d \
98+
--net keycloak-network \
99+
-e MYSQL_DATABASE=keycloak \
100+
-e MYSQL_USER=keycloak \
101+
-e MYSQL_PASSWORD=keycloak123 \
102+
-e MYSQL_ROOT_PASSWORD=keycloak123 \
103+
mysql:8.0
104+
105+
# Start a Keycloak instance
106+
docker run --name keycloak \
107+
-d \
108+
--net keycloak-network \
109+
-p 8180:8080 \
110+
-e DB_VENDOR=mysql \
111+
-e DB_ADDR=keycloak-mysql \
112+
-e DB_DATABASE=keycloak \
113+
-e DB_USER=keycloak \
114+
-e DB_PASSWORD=keycloak123 \
115+
-e KEYCLOAK_USER=admin \
116+
-e KEYCLOAK_PASSWORD=admin \
117+
quay.io/keycloak/keycloak:12.0.1
118+
119+
# check logs
120+
# docker logs -f keycloak
121+
```
122+
123+
124+
125+
说明:
126+
127+
- 采用MySQL来持久化Keycloak配置。
128+
- 设置Keycloak的端口为`8180`
129+
130+
131+
132+
参见:
133+
134+
- <https://www.keycloak.org/getting-started/getting-started-docker>
135+
- <https://hub.docker.com/r/jboss/keycloak/>
136+
137+
138+
139+
## 在Keycloak上配置
140+
141+
在Keycloak上新建Realm、Client、Role和User:
142+
143+
- 创建一个新Realm - `xdevops`
144+
- 在该Realm下创建一个Client
145+
- Client ID - `springboot-keycloak-demo`
146+
- Root URL - `http://localhost:8080/` (对应的Valid Redirect URL为`http://localhost:8080/*`)
147+
- 在该Realm下创建两个Role
148+
- `admin` - 管理员
149+
- `user` - 普通用户
150+
-`admin` 角色下创建`william`用户,在`user`角色下创建`john`用户。
151+
152+
153+
154+
参见:
155+
156+
- <https://www.keycloak.org/getting-started/getting-started-docker>
157+
158+
159+
160+
## 构建Spring Boot应用
161+
162+
### 配置Keycloak属性
163+
164+
`application.yaml`中配置Keycloak属性:
165+
166+
```yaml
167+
keycloak:
168+
# the name of the realm, required
169+
realm: xdevops
170+
# the client-id of the application, required
171+
resource: springboot-keycloak-demo
172+
# the base URL of the Keycloak server, required
173+
auth-server-url: http://localhost:8180/auth
174+
# establishes if communications with the Keycloak server must happen over HTTPS
175+
# set to external, meaning that it's only needed for external requests (default value)
176+
# In production, instead, we should set it to all. Optional
177+
ssl-required: external
178+
# prevents the application from sending credentials to the Keycloak server (false is the default value)
179+
# set it to true whenever we use public clients instead of confidential
180+
public-client: true
181+
# the attribute with which to populate the UserPrincipal name
182+
principal-attribute: preferred_username
183+
184+
```
185+
186+
187+
188+
说明:
189+
190+
- `realm` 为上面创建的Relam。
191+
192+
- `resource `为上面创建的Client ID。
193+
194+
- `auth-server-url` 为Keycloak server的auth url。
195+
196+
- 默认创建的Client的Access Type为`public`,所以这里设置`public-client``true`
197+
198+
- `principal-attribute: preferred_username` 表示用Keycloak User的`preferred_username` 属性作为Spring Security Principal的`name`
199+
200+
201+
202+
### Keycloak安全配置
203+
204+
205+
206+
创建一个`SecurityConfig`类:
207+
208+
209+
210+
```java
211+
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
212+
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
213+
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
214+
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
215+
import org.springframework.beans.factory.annotation.Autowired;
216+
import org.springframework.context.annotation.Bean;
217+
import org.springframework.context.annotation.ComponentScan;
218+
import org.springframework.context.annotation.Configuration;
219+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
220+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
221+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
222+
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
223+
import org.springframework.security.core.session.SessionRegistryImpl;
224+
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
225+
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
226+
227+
@Configuration
228+
@EnableWebSecurity
229+
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
230+
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
231+
232+
@Override
233+
protected void configure(HttpSecurity http) throws Exception {
234+
super.configure(http);
235+
http.authorizeRequests()
236+
.antMatchers("/manager").hasRole("admin")
237+
.antMatchers("/books").hasAnyRole("user", "admin")
238+
.anyRequest().permitAll();
239+
}
240+
241+
/**
242+
* Make sure roles are not prefixed with ROLE_.
243+
* @param builder
244+
*/
245+
@Autowired
246+
public void configureGlobal(AuthenticationManagerBuilder builder) {
247+
KeycloakAuthenticationProvider provider = keycloakAuthenticationProvider();
248+
provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
249+
builder.authenticationProvider(provider);
250+
}
251+
252+
/**
253+
* Use the Spring Boot application properties file support instead of the default keycloak.json.
254+
* @return
255+
*/
256+
@Bean
257+
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
258+
return new KeycloakSpringBootConfigResolver();
259+
}
260+
261+
@Override
262+
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
263+
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
264+
}
265+
266+
}
267+
```
268+
269+
270+
271+
说明:
272+
273+
- 访问控制
274+
- 配置了只有`admin`角色时才能访问`/manager` 端点。
275+
- 配置了只有`user``admin`角色时才能访问`/books`端点。
276+
- 访问其他端点,不作控制。
277+
- 注入`configureGlobal`,不让Spring Security默认在Role前添加`ROLE_`
278+
- 注入`keycloakConfigResolver`,让Spring Boot从application properties/yaml 中读取Keycloak配置,而不是从默认的类路径的`key cloak.json`中读取配置。
279+
280+
281+
282+
关于`httpsecurity`的用法参见:
283+
284+
- <https://www.baeldung.com/spring-security-expressions>
285+
286+
287+
288+
### Web层
289+
290+
`LibraryController`类中定义了两个端点:
291+
292+
- `/books` - 普通用户或管理员都可以浏览图书。
293+
- `/manager` - 管理员才可以管理图书。
294+
295+
296+
297+
```java
298+
import org.springframework.stereotype.Controller;
299+
import org.springframework.ui.Model;
300+
import org.springframework.web.bind.annotation.GetMapping;
301+
302+
import javax.servlet.http.HttpServletRequest;
303+
import java.security.Principal;
304+
305+
@Controller
306+
public class LibraryController {
307+
308+
private final BookRepository bookRepository;
309+
310+
public LibraryController(BookRepository bookRepository) {
311+
this.bookRepository = bookRepository;
312+
}
313+
314+
@GetMapping("/books")
315+
public String getBooks(Model model, Principal principal) {
316+
model.addAttribute("books", bookRepository.readAll());
317+
model.addAttribute("name", principal.getName());
318+
return "books";
319+
}
320+
321+
@GetMapping("/manager")
322+
public String manageBooks(Model model, HttpServletRequest request) {
323+
model.addAttribute("books", bookRepository.readAll());
324+
model.addAttribute("name", SecurityUtils.getIDToken(request).getGivenName());
325+
return "manager";
326+
}
327+
}
328+
```
329+
330+
331+
332+
说明:
333+
334+
- `getBooks` 方法演示了直接通过Spring Security Principal获取当前用户的名称,这里是Keycloak User的`preferred_username`
335+
- `manageBooks` 方法演示了通过一个工具类从request中获取Keycloak User的详细信息。
336+
337+
338+
339+
### Keycloak工具类
340+
341+
342+
343+
```java
344+
import org.keycloak.KeycloakSecurityContext;
345+
import org.keycloak.representations.IDToken;
346+
347+
import javax.servlet.http.HttpServletRequest;
348+
349+
public final class SecurityUtils {
350+
351+
private SecurityUtils() {
352+
353+
}
354+
355+
public static KeycloakSecurityContext getKeycloakSecurityContext(HttpServletRequest request) {
356+
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
357+
}
358+
359+
public static IDToken getIDToken(HttpServletRequest request) {
360+
return SecurityUtils.getKeycloakSecurityContext(request).getIdToken();
361+
}
362+
}
363+
```
364+
365+
366+
367+
说明:
368+
369+
- `getIDToken` 方法返回了Keycloak security context,其中包含当前登录的Keycloak User的详细信息。
370+
371+
372+
373+
## 小结
374+
375+
376+
377+
本文的完整代码示例:
378+
379+
- [springboot-keycloak-demo](https://github.com/cookcodeblog/springboot-keycloak-demo)
380+
381+
382+
383+
## 参考文档
384+
385+
- [A Quick Guide to Using Keycloak with Spring Boot](https://www.baeldung.com/spring-boot-keycloak)
386+
- <https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-keycloak>
387+
- [Spring Security and Keycloak to Secure a Spring Boot Application - A First Look](https://www.thomasvitale.com/spring-security-keycloak/)
388+
- <https://github.com/ThomasVitale/spring-keycloak-tutorials/tree/master/keycloak-spring-security-first-look>
389+
- [Easily secure your Spring Boot applications with Keycloak](https://developers.redhat.com/blog/2017/05/25/easily-secure-your-spring-boot-applications-with-keycloak)
390+
391+

‎mvnw

+322
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
#!/bin/sh
2+
# ----------------------------------------------------------------------------
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# https://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
# ----------------------------------------------------------------------------
20+
21+
# ----------------------------------------------------------------------------
22+
# Maven Start Up Batch script
23+
#
24+
# Required ENV vars:
25+
# ------------------
26+
# JAVA_HOME - location of a JDK home dir
27+
#
28+
# Optional ENV vars
29+
# -----------------
30+
# M2_HOME - location of maven2's installed home dir
31+
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
32+
# e.g. to debug Maven itself, use
33+
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34+
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35+
# ----------------------------------------------------------------------------
36+
37+
if [ -z "$MAVEN_SKIP_RC" ]; then
38+
39+
if [ -f /etc/mavenrc ]; then
40+
. /etc/mavenrc
41+
fi
42+
43+
if [ -f "$HOME/.mavenrc" ]; then
44+
. "$HOME/.mavenrc"
45+
fi
46+
47+
fi
48+
49+
# OS specific support. $var _must_ be set to either true or false.
50+
cygwin=false
51+
darwin=false
52+
mingw=false
53+
case "$(uname)" in
54+
CYGWIN*) cygwin=true ;;
55+
MINGW*) mingw=true ;;
56+
Darwin*)
57+
darwin=true
58+
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
59+
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
60+
if [ -z "$JAVA_HOME" ]; then
61+
if [ -x "/usr/libexec/java_home" ]; then
62+
export JAVA_HOME="$(/usr/libexec/java_home)"
63+
else
64+
export JAVA_HOME="/Library/Java/Home"
65+
fi
66+
fi
67+
;;
68+
esac
69+
70+
if [ -z "$JAVA_HOME" ]; then
71+
if [ -r /etc/gentoo-release ]; then
72+
JAVA_HOME=$(java-config --jre-home)
73+
fi
74+
fi
75+
76+
if [ -z "$M2_HOME" ]; then
77+
## resolve links - $0 may be a link to maven's home
78+
PRG="$0"
79+
80+
# need this for relative symlinks
81+
while [ -h "$PRG" ]; do
82+
ls=$(ls -ld "$PRG")
83+
link=$(expr "$ls" : '.*-> \(.*\)$')
84+
if expr "$link" : '/.*' >/dev/null; then
85+
PRG="$link"
86+
else
87+
PRG="$(dirname "$PRG")/$link"
88+
fi
89+
done
90+
91+
saveddir=$(pwd)
92+
93+
M2_HOME=$(dirname "$PRG")/..
94+
95+
# make it fully qualified
96+
M2_HOME=$(cd "$M2_HOME" && pwd)
97+
98+
cd "$saveddir"
99+
# echo Using m2 at $M2_HOME
100+
fi
101+
102+
# For Cygwin, ensure paths are in UNIX format before anything is touched
103+
if $cygwin; then
104+
[ -n "$M2_HOME" ] &&
105+
M2_HOME=$(cygpath --unix "$M2_HOME")
106+
[ -n "$JAVA_HOME" ] &&
107+
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
108+
[ -n "$CLASSPATH" ] &&
109+
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
110+
fi
111+
112+
# For Mingw, ensure paths are in UNIX format before anything is touched
113+
if $mingw; then
114+
[ -n "$M2_HOME" ] &&
115+
M2_HOME="$( (
116+
cd "$M2_HOME"
117+
pwd
118+
))"
119+
[ -n "$JAVA_HOME" ] &&
120+
JAVA_HOME="$( (
121+
cd "$JAVA_HOME"
122+
pwd
123+
))"
124+
fi
125+
126+
if [ -z "$JAVA_HOME" ]; then
127+
javaExecutable="$(which javac)"
128+
if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
129+
# readlink(1) is not available as standard on Solaris 10.
130+
readLink=$(which readlink)
131+
if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
132+
if $darwin; then
133+
javaHome="$(dirname \"$javaExecutable\")"
134+
javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
135+
else
136+
javaExecutable="$(readlink -f \"$javaExecutable\")"
137+
fi
138+
javaHome="$(dirname \"$javaExecutable\")"
139+
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
140+
JAVA_HOME="$javaHome"
141+
export JAVA_HOME
142+
fi
143+
fi
144+
fi
145+
146+
if [ -z "$JAVACMD" ]; then
147+
if [ -n "$JAVA_HOME" ]; then
148+
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
149+
# IBM's JDK on AIX uses strange locations for the executables
150+
JAVACMD="$JAVA_HOME/jre/sh/java"
151+
else
152+
JAVACMD="$JAVA_HOME/bin/java"
153+
fi
154+
else
155+
JAVACMD="$(which java)"
156+
fi
157+
fi
158+
159+
if [ ! -x "$JAVACMD" ]; then
160+
echo "Error: JAVA_HOME is not defined correctly." >&2
161+
echo " We cannot execute $JAVACMD" >&2
162+
exit 1
163+
fi
164+
165+
if [ -z "$JAVA_HOME" ]; then
166+
echo "Warning: JAVA_HOME environment variable is not set."
167+
fi
168+
169+
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
170+
171+
# traverses directory structure from process work directory to filesystem root
172+
# first directory with .mvn subdirectory is considered project base directory
173+
find_maven_basedir() {
174+
175+
if [ -z "$1" ]; then
176+
echo "Path not specified to find_maven_basedir"
177+
return 1
178+
fi
179+
180+
basedir="$1"
181+
wdir="$1"
182+
while [ "$wdir" != '/' ]; do
183+
if [ -d "$wdir"/.mvn ]; then
184+
basedir=$wdir
185+
break
186+
fi
187+
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
188+
if [ -d "${wdir}" ]; then
189+
wdir=$(
190+
cd "$wdir/.."
191+
pwd
192+
)
193+
fi
194+
# end of workaround
195+
done
196+
echo "${basedir}"
197+
}
198+
199+
# concatenates all lines of a file
200+
concat_lines() {
201+
if [ -f "$1" ]; then
202+
echo "$(tr -s '\n' ' ' <"$1")"
203+
fi
204+
}
205+
206+
BASE_DIR=$(find_maven_basedir "$(pwd)")
207+
if [ -z "$BASE_DIR" ]; then
208+
exit 1
209+
fi
210+
211+
##########################################################################################
212+
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
213+
# This allows using the maven wrapper in projects that prohibit checking in binary data.
214+
##########################################################################################
215+
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
216+
if [ "$MVNW_VERBOSE" = true ]; then
217+
echo "Found .mvn/wrapper/maven-wrapper.jar"
218+
fi
219+
else
220+
if [ "$MVNW_VERBOSE" = true ]; then
221+
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
222+
fi
223+
if [ -n "$MVNW_REPOURL" ]; then
224+
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
225+
else
226+
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
227+
fi
228+
while IFS="=" read key value; do
229+
case "$key" in wrapperUrl)
230+
jarUrl="$value"
231+
break
232+
;;
233+
esac
234+
done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
235+
if [ "$MVNW_VERBOSE" = true ]; then
236+
echo "Downloading from: $jarUrl"
237+
fi
238+
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
239+
if $cygwin; then
240+
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
241+
fi
242+
243+
if command -v wget >/dev/null; then
244+
if [ "$MVNW_VERBOSE" = true ]; then
245+
echo "Found wget ... using wget"
246+
fi
247+
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
248+
wget "$jarUrl" -O "$wrapperJarPath"
249+
else
250+
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
251+
fi
252+
elif command -v curl >/dev/null; then
253+
if [ "$MVNW_VERBOSE" = true ]; then
254+
echo "Found curl ... using curl"
255+
fi
256+
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
257+
curl -o "$wrapperJarPath" "$jarUrl" -f
258+
else
259+
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
260+
fi
261+
262+
else
263+
if [ "$MVNW_VERBOSE" = true ]; then
264+
echo "Falling back to using Java to download"
265+
fi
266+
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
267+
# For Cygwin, switch paths to Windows format before running javac
268+
if $cygwin; then
269+
javaClass=$(cygpath --path --windows "$javaClass")
270+
fi
271+
if [ -e "$javaClass" ]; then
272+
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
273+
if [ "$MVNW_VERBOSE" = true ]; then
274+
echo " - Compiling MavenWrapperDownloader.java ..."
275+
fi
276+
# Compiling the Java class
277+
("$JAVA_HOME/bin/javac" "$javaClass")
278+
fi
279+
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
280+
# Running the downloader
281+
if [ "$MVNW_VERBOSE" = true ]; then
282+
echo " - Running MavenWrapperDownloader.java ..."
283+
fi
284+
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
285+
fi
286+
fi
287+
fi
288+
fi
289+
##########################################################################################
290+
# End of extension
291+
##########################################################################################
292+
293+
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
294+
if [ "$MVNW_VERBOSE" = true ]; then
295+
echo $MAVEN_PROJECTBASEDIR
296+
fi
297+
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
298+
299+
# For Cygwin, switch paths to Windows format before running java
300+
if $cygwin; then
301+
[ -n "$M2_HOME" ] &&
302+
M2_HOME=$(cygpath --path --windows "$M2_HOME")
303+
[ -n "$JAVA_HOME" ] &&
304+
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
305+
[ -n "$CLASSPATH" ] &&
306+
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
307+
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
308+
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
309+
fi
310+
311+
# Provide a "standardized" way to retrieve the CLI args that will
312+
# work with both Windows and non-Windows executions.
313+
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
314+
export MAVEN_CMD_LINE_ARGS
315+
316+
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
317+
318+
exec "$JAVACMD" \
319+
$MAVEN_OPTS \
320+
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
321+
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
322+
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

‎mvnw.cmd

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
@REM ----------------------------------------------------------------------------
2+
@REM Licensed to the Apache Software Foundation (ASF) under one
3+
@REM or more contributor license agreements. See the NOTICE file
4+
@REM distributed with this work for additional information
5+
@REM regarding copyright ownership. The ASF licenses this file
6+
@REM to you under the Apache License, Version 2.0 (the
7+
@REM "License"); you may not use this file except in compliance
8+
@REM with the License. You may obtain a copy of the License at
9+
@REM
10+
@REM https://www.apache.org/licenses/LICENSE-2.0
11+
@REM
12+
@REM Unless required by applicable law or agreed to in writing,
13+
@REM software distributed under the License is distributed on an
14+
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
@REM KIND, either express or implied. See the License for the
16+
@REM specific language governing permissions and limitations
17+
@REM under the License.
18+
@REM ----------------------------------------------------------------------------
19+
20+
@REM ----------------------------------------------------------------------------
21+
@REM Maven Start Up Batch script
22+
@REM
23+
@REM Required ENV vars:
24+
@REM JAVA_HOME - location of a JDK home dir
25+
@REM
26+
@REM Optional ENV vars
27+
@REM M2_HOME - location of maven2's installed home dir
28+
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29+
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
30+
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31+
@REM e.g. to debug Maven itself, use
32+
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33+
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34+
@REM ----------------------------------------------------------------------------
35+
36+
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37+
@echo off
38+
@REM set title of command window
39+
title %0
40+
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
41+
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
42+
43+
@REM set %HOME% to equivalent of $HOME
44+
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
45+
46+
@REM Execute a user defined script before this one
47+
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
48+
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
49+
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
50+
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
51+
:skipRcPre
52+
53+
@setlocal
54+
55+
set ERROR_CODE=0
56+
57+
@REM To isolate internal variables from possible post scripts, we use another setlocal
58+
@setlocal
59+
60+
@REM ==== START VALIDATION ====
61+
if not "%JAVA_HOME%" == "" goto OkJHome
62+
63+
echo.
64+
echo Error: JAVA_HOME not found in your environment. >&2
65+
echo Please set the JAVA_HOME variable in your environment to match the >&2
66+
echo location of your Java installation. >&2
67+
echo.
68+
goto error
69+
70+
:OkJHome
71+
if exist "%JAVA_HOME%\bin\java.exe" goto init
72+
73+
echo.
74+
echo Error: JAVA_HOME is set to an invalid directory. >&2
75+
echo JAVA_HOME = "%JAVA_HOME%" >&2
76+
echo Please set the JAVA_HOME variable in your environment to match the >&2
77+
echo location of your Java installation. >&2
78+
echo.
79+
goto error
80+
81+
@REM ==== END VALIDATION ====
82+
83+
:init
84+
85+
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86+
@REM Fallback to current working directory if not found.
87+
88+
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89+
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90+
91+
set EXEC_DIR=%CD%
92+
set WDIR=%EXEC_DIR%
93+
:findBaseDir
94+
IF EXIST "%WDIR%"\.mvn goto baseDirFound
95+
cd ..
96+
IF "%WDIR%"=="%CD%" goto baseDirNotFound
97+
set WDIR=%CD%
98+
goto findBaseDir
99+
100+
:baseDirFound
101+
set MAVEN_PROJECTBASEDIR=%WDIR%
102+
cd "%EXEC_DIR%"
103+
goto endDetectBaseDir
104+
105+
:baseDirNotFound
106+
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107+
cd "%EXEC_DIR%"
108+
109+
:endDetectBaseDir
110+
111+
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112+
113+
@setlocal EnableExtensions EnableDelayedExpansion
114+
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115+
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116+
117+
:endReadAdditionalConfig
118+
119+
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120+
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
121+
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
122+
123+
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
124+
125+
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
126+
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
127+
)
128+
129+
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
130+
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
131+
if exist %WRAPPER_JAR% (
132+
if "%MVNW_VERBOSE%" == "true" (
133+
echo Found %WRAPPER_JAR%
134+
)
135+
) else (
136+
if not "%MVNW_REPOURL%" == "" (
137+
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
138+
)
139+
if "%MVNW_VERBOSE%" == "true" (
140+
echo Couldn't find %WRAPPER_JAR%, downloading it ...
141+
echo Downloading from: %DOWNLOAD_URL%
142+
)
143+
144+
powershell -Command "&{"^
145+
"$webclient = new-object System.Net.WebClient;"^
146+
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
147+
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
148+
"}"^
149+
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
150+
"}"
151+
if "%MVNW_VERBOSE%" == "true" (
152+
echo Finished downloading %WRAPPER_JAR%
153+
)
154+
)
155+
@REM End of extension
156+
157+
@REM Provide a "standardized" way to retrieve the CLI args that will
158+
@REM work with both Windows and non-Windows executions.
159+
set MAVEN_CMD_LINE_ARGS=%*
160+
161+
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
162+
if ERRORLEVEL 1 goto error
163+
goto end
164+
165+
:error
166+
set ERROR_CODE=1
167+
168+
:end
169+
@endlocal & set ERROR_CODE=%ERROR_CODE%
170+
171+
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
172+
@REM check for post script, once with legacy .bat ending and once with .cmd ending
173+
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
174+
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
175+
:skipRcPost
176+
177+
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
178+
if "%MAVEN_BATCH_PAUSE%" == "on" pause
179+
180+
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
181+
182+
exit /B %ERROR_CODE%

‎pom.xml

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.4.0</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>cn.xdevops.demo</groupId>
12+
<artifactId>springboot-keycloak-demo</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>springboot-keycloak-demo</name>
15+
<description>Spring Boot integrating Keycloak demo</description>
16+
<properties>
17+
<java.version>1.8</java.version>
18+
</properties>
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-security</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-web</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.thymeleaf.extras</groupId>
34+
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-devtools</artifactId>
40+
<scope>runtime</scope>
41+
<optional>true</optional>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.projectlombok</groupId>
45+
<artifactId>lombok</artifactId>
46+
<optional>true</optional>
47+
</dependency>
48+
<dependency>
49+
<groupId>org.springframework.boot</groupId>
50+
<artifactId>spring-boot-starter-test</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.springframework.security</groupId>
55+
<artifactId>spring-security-test</artifactId>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>org.keycloak</groupId>
60+
<artifactId>keycloak-spring-boot-starter</artifactId>
61+
</dependency>
62+
</dependencies>
63+
64+
<dependencyManagement>
65+
<dependencies>
66+
<dependency>
67+
<groupId>org.keycloak.bom</groupId>
68+
<artifactId>keycloak-adapter-bom</artifactId>
69+
<version>12.0.1</version>
70+
<type>pom</type>
71+
<scope>import</scope>
72+
</dependency>
73+
</dependencies>
74+
</dependencyManagement>
75+
76+
<build>
77+
<plugins>
78+
<plugin>
79+
<groupId>org.springframework.boot</groupId>
80+
<artifactId>spring-boot-maven-plugin</artifactId>
81+
<configuration>
82+
<excludes>
83+
<exclude>
84+
<groupId>org.projectlombok</groupId>
85+
<artifactId>lombok</artifactId>
86+
</exclude>
87+
</excludes>
88+
</configuration>
89+
</plugin>
90+
</plugins>
91+
</build>
92+
93+
</project>
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
# Create a user defined network
4+
docker network create keycloak-network
5+
6+
# Start a MySQL instance
7+
docker run --name keycloak-mysql \
8+
-d \
9+
--net keycloak-network \
10+
-e MYSQL_DATABASE=keycloak \
11+
-e MYSQL_USER=keycloak \
12+
-e MYSQL_PASSWORD=keycloak123 \
13+
-e MYSQL_ROOT_PASSWORD=keycloak123 \
14+
mysql:8.0
15+
16+
# Start a Keycloak instance
17+
docker run --name keycloak \
18+
-d \
19+
--net keycloak-network \
20+
-p 8180:8080 \
21+
-e DB_VENDOR=mysql \
22+
-e DB_ADDR=keycloak-mysql \
23+
-e DB_DATABASE=keycloak \
24+
-e DB_USER=keycloak \
25+
-e DB_PASSWORD=keycloak123 \
26+
-e KEYCLOAK_USER=admin \
27+
-e KEYCLOAK_PASSWORD=admin \
28+
quay.io/keycloak/keycloak:12.0.1
29+
30+
# check logs
31+
# docker logs -f keycloak
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
docker run --name keycloak \
4+
-d \
5+
-p 8180:8080 \
6+
-e KEYCLOAK_USER=admin \
7+
-e KEYCLOAK_PASSWORD=admin \
8+
quay.io/keycloak/keycloak:12.0.1
9+
10+
# check logs
11+
# docker logs -f keycloak
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker stop keycloak && docker rm keycloak

‎src/.DS_Store

6 KB
Binary file not shown.

‎src/main/.DS_Store

6 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cn.xdevops.demo;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class Application {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(Application.class, args);
11+
}
12+
13+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package cn.xdevops.demo;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
@Data
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
public class Book {
11+
private String id;
12+
private String title;
13+
private String author;
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cn.xdevops.demo;
2+
3+
import org.springframework.stereotype.Repository;
4+
5+
import java.util.ArrayList;
6+
import java.util.Comparator;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
11+
@Repository
12+
public class BookRepository {
13+
private static Map<String, Book> books = new ConcurrentHashMap<>();
14+
15+
static {
16+
books.put("B01", new Book("B01", "Harry Potter and the Deathly Hallows", "J.K. Rowling"));
17+
books.put("B02", new Book("B02", "The Lord of the Rings", "J.R.R. Tolkien"));
18+
books.put("B03", new Book("B03", "War and Peace", "Leo Tolstoy"));
19+
}
20+
21+
public List<Book> readAll() {
22+
List<Book> allBooks = new ArrayList<>(books.values());
23+
allBooks.sort(Comparator.comparing(Book::getId));
24+
return allBooks;
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cn.xdevops.demo;
2+
3+
import org.springframework.stereotype.Controller;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
6+
import javax.servlet.ServletException;
7+
import javax.servlet.http.HttpServletRequest;
8+
9+
@Controller
10+
public class HomeController {
11+
@GetMapping
12+
public String home() {
13+
return "index";
14+
}
15+
16+
@GetMapping("/logout")
17+
public String logout(HttpServletRequest request) throws ServletException {
18+
request.logout();
19+
return "redirect:/";
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cn.xdevops.demo;
2+
3+
import org.springframework.stereotype.Controller;
4+
import org.springframework.ui.Model;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
7+
import javax.servlet.http.HttpServletRequest;
8+
import java.security.Principal;
9+
10+
@Controller
11+
public class LibraryController {
12+
13+
private final BookRepository bookRepository;
14+
15+
public LibraryController(BookRepository bookRepository) {
16+
this.bookRepository = bookRepository;
17+
}
18+
19+
@GetMapping("/books")
20+
public String getBooks(Model model, Principal principal) {
21+
model.addAttribute("books", bookRepository.readAll());
22+
model.addAttribute("name", principal.getName());
23+
return "books";
24+
}
25+
26+
@GetMapping("/manager")
27+
public String manageBooks(Model model, HttpServletRequest request) {
28+
model.addAttribute("books", bookRepository.readAll());
29+
model.addAttribute("name", SecurityUtils.getIDToken(request).getGivenName());
30+
return "manager";
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cn.xdevops.demo;
2+
3+
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
4+
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
5+
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
6+
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.ComponentScan;
10+
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14+
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
15+
import org.springframework.security.core.session.SessionRegistryImpl;
16+
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
17+
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
18+
19+
@Configuration
20+
@EnableWebSecurity
21+
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
22+
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
23+
24+
@Override
25+
protected void configure(HttpSecurity http) throws Exception {
26+
super.configure(http);
27+
http.authorizeRequests()
28+
.antMatchers("/manager").hasRole("admin")
29+
.antMatchers("/books").hasAnyRole("user", "admin")
30+
.anyRequest().permitAll();
31+
}
32+
33+
/**
34+
* Make sure roles are not prefixed with ROLE_.
35+
* @param builder
36+
*/
37+
@Autowired
38+
public void configureGlobal(AuthenticationManagerBuilder builder) {
39+
KeycloakAuthenticationProvider provider = keycloakAuthenticationProvider();
40+
provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
41+
builder.authenticationProvider(provider);
42+
}
43+
44+
/**
45+
* Use the Spring Boot application properties file support instead of the default keycloak.json.
46+
* @return
47+
*/
48+
@Bean
49+
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
50+
return new KeycloakSpringBootConfigResolver();
51+
}
52+
53+
@Override
54+
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
55+
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
56+
}
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cn.xdevops.demo;
2+
3+
import org.keycloak.KeycloakSecurityContext;
4+
import org.keycloak.representations.IDToken;
5+
6+
import javax.servlet.http.HttpServletRequest;
7+
8+
public final class SecurityUtils {
9+
10+
private SecurityUtils() {
11+
12+
}
13+
14+
public static KeycloakSecurityContext getKeycloakSecurityContext(HttpServletRequest request) {
15+
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
16+
}
17+
18+
public static IDToken getIDToken(HttpServletRequest request) {
19+
return SecurityUtils.getKeycloakSecurityContext(request).getIdToken();
20+
}
21+
}

‎src/main/resources/.DS_Store

8 KB
Binary file not shown.

‎src/main/resources/application.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
keycloak:
2+
# the name of the realm, required
3+
realm: xdevops
4+
# the client-id of the application, required
5+
resource: springboot-keycloak-demo
6+
# the base URL of the Keycloak server, required
7+
auth-server-url: http://localhost:8180/auth
8+
# establishes if communications with the Keycloak server must happen over HTTPS
9+
# set to external, meaning that it's only needed for external requests (default value)
10+
# In production, instead, we should set it to all. Optional
11+
ssl-required: external
12+
# prevents the application from sending credentials to the Keycloak server (false is the default value)
13+
# set it to true whenever we use public clients instead of confidential
14+
public-client: true
15+
# the attribute with which to populate the UserPrincipal name
16+
principal-attribute: preferred_username

‎src/main/resources/static/.DS_Store

6 KB
Binary file not shown.
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
body {
2+
padding-top: 5rem;
3+
}
4+
.starter-template {
5+
padding: 3rem 1.5rem;
6+
text-align: center;
7+
}
Loading
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!DOCTYPE html>
2+
<html lang="en" xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Browse Books</title>
6+
7+
<!-- Style -->
8+
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
9+
<link rel="stylesheet" type="text/css" href="/css/style.css">
10+
11+
</head>
12+
<body>
13+
14+
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
15+
<a class="navbar-brand" href="#">Public Library</a>
16+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
17+
<span class="navbar-toggler-icon"></span>
18+
</button>
19+
20+
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
21+
<ul class="navbar-nav mr-auto">
22+
<li class="nav-item">
23+
<a class="nav-link" href="/">Home</a>
24+
</li>
25+
<li class="nav-item active">
26+
<a class="nav-link" href="/books">Browse Books <span class="sr-only">(current)</span></a>
27+
</li>
28+
<li class="nav-item">
29+
<a class="nav-link" href="/manager">Manage Library</a>
30+
</li>
31+
</ul>
32+
<ul class="navbar-nav ml-auto">
33+
<li class="nav-item">
34+
<a class="nav-link" href="/logout">Logout</a>
35+
</li>
36+
</ul>
37+
</div>
38+
</nav>
39+
40+
<main role="main" class="container">
41+
42+
<div class="starter-template">
43+
<h1>Hello <span th:text="${name}"></span></h1>
44+
<p class="lead">This page is protected. You have member privileges, so you can browse the books.</p>
45+
</div>
46+
47+
<table class="table">
48+
<thead>
49+
<tr>
50+
<th scope="col">#</th>
51+
<th scope="col">Title</th>
52+
<th scope="col">Author</th>
53+
</tr>
54+
</thead>
55+
56+
<tbody>
57+
<tr th:each="book : ${books}">
58+
<td th:text="${book.id}"></td>
59+
<td th:text="${book.title}"></td>
60+
<td th:text="${book.author}"></td>
61+
</tr>
62+
</tbody>
63+
</table>
64+
65+
</main><!-- /.container -->
66+
67+
<!-- Scripts -->
68+
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
69+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
70+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
71+
72+
</body>
73+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>403 | Forbidden</title>
6+
7+
<!-- Style -->
8+
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
9+
<link rel="stylesheet" type="text/css" href="/css/style.css">
10+
11+
</head>
12+
<body>
13+
14+
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
15+
<a class="navbar-brand" href="#">Public Library</a>
16+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
17+
<span class="navbar-toggler-icon"></span>
18+
</button>
19+
20+
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
21+
<ul class="navbar-nav mr-auto">
22+
<li class="nav-item">
23+
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
24+
</li>
25+
<li class="nav-item">
26+
<a class="nav-link" href="/books">Browse Books</a>
27+
</li>
28+
<li class="nav-item">
29+
<a class="nav-link" href="/manager">Manage Library</a>
30+
</li>
31+
</ul>
32+
</div>
33+
</nav>
34+
35+
<main role="main" class="container">
36+
37+
<div class="starter-template">
38+
<h1>Forbidden</h1>
39+
<p class="lead">You don't have the right privileges to access this area.</p>
40+
</div>
41+
42+
</main><!-- /.container -->
43+
44+
<!-- Scripts -->
45+
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
46+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
47+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
48+
49+
</body>
50+
</html>
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Home | Public Library</title>
6+
7+
<!-- Style -->
8+
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
9+
<link rel="stylesheet" type="text/css" href="/css/style.css" crossorigin="anonymous">
10+
11+
</head>
12+
<body>
13+
14+
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
15+
<a class="navbar-brand" href="#">Public Library</a>
16+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
17+
<span class="navbar-toggler-icon"></span>
18+
</button>
19+
20+
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
21+
<ul class="navbar-nav mr-auto">
22+
<li class="nav-item active">
23+
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
24+
</li>
25+
<li class="nav-item">
26+
<a class="nav-link" href="/books">Browse Books</a>
27+
</li>
28+
<li class="nav-item">
29+
<a class="nav-link" href="/manager">Manage Library</a>
30+
</li>
31+
</ul>
32+
</div>
33+
</nav>
34+
35+
<main role="main" class="container">
36+
37+
<div class="starter-template">
38+
<h1>Public Library</h1>
39+
<p class="lead">Welcome to the public library. You are free to visit this page since it is not protected.</p>
40+
41+
</div>
42+
43+
</main><!-- /.container -->
44+
45+
<!-- Scripts -->
46+
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
47+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
48+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
49+
50+
</body>
51+
</html>
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE html>
2+
<html lang="en" xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Manage Library</title>
6+
7+
<!-- Style -->
8+
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
9+
<link rel="stylesheet" type="text/css" href="/css/style.css">
10+
11+
</head>
12+
<body>
13+
14+
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
15+
<a class="navbar-brand" href="#">Public Library</a>
16+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
17+
<span class="navbar-toggler-icon"></span>
18+
</button>
19+
20+
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
21+
<ul class="navbar-nav mr-auto">
22+
<li class="nav-item">
23+
<a class="nav-link" href="/">Home</a>
24+
</li>
25+
<li class="nav-item">
26+
<a class="nav-link" href="/books">Browse Books</a>
27+
</li>
28+
<li class="nav-item active">
29+
<a class="nav-link" href="/manager">Manage Library <span class="sr-only">(current)</span></a>
30+
</li>
31+
</ul>
32+
<ul class="navbar-nav ml-auto">
33+
<li class="nav-item">
34+
<a class="nav-link" href="/logout">Logout</a>
35+
</li>
36+
</ul>
37+
</div>
38+
</nav>
39+
40+
<main role="main" class="container">
41+
42+
<div class="starter-template">
43+
<h1>Hello <span th:text="${name}"></span></h1>
44+
<p class="lead">This page is protected. You have librarian privileges, so you can manage the library.</p>
45+
<img src="/img/public-library-bookshelves-books.jpg" class="img-fluid" style="max-width:480px">
46+
</div>
47+
48+
</main><!-- /.container -->
49+
50+
<!-- Scripts -->
51+
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
52+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
53+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
54+
55+
</body>
56+
</html>

0 commit comments

Comments
 (0)
Please sign in to comment.