当前位置: 首页 > article >正文

springmvc-springsecurity-redhat keycloak SAML2 xml实现

环境准备:

jdk17

redhat keycloak 24

spring security 6

参照文档:

红帽KeyCloak:Red Hat build of Keycloak | Red Hat Product Documentation

入门指南:入门指南 | Red Hat Product Documentation

服务器管理指南:服务器管理指南 | Red Hat Product Documentation

Redhat Keycloak:

本地启动:

\rhbk-24.0.7\bin\kc.bat start-dev --http-port 8180

管理控制台的URL:http://localhost:8180/admin

账户控制台的URL:http://localhost:8180/realms/{myrealm}/account

Spring MVC:

<mvc:redirect-view-controller path="/aml01/saml2/sso_login"
        redirect-url="/saml2/authenticate/saml-app" />

saml-app:同security中的registration-id

Spring security:

POM引入包:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-saml2-service-provider</artifactId>
</dependency>
	<http auto-config="true">
		<intercept-url pattern="/**" access="authenticated"/>
		<saml2-login
		authentication-success-handler-ref="samlAuthenticationSuccessHandler"
/>
		<saml2-logout />
	</http>

<relying-party-registrations>
		<relying-party-registration registration-id="saml-app"
																entity-id="saml-app"
																assertion-consumer-service-location="http://localhost:8080/login/saml2/sso/{registrationId}"
																assertion-consumer-service-binding="POST"
																single-logout-service-location="http://localhost:8080/logout/saml2/slo"
																single-logout-service-response-location="http://localhost:8080/logout/saml2/slo"
																asserting-party-id="saml-xml">
			<signing-credential certificate-location="classpath:credentials/rp-certificate.crt"
													private-key-location="classpath:credentials/rp-private.key"/>
		</relying-party-registration>

		<asserting-party asserting-party-id="saml-xml"
										 entity-id="http://localhost:8180/realms/demo"
										 single-sign-on-service-location="http://localhost:8180/realms/demo/protocol/saml"
										 single-sign-on-service-binding="POST"
										 signing-algorithms="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
										 single-logout-service-location="http://localhost:8180/realms/demo/protocol/saml"
										 single-logout-service-binding="POST"
										 single-logout-service-response-location="http://localhost:8180/realms/demo/protocol/saml"
										 want-authn-requests-signed="true"
										 >
			<verification-credential private-key-location="classpath:credentials/rp-private.key"
					certificate-location="classpath:credentials/idp-certificate.crt"/>
			<encryption-credential private-key-location="classpath:credentials/rp-private.key"
					certificate-location="classpath:credentials/idp-certificate.crt"/>
		</asserting-party>
	</relying-party-registrations>

这个URL「assertion-consumer-service-location="{baseUrl}/login/saml2/sso/{registrationId}"」和keycloak的client的Valid redirect URIs相同

Valid redirect URIs:localhost:8080/login/saml2/sso/saml-app

证明书和key做成:
openssl req -newkey rsa:2048 -nodes -keyout rp-private.key -x509 -days 365 -out rp-certificate.crt

rp-certificate.crt导入keycloak的Clients -> client details ->keys ->import key

代码:

包结构:

└─pom.xml
│  
└─src
    ├─main
    │  ├─java
    │  │  └─example
    │  │          IndexController.java
    │  │          KeyLoader.java
    │  │          WebConfiguration.java
    │  │          
    │  ├─resources
    │  │  │  logback.xml
    │  │  │  
    │  │  └─credentials
    │  │          idp-certificate.crt
    │  │          rp-certificate.crt
    │  │          rp-private.key
    │  │          
    │  └─webapp
    │      ├─META-INF
    │      │      MANIFEST.MF
    │      │      
    │      ├─resources
    │      │  ├─css
    │      │  │      bootstrap-responsive.css
    │      │  │      bootstrap.css
    │      │  │      
    │      │  └─img
    │      │          favicon.ico
    │      │          logo.png
    │      │          
    │      └─WEB-INF
    │          │  jboss-web.xml
    │          │  spring-servlet.xml
    │          │  web.xml
    │          │  
    │          ├─spring
    │          │      security.xml
    │          │      
    │          └─templates
    │                  index.html
    │                  
    └─test
        └─java

pom:

<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>

    <groupId>org.springframework.security</groupId>
    <artifactId>keycloak-integration-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <spring.version>6.2.1</spring.version> <!-- Adjust to your Spring BOM version -->
        <junit.version>5.10.3</junit.version>
    </properties>

    <dependencies>
        <!-- OpenSAML Dependencies -->
        <dependency>
            <groupId>org.opensaml</groupId>
            <artifactId>opensaml-saml-api</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.opensaml</groupId>
            <artifactId>opensaml-saml-impl</artifactId>
            <version>4.1.1</version>
        </dependency>

        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-saml2-service-provider</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Thymeleaf Dependencies -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>

        <!-- Servlet API -->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Testing Dependencies -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>6.1.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.26.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.htmlunit</groupId>
            <artifactId>htmlunit</artifactId>
            <version>4.3.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>aml-web</finalName>
        <plugins>
            <!-- Maven War Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>

            <!-- Maven Surefire Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M7</version>
                <configuration>
                    <includes>
                        <include>**/*Tests.java</include>
                    </includes>
                </configuration>
            </plugin>

            <!-- Other plugins like Gretty and Integrtest would need to be replaced with their Maven equivalents or configured differently -->
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>central</id>
            <url>https://repo.maven.apache.org/maven2</url>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>https://repo.spring.io/milestone</url>
        </repository>
        <repository>
            <id>shibboleth-releases</id>
            <url>https://build.shibboleth.net/nexus/content/repositories/releases</url>
        </repository>
    </repositories>
</project>

security.xml:

<b:beans xmlns="http://www.springframework.org/schema/security"
		 xmlns:b="http://www.springframework.org/schema/beans"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xmlns:util="http://www.springframework.org/schema/util"
		 xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
						http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

	<http auto-config="true">
		<intercept-url pattern="/**" access="authenticated"/>
		<saml2-login
		authentication-success-handler-ref="samlAuthenticationSuccessHandler"
/>
		<saml2-logout />
	</http>
	
	<!-- 認証成功した場合画面遷移Handler -->
    <b:bean id="samlAuthenticationSuccessHandler"
        class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler">
        <b:property name="targetUrlParameter" value="redirectTo" />
        <b:property name="alwaysUseDefaultTargetUrl" value="true" />
        <b:property name="defaultTargetUrl" value="/test" />
    </b:bean>


	<user-service>
		<user name="user" password="{noop}password" authorities="ROLE_USER" />
	</user-service>

 <relying-party-registrations>
		<relying-party-registration registration-id="saml-app"
																entity-id="saml-app"
																assertion-consumer-service-location="http://localhost:8080/login/saml2/sso/{registrationId}"
																assertion-consumer-service-binding="POST"
																single-logout-service-location="http://localhost:8080/logout/saml2/slo"
																single-logout-service-response-location="http://localhost:8080/logout/saml2/slo"
																asserting-party-id="saml-xml">
			<signing-credential certificate-location="classpath:credentials/rp-certificate.crt"
													private-key-location="classpath:credentials/rp-private.key"/>
		</relying-party-registration>

		<asserting-party asserting-party-id="saml-xml"
										 entity-id="http://localhost:8180/realms/demo"
										 single-sign-on-service-location="http://localhost:8180/realms/demo/protocol/saml"
										 single-sign-on-service-binding="POST"
										 signing-algorithms="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
										 single-logout-service-location="http://localhost:8180/realms/demo/protocol/saml"
										 single-logout-service-binding="POST"
										 single-logout-service-response-location="http://localhost:8180/realms/demo/protocol/saml"
										 want-authn-requests-signed="true"
										 >
			<verification-credential private-key-location="classpath:credentials/rp-private.key"
					certificate-location="classpath:credentials/idp-certificate.crt"/>
			<encryption-credential private-key-location="classpath:credentials/rp-private.key"
					certificate-location="classpath:credentials/idp-certificate.crt"/>
		</asserting-party>
	</relying-party-registrations> 

</b:beans>

spring-servlet.xml:

<!--
  ~ Copyright 2022 the original author or authors.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      https://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
				http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  
  <context:component-scan base-package="example"/>

</beans>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

	<!--
	  - Location of the XML file that defines the root application context
	  - Applied by ContextLoaderListener.
	  -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/spring/*.xml
		</param-value>
	</context-param>


	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

index.html:

<!--
  ~ Copyright 2022 the original author or authors.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      https://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <title>Spring Security - SAML 2.0 Login & Logout</title>
    <meta charset="utf-8" />
    <style>
        span, dt {
            font-weight: bold;
        }
    </style>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <ul class="nav">
        <li class="nav-item">
            <form th:action="@{/logout}" method="post">
                <button class="btn btn-primary" id="rp_logout_button" type="submit">
                    RP-initiated Logout
                </button>
            </form>
        </li>
    </ul>
    </div>
    <main role="main" class="container">
        <h1 class="mt-5">SAML 2.0 Login & Single Logout with Spring Security</h1>
        <p class="lead">You are successfully logged in as <span sec:authentication="name"></span></p>
        <p class="lead">You're email address is <span th:text="${emailAddress}"></span></p>
        <h2 class="mt-2">All Your Attributes</h2>
        <dl th:each="userAttribute : ${userAttributes}">
            <dt th:text="${userAttribute.key}"></dt>
            <dd th:text="${userAttribute.value}"></dd>
        </dl>

        <h6>Visit the <a href="https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-saml2" target="_blank">SAML 2.0 Login & Logout</a> documentation for more details.</h6>
    </main>
</div>
</body>
</html>

logback.xml:

<configuration>
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
	<encoder>
		<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
	</encoder>
	</appender>
	
	 <logger name="org.springframework.security" level="TRACE"/>

	<root level="TRACE">
		<appender-ref ref="STDOUT" />
	</root>

</configuration>

credentials:

idp-certificate.crt  keycloak的idp RSA256 cetificate

rp-certificate.crt   上面生成

rp-private.key   上面生成

WebConfiguration.java:

/*
 * Copyright 2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package example;

import java.util.List;

import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer, ApplicationContextAware {

	private ApplicationContext context;

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
		AuthenticationPrincipalArgumentResolver principalArgumentResolver = new AuthenticationPrincipalArgumentResolver();
		principalArgumentResolver
			.setBeanResolver(new BeanFactoryResolver(this.context.getAutowireCapableBeanFactory()));
		resolvers.add(principalArgumentResolver);
	}

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		this.context = context;
	}

	@Bean
	public SpringResourceTemplateResolver templateResolver() {
		SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
		templateResolver.setApplicationContext(this.context);
		templateResolver.setPrefix("/WEB-INF/templates/");
		templateResolver.setSuffix(".html");
		templateResolver.setTemplateMode(TemplateMode.HTML);
		templateResolver.setCacheable(false);
		return templateResolver;
	}

	@Bean
	public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
		SpringTemplateEngine templateEngine = new SpringTemplateEngine();
		templateEngine.setTemplateResolver(templateResolver);
		templateEngine.setEnableSpringELCompiler(true);
		templateEngine.addDialect(new SpringSecurityDialect());
		return templateEngine;
	}

	@Bean
	public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {
		ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
		viewResolver.setTemplateEngine(templateEngine);
		return viewResolver;
	}

}

IndexController.java:

/*
 * Copyright 2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package example;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

	private final RelyingPartyRegistrationRepository repository;

	public IndexController(RelyingPartyRegistrationRepository repository) {
		this.repository = repository;
	}

	@GetMapping("/test")
	public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
		String emailAddress = principal.getFirstAttribute("email");
		model.addAttribute("emailAddress", emailAddress);
		model.addAttribute("userAttributes", principal.getAttributes());
		return "index";
	}

}

访问地址:

localhost:8080/test


http://www.kler.cn/a/369044.html

相关文章:

  • 数据结构:“小猫钓鱼游戏”
  • web pdf 图片拖动图片合成
  • 关闭windows更新方法
  • 【认知智能】编译器1
  • B端产品常用组件及设计规则 原型图 Axure原型图 交互设计
  • 4款免费恢复工具,一键拯救你的重要资料
  • 【C++】继承与模板
  • WASM 使用说明23事(RUST实现)
  • 【TIMM库】是一个专门为PyTorch用户设计的图像模型库 python库
  • 15分钟学 Go 第 23 天:并发基础:Goroutines
  • 【CSS3】css开篇基础(4)
  • JavaScript 函数与事件处理
  • 灵动AI:艺术与科技的融合
  • 网络搜索引擎Shodan(4)
  • 最优化方法-无约束优化算法(最速下降法)matlab实现
  • opencv学习笔记(3):图像和视频的读取(C++)
  • 【AIGC】ChatGPT提示词Prompt精确控制指南:Scott Guthrie的建议详解与普通用户实践解析
  • 26.Redis主从架构
  • Hadoop-001-本地虚拟机环境搭建
  • oracle 行转列(PIVOT 多个行数据按照指定的列进行汇总) 列转行(UNPIVOT)
  • RHCE笔记-NFS服务
  • 第十七周:机器学习
  • 2025年软考高级哪个最简单?
  • 重构商业生态:DApp创新玩法与盈利模式的深度剖析
  • c语言中整数在内存中的存储
  • 7、基于爬虫+Flask+Echarts+MySQL的网易云评论可视化大屏