POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project-name>spring-security-reactive-multi-login</project-name>
<!--对应关系参考: https://spring.io/projects/spring-cloud/#learn -->
<spring-boot.version>2.6.15</spring-boot.version>
<spring-cloud.version>2021.0.9</spring-cloud.version>
</properties>
<groupId>org.fiend</groupId>
<artifactId>${project-name}</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>${project-name}</name>
<description>Demo project for Spring Security Reactive</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--<dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-oauth2-resource-server</artifactId>-->
<!--</dependency>-->
<!-- ============== 应用 ================= -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.25</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<!--jwt工具-->
<!--<dependency>-->
<!-- <groupId>com.nimbusds</groupId>-->
<!-- <artifactId>nimbus-jose-jwt</artifactId>-->
<!-- <version>9.37.3</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.4</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.fiend.Server</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 解决资源文件的编码问题 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- maven打source包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<!--<phase>verify</phase>-->
<goals>
<!--jar, jar-no-fork-->
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- spring Boot在编译的时候, 是有默认JDK版本的, 这里自定义指定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!--拷贝依赖jar到指定的目录-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!-- maven-jar-plugin用于生成META-INF/MANIFEST.MF文件的部分内容, -->
<addClasspath>true</addClasspath>
<!-- 指定依赖包所在目录。 -->
<classpathPrefix>lib/</classpathPrefix>
<!-- 指定MANIFEST.MF中的Main-Class, -->
<mainClass>org.fiend.SpringBootMiniDemoSrv</mainClass>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
<excludes>
<!--<exclude>*.properties</exclude>-->
<!--<exclude>*.yml</exclude>-->
<!--<exclude>*.xml</exclude>-->
<!--<exclude>org/fiend/controller/HomeController.class</exclude>-->
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.1.2184</version>
</plugin>
</plugins>
</build>
</project>
application.yml
# ========================== springboot变量使用方法 ========================== #
# 端口号变量: ${server.port}
# user-info:
# name: ${spring.application.name}:${server.port}:${spring.version}
server:
port: 8600
max-http-header-size: 8192
tomcat:
max-connections: 10000 # 最大连接数, 默认为10000
accept-count: 500 # 最大连接等待数, 默认100
threads:
max: 475 # 最大工作线程数, 默认200
min-spare: 400 #最小工作线程数, 默认10
spring:
application:
name: spring-security-reactive-multi-login
webflux:
# 设置 context path 路径
# base-path: /my-context-path
security:
user:
name: user
password: 123
# ========================== logging配置 ========================== #
#配置日志
logging:
# config: /data/config/logback-spring.xml # 指定logback-spring.xml文件路径
# config: classpath:logback-spring.xml
level:
org.springframework.cloud.gateway: TRACE
root: info
# # 不同目录下的日志可配置不同级别 info, error
# com.euler: debug
# org.springfromework.web: info
Service
CustomMapReactiveUserDetailsService
package org.fiend.a.security.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.*;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 普通用户名密码登录 UserDetailsService
* @author Administrator 2024-01-16 15:29:59
*/
public class CustomMapReactiveUserDetailsService implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {
Logger log = LoggerFactory.getLogger(getClass());
private final Map<String, UserDetails> users;
public CustomMapReactiveUserDetailsService(UserDetails... users) {
this(Arrays.asList(users));
}
/**
* Creates a new instance
* @param users the {@link UserDetails} to use
*/
public CustomMapReactiveUserDetailsService(Collection<UserDetails> users) {
Assert.notEmpty(users, "users cannot be null or empty");
this.users = new ConcurrentHashMap<>();
for (UserDetails user : users) {
this.users.put(getKey(user.getUsername()), user);
}
}
@Override
public Mono<UserDetails> findByUsername(String username) {
// 如果用户名不符合ldap的命名规则, 则不必再去users中查询,
// 因为实际情况下去users中查询, 可能涉及到去相关数据库中查询,
// 资源消耗较大
if (isIllegalUsername(username)) {
log.warn("custom username is illegal!");
return Mono.empty();
}
String key = getKey(username);
UserDetails result = users.get(key);
return (result != null) ? Mono.just(User.withUserDetails(result).build()) : Mono.empty();
}
private boolean isIllegalUsername(String username) {
if (username == null || username.isEmpty()) {
return true; // 空字符串或null不合法
}
// 只能包含数字大小写字母和“-”, 字符串最小长度为1,最大长度为16
Pattern usernamePattern = Pattern.compile("^[a-zA-Z0-9-]{1,16}#34;);
Matcher matcher = usernamePattern.matcher(username);
return !matcher.matches();
}
@Override
public Mono<UserDetails> updatePassword(UserDetails user, String newPassword) {
// @formatter:off
return Mono.just(user)
.map((userDetails) -> withNewPassword(userDetails, newPassword))
.doOnNext((userDetails) -> {
String key = getKey(user.getUsername());
this.users.put(key, userDetails);
});
// @formatter:on
}
private UserDetails withNewPassword(UserDetails userDetails, String newPassword) {
// @formatter:off
return User.withUserDetails(userDetails)
.password(newPassword)
.build();
// @formatter:on
}
private String getKey(String username) {
return username.toLowerCase();
}
}
EmailMapReactiveUserDetailsService
package org.fiend.a.security.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.*;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* email用户名密码登录 UserDetailsService
* @author Administrator 2024-01-16 15:29:59
*/
public class EmailMapReactiveUserDetailsService implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {
Logger log = LoggerFactory.getLogger(getClass());
private final Map<String, UserDetails> users;
public EmailMapReactiveUserDetailsService(UserDetails... users) {
this(Arrays.asList(users));
}
/**
* Creates a new instance
* @param users the {@link UserDetails} to use
*/
public EmailMapReactiveUserDetailsService(Collection<UserDetails> users) {
Assert.notEmpty(users, "users cannot be null or empty");
this.users = new ConcurrentHashMap<>();
for (UserDetails user : users) {
this.users.put(getKey(user.getUsername()), user);
}
}
@Override
public Mono<UserDetails> findByUsername(String username) {
// 如果用户名不符合ldap的命名规则, 则不必再去users中查询,
// 因为实际情况下去users中查询, 可能涉及到去相关数据库中查询,
// 资源消耗较大
if (isIllegalUsername(username)) {
log.warn("email username is illegal!");
return Mono.empty();
}
String key = getKey(username);
UserDetails result = users.get(key);
return (result != null) ? Mono.just(User.withUserDetails(result).build()) : Mono.empty();
}
private boolean isIllegalUsername(String username) {
if (username == null || username.isEmpty()) {
return true; // 空字符串或null不合法
}
// email地址校验
Pattern usernamePattern = Pattern.compile("^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+#34;);
Matcher matcher = usernamePattern.matcher(username);
return !matcher.matches();
}
@Override
public Mono<UserDetails> updatePassword(UserDetails user, String newPassword) {
// @formatter:off
return Mono.just(user)
.map((userDetails) -> withNewPassword(userDetails, newPassword))
.doOnNext((userDetails) -> {
String key = getKey(user.getUsername());
this.users.put(key, userDetails);
});
// @formatter:on
}
private UserDetails withNewPassword(UserDetails userDetails, String newPassword) {
// @formatter:off
return User.withUserDetails(userDetails)
.password(newPassword)
.build();
// @formatter:on
}
private String getKey(String username) {
return username.toLowerCase();
}
}
LdapMapReactiveUserDetailsService
package org.fiend.a.security.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.*;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* ldap用户名密码登录 UserDetailsService
* @author Administrator 2024-01-16 15:29:59
*/
public class LdapMapReactiveUserDetailsService implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {
Logger log = LoggerFactory.getLogger(getClass());
private final Map<String, UserDetails> users;
public LdapMapReactiveUserDetailsService(UserDetails... users) {
this(Arrays.asList(users));
}
/**
* Creates a new instance
* @param users the {@link UserDetails} to use
*/
public LdapMapReactiveUserDetailsService(Collection<UserDetails> users) {
Assert.notEmpty(users, "users cannot be null or empty");
this.users = new ConcurrentHashMap<>();
for (UserDetails user : users) {
this.users.put(getKey(user.getUsername()), user);
}
}
@Override
public Mono<UserDetails> findByUsername(String username) {
// 如果用户名不符合ldap的命名规则, 则不必再去users中查询,
// 因为实际情况下去users中查询, 可能涉及到去相关数据库中查询,
// 资源消耗较大
if (isIllegalUsername(username)) {
log.warn("ldap username is illegal!");
return Mono.empty();
}
String key = getKey(username);
UserDetails result = users.get(key);
return (result != null) ? Mono.just(User.withUserDetails(result).build()) : Mono.empty();
}
private boolean isIllegalUsername(String username) {
if (username == null || username.isEmpty()) {
return true; // 空字符串或null不合法
}
// ldap地址校验
Pattern usernamePattern = Pattern.compile("^dc=[^,]+,dc=[^,]+#34;);
Matcher matcher = usernamePattern.matcher(username);
return !matcher.matches();
}
@Override
public Mono<UserDetails> updatePassword(UserDetails user, String newPassword) {
// @formatter:off
return Mono.just(user)
.map((userDetails) -> withNewPassword(userDetails, newPassword))
.doOnNext((userDetails) -> {
String key = getKey(user.getUsername());
this.users.put(key, userDetails);
});
// @formatter:on
}
private UserDetails withNewPassword(UserDetails userDetails, String newPassword) {
// @formatter:off
return User.withUserDetails(userDetails)
.password(newPassword)
.build();
// @formatter:on
}
private String getKey(String username) {
return username.toLowerCase();
}
}
Manager
CustomUserReactiveAuthenticationManager
package org.fiend.a.security.manager;
import org.fiend.a.security.service.CustomMapReactiveUserDetailsService;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
/**
* @author Administrator 2024-01-03 14:00:35
*/
public class CustomUserReactiveAuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {
private Scheduler scheduler = Schedulers.boundedElastic();
private UserDetailsChecker preAuthenticationChecks = this::defaultPreAuthenticationChecks;
private UserDetailsChecker postAuthenticationChecks = this::defaultPostAuthenticationChecks;
private PasswordEncoder passwordEncoder = passwordEncoder();
private CustomMapReactiveUserDetailsService userDetailsPasswordService = userDetailsService();
// private MapReactiveUserDetailsService userDetailsPasswordService = userDetailsService2();
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
String username = authentication.getName();
String presentedPassword = (String) authentication.getCredentials();
// return retrieveUser(username)
// // .switchIfEmpty(Mono.just(authentication)) // 空UserDetails, 说明此manager没有此用户
// .publishOn(scheduler)
// .filter(u -> passwordEncoder().matches(password, passwordEncoder().encode(u.getPassword())))
// .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
// .map(u-> new UsernamePasswordAuthenticationToken(u, u.getPassword()));
return retrieveUser(username)
// 空UserDetails, 说明此manager没有此用户, 交给下一个manager处理,
// 下一个manager即在myDelegatingAuthenticationManager()中定义的下一个manager
.switchIfEmpty(Mono.empty())
// 如果查询结果为空则抛出错误结果返回, 这里使用了myDelegatingAuthenticationManager()因此不执行此行代码
// .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
.doOnNext(this.preAuthenticationChecks::check)
.publishOn(this.scheduler)
.filter((userDetails) -> this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
.flatMap((userDetails) -> upgradeEncodingIfNecessary(userDetails, presentedPassword))
.doOnNext(this.postAuthenticationChecks::check)
.map(this::createUsernamePasswordAuthenticationToken);
}
@Override
protected Mono<UserDetails> retrieveUser(String username) {
return this.userDetailsPasswordService.findByUsername(username);
}
public CustomMapReactiveUserDetailsService userDetailsService() {
// 返回自定义的ReactiveUserDetailsService实现
UserDetails user = User.withUsername("admin")
.password("123")
.roles("USER")
.build();
return new CustomMapReactiveUserDetailsService(user);
}
public MapReactiveUserDetailsService userDetailsService2() {
// 返回自定义的ReactiveUserDetailsService实现
UserDetails user = User.withUsername("admin")
.password("123")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
private Mono<UserDetails> upgradeEncodingIfNecessary(UserDetails userDetails, String presentedPassword) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(userDetails.getPassword());
if (upgradeEncoding) {
String newPassword = this.passwordEncoder.encode(presentedPassword);
return this.userDetailsPasswordService.updatePassword(userDetails, newPassword);
}
return Mono.just(userDetails);
}
private UsernamePasswordAuthenticationToken createUsernamePasswordAuthenticationToken(UserDetails userDetails) {
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(),
userDetails.getAuthorities());
}
private void defaultPreAuthenticationChecks(UserDetails user) {
if (!user.isAccountNonLocked()) {
this.logger.debug("User account is locked");
throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
if (!user.isEnabled()) {
this.logger.debug("User account is disabled");
throw new DisabledException(
this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
}
if (!user.isAccountNonExpired()) {
this.logger.debug("User account is expired");
throw new AccountExpiredException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
private void defaultPostAuthenticationChecks(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
this.logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
}
}
EmailUserReactiveAuthenticationManager
package org.fiend.a.security.manager;
import org.fiend.a.security.service.EmailMapReactiveUserDetailsService;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
/**
* @author Administrator 2024-01-03 14:00:35
*/
public class EmailUserReactiveAuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {
private Scheduler scheduler = Schedulers.boundedElastic();
private UserDetailsChecker preAuthenticationChecks = this::defaultPreAuthenticationChecks;
private UserDetailsChecker postAuthenticationChecks = this::defaultPostAuthenticationChecks;
private PasswordEncoder passwordEncoder = emailPasswordEncoder();
private EmailMapReactiveUserDetailsService emailMapReactiveUserDetailsService = userDetailsService();
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
String username = authentication.getName();
String presentedPassword = String.valueOf(authentication.getCredentials());
// return retrieveUser(username)
// // .switchIfEmpty(Mono.empty()) // 空UserDetails, 说明此manager没有此用户
// .doOnNext(this.preAuthenticationChecks::check)
// .publishOn(scheduler)
// .filter(u -> passwordEncoder.matches(password, emailPasswordEncoder().encode(u.getPassword())))
// .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
// .map(u-> new UsernamePasswordAuthenticationToken(u, u.getPassword()));
return retrieveUser(username)
// 空UserDetails, 说明此manager没有此用户, 交给下一个manager处理,
// 下一个manager即在myDelegatingAuthenticationManager()中定义的下一个manager
.switchIfEmpty(Mono.empty())
// 如果查询结果为空则抛出错误结果返回, 这里使用了myDelegatingAuthenticationManager()因此不执行此行代码
// .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
// .doOnNext(this.preAuthenticationChecks::check)
.publishOn(scheduler)
// .filter(u -> passwordEncoder.matches(password, passwordEncoder.encode(u.getPassword())))
.filter(u -> passwordEncoder.matches(presentedPassword, u.getPassword()))
// .flatMap((userDetails) -> upgradeEncodingIfNecessary(userDetails, presentedPassword))
// .doOnNext(this.postAuthenticationChecks::check)
.map(this::createUsernamePasswordAuthenticationToken);
}
@Override
protected Mono<UserDetails> retrieveUser(String username) {
return userDetailsService().findByUsername(username);
}
private EmailMapReactiveUserDetailsService userDetailsService() {
// 返回自定义的ReactiveUserDetailsService实现
UserDetails user = User.withUsername("test@163.com")
.password("123")
.roles("USER")
.build();
return new EmailMapReactiveUserDetailsService(user);
}
public PasswordEncoder emailPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
private Mono<UserDetails> upgradeEncodingIfNecessary(UserDetails userDetails, String presentedPassword) {
boolean upgradeEncoding = this.emailMapReactiveUserDetailsService != null
&& this.passwordEncoder.upgradeEncoding(userDetails.getPassword());
if (upgradeEncoding) {
String newPassword = this.passwordEncoder.encode(presentedPassword);
return this.emailMapReactiveUserDetailsService.updatePassword(userDetails, newPassword);
}
return Mono.just(userDetails);
}
private UsernamePasswordAuthenticationToken createUsernamePasswordAuthenticationToken(UserDetails userDetails) {
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(),
userDetails.getAuthorities());
}
private void defaultPreAuthenticationChecks(UserDetails user) {
if (!user.isAccountNonLocked()) {
this.logger.debug("User account is locked");
throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
if (!user.isEnabled()) {
this.logger.debug("User account is disabled");
throw new DisabledException(
this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
}
if (!user.isAccountNonExpired()) {
this.logger.debug("User account is expired");
throw new AccountExpiredException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
private void defaultPostAuthenticationChecks(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
this.logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
}
}
LdapUserReactiveAuthenticationManager
package org.fiend.a.security.manager;
import org.fiend.a.security.service.LdapMapReactiveUserDetailsService;
import org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
/**
* @author Administrator 2024-01-03 14:00:35
*/
public class LdapUserReactiveAuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {
private Scheduler scheduler = Schedulers.boundedElastic();
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
return retrieveUser(username)
// 在delegatingAuthenticationManager()中最后一个manager,
// 说明没有此用户, 需要报异常, 因此该行需要注释掉
// .switchIfEmpty(Mono.empty())
// 如果查询结果为空则抛出错误结果返回, 该manager是myDelegatingAuthenticationManager()最后一个manager,
// 因此查询结果为空, 需要抛出错误
.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
.publishOn(scheduler)
.filter(u -> emailPasswordEncoder().matches(password, emailPasswordEncoder().encode(u.getPassword())))
.map(this::createUsernamePasswordAuthenticationToken);
}
@Override
protected Mono<UserDetails> retrieveUser(String username) {
return userDetailsService().findByUsername(username);
}
private LdapMapReactiveUserDetailsService userDetailsService() {
// 返回自定义的 ReactiveUserDetailsService 实现
UserDetails user = User.withUsername("dc=example,dc=org")
.password("123")
.roles("USER")
.build();
return new LdapMapReactiveUserDetailsService(user);
}
public PasswordEncoder emailPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
private UsernamePasswordAuthenticationToken createUsernamePasswordAuthenticationToken(UserDetails userDetails) {
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(),
userDetails.getAuthorities());
}
}
SecurityConfig
package org.fiend.a.security;
import org.fiend.a.security.manager.CustomUserReactiveAuthenticationManager;
import org.fiend.a.security.manager.EmailUserReactiveAuthenticationManager;
import org.fiend.a.security.manager.LdapUserReactiveAuthenticationManager;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* 实现多种登录方式 -- 普通用户名密码登录, 邮箱登录, Ldap登录等
* @author Administrator 2024-01-16 9:48:50
*/
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain mySpringWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.anyExchange().authenticated()
.and()
// 不能同时设置多个manager,否则, 只会最后一个生效
.authenticationManager(myDelegatingAuthenticationManager())
// .authenticationManager(customAuthenticationManager())
// .authenticationManager(new CustomUserReactiveAuthenticationManager())
// .authenticationManager(new EmailUserReactiveAuthenticationManager())
// .authenticationManager(new LdapUserReactiveAuthenticationManager())
.formLogin()
// .authenticationManager(myDelegatingAuthenticationManager())
.and()
.httpBasic()
.and()
.build();
}
/* ====================== 组合多个认证提供者 =========================== */
/**
* 主要是通过 DelegatingReactiveAuthenticationManager 的
* List<ReactiveAuthenticationManager>
* 来添加不同的ReactiveAuthenticationManager实现不同方式的登录
* (用户名密码登录, 邮箱登录, Ldap登录等)
*/
public DelegatingReactiveAuthenticationManager myDelegatingAuthenticationManager() {
return new DelegatingReactiveAuthenticationManager(
new CustomUserReactiveAuthenticationManager(),
new EmailUserReactiveAuthenticationManager(),
new LdapUserReactiveAuthenticationManager());
}
/* ====================== 组合多个认证提供者 =========================== */
/**
* 主要是通过 DelegatingReactiveAuthenticationManager 的
* List<ReactiveAuthenticationManager>
* 来添加不同的ReactiveAuthenticationManager实现不同方式的登录
* (用户名密码登录, 邮箱登录, Ldap登录等)
*/
public DelegatingReactiveAuthenticationManager delegatingAuthenticationManager() {
return new DelegatingReactiveAuthenticationManager(
customAuthenticationManager(),
emailAuthenticationManager(),
ldapAuthenticationManager());
}
/* =========================== 普通用户名密码登录 =========================== */
public ReactiveAuthenticationManager customAuthenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager =
new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService());
// 可能需要设置密码编码器
manager.setPasswordEncoder(passwordEncoder());
return manager;
}
public ReactiveUserDetailsService userDetailsService() {
// 返回自定义的ReactiveUserDetailsService实现
UserDetails user = User.withUsername("admin")
.password("123")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/* =========================== 邮箱登录 =========================== */
public ReactiveAuthenticationManager emailAuthenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager =
new UserDetailsRepositoryReactiveAuthenticationManager(emailUserDetailsService());
// 可能需要设置密码编码器
manager.setPasswordEncoder(emailPasswordEncoder());
return manager;
}
public ReactiveUserDetailsService emailUserDetailsService() {
// 返回自定义的ReactiveUserDetailsService实现
UserDetails user = User.withUsername("email")
.password("123")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
public PasswordEncoder emailPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/* =========================== LDAP登录 =========================== */
public ReactiveAuthenticationManager ldapAuthenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager =
new UserDetailsRepositoryReactiveAuthenticationManager(ldapUserDetailsService());
// 可能需要设置密码编码器
manager.setPasswordEncoder(ldapPasswordEncoder());
return manager;
}
public ReactiveUserDetailsService ldapUserDetailsService() {
// 返回自定义的ReactiveUserDetailsService实现
UserDetails user = User.withUsername("email")
.password("123")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
public PasswordEncoder ldapPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
LoginController
package org.fiend.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
/**
* @author 86133 2023-05-11 9:12:44
*/
@RestController
public class LoginController {
Logger log = LoggerFactory.getLogger(getClass());
/**
* @return
*/
@RequestMapping("login")
public Mono<String> login() {
return Mono.just("login success!");
}
/**
* localhost:8600/hello
* @return
*/
@RequestMapping("/hello")
public Mono<String> hello() {
return Mono.just("Flux");
}
}
本文暂时没有评论,来添加一个吧(●'◡'●)