02.用户信息UserDetails相关入门

news2024/11/24 7:16:49

1. 前言

前一篇介绍了 Spring Security 入门的基础准备。从这篇开始我们来一步步窥探它是如何工作的。我们又该如何驾驭它。本篇将通过 Spring Boot 2.x 来讲解 Spring Security 中的用户主体UserDetails。以及从中找点乐子。

2. Spring Boot 集成 Spring Security

这个简直老生常谈了。不过为了照顾大多数还是说一下。集成 Spring Security 只需要引入其对应的 Starter 组件。Spring Security 不仅仅能保护Servlet Web 应用,也可以保护Reactive Web应用,本文我们讲前者。我们只需要在 Spring Security 项目引入以下依赖即可:

    <dependencies>
        <!--  actuator 指标监控  非必须 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--  spring security starter 必须  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- spring mvc  servlet web  必须  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--   lombok 插件 非必须       -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 测试   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

3. UserDetailsServiceAutoConfiguration

启动项目,访问Actuator端点http://localhost:8080/actuator会跳转到一个登录页面http://localhost:8080/login如下:

在这里插入图片描述

要求你输入用户名 Username (默认值为user)和密码 Password 。密码在springboot控制台会打印出类似 Using generated security password: e1f163be-ad18-4be1-977c-88a6bcee0d37 的字样,后面的长串就是密码,当然这不是生产可用的。如果你足够细心会从控制台打印日志发现该随机密码是由UserDetailsServiceAutoConfiguration 配置类生成的,我们就从它开始顺藤摸瓜来一探究竟。

3.1 UserDetailsService

UserDetailsService接口。该接口只提供了一个方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

该方法很容易理解:通过用户名来加载用户 。这个方法主要用于从系统数据中查询并加载具体的用户到Spring Security中。

3.2 UserDetails

从上面UserDetailsService 可以知道最终交给Spring Security的是UserDetails 。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象Authentication中去。UserDetails 默认提供了:

  • 用户的权限集, 默认需要添加ROLE_ 前缀
  • 用户的加密后的密码, 不加密会使用{noop}前缀
  • 应用内唯一的用户名
  • 账户是否过期
  • 账户是否锁定
  • 凭证是否过期
  • 用户是否可用

如果以上的信息满足不了你使用,你可以自行实现扩展以存储更多的用户信息。比如用户的邮箱、手机号等等。通常我们使用其实现类:

org.springframework.security.core.userdetails.User

该类内置一个建造器UserBuilder 会很方便地帮助我们构建UserDetails 对象,后面我们会用到它。

3.3 UserDetailsServiceAutoConfiguration

UserDetailsServiceAutoConfiguration 全限定名为:

org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

源码如下:

@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {

	private static final String NOOP_PASSWORD_PREFIX = "{noop}";

	private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{. }.*$");

	private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

	@Bean
	@ConditionalOnMissingBean(
			type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
	@Lazy
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider<PasswordEncoder> passwordEncoder) {
		SecurityProperties.User user = properties.getUser();
		List<String> roles = user.getRoles();
		return new InMemoryUserDetailsManager(
				User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
						.roles(StringUtils.toStringArray(roles)).build());
	}

	private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
		String password = user.getPassword();
		if (user.isPasswordGenerated()) {
			logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
		}
		if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
			return password;
		}
		return NOOP_PASSWORD_PREFIX   password;
	}

}

我们来简单解读一下该类,从@Conditional系列注解我们知道该类在类路径下存在AuthenticationManager、在Spring 容器中存在Bean ObjectPostProcessor并且不存在Bean AuthenticationManager, AuthenticationProvider, UserDetailsService的情况下生效。千万不要纠结这些类干嘛用的! 该类只初始化了一个UserDetailsManager 类型的Bean。UserDetailsManager 类型负责对安全用户实体抽象UserDetails的增删查改操作。同时还继承了UserDetailsService接口。

明白了上面这些让我们把目光再回到UserDetailsServiceAutoConfiguration 上来。该类初始化了一个名为InMemoryUserDetailsManager 的内存用户管理器。该管理器通过配置注入了一个默认的UserDetails存在内存中,就是我们上面用的那个user ,每次启动user都是动态生成的。那么问题来了如果我们定义自己的UserDetailsManager Bean是不是就可以实现我们需要的用户管理逻辑呢?

3.4 自定义UserDetailsManager

我们来自定义一个UserDetailsManager 来看看能不能达到自定义用户管理的效果。首先我们针对UserDetailsManager 的所有方法进行一个代理的实现,我们依然将用户存在内存中,区别就是这是我们自定义的:

package cn.felord.spring.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.HashMap;
import java.util.Map;

/**
 * 代理 {@link org.springframework.security.provisioning.UserDetailsManager} 所有功能
 *
 * @author Felordcn
 */
public class UserDetailsRepository {

    private Map<String, UserDetails> users = new HashMap<>();


    public void createUser(UserDetails user) {
        users.putIfAbsent(user.getUsername(), user);
    }


    public void updateUser(UserDetails user) {
        users.put(user.getUsername(), user);
    }


    public void deleteUser(String username) {
        users.remove(username);
    }


    public void changePassword(String oldPassword, String newPassword) {
        Authentication currentUser = SecurityContextHolder.getContext()
                .getAuthentication();

        if (currentUser == null) {
            // This would indicate bad coding somewhere
            throw new AccessDeniedException(
                    "Can't change password as no Authentication object found in context "
                              "for current user.");
        }

        String username = currentUser.getName();

        UserDetails user = users.get(username);


        if (user == null) {
            throw new IllegalStateException("Current user doesn't exist in database.");
        }

        // todo copy InMemoryUserDetailsManager  自行实现具体的更新密码逻辑
    }


    public boolean userExists(String username) {

        return users.containsKey(username);
    }


    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return users.get(username);
    }
}

该类负责具体对UserDetails 的增删改查操作。我们将其注入Spring 容器:

    @Bean
    public UserDetailsRepository userDetailsRepository() {
        UserDetailsRepository userDetailsRepository = new UserDetailsRepository();

        // 为了让我们的登录能够运行 这里我们初始化一个用户Felordcn 密码采用明文 当你在密码12345上使用了前缀{noop} 意味着你的密码不使用加密,authorities 一定不能为空 这代表用户的角色权限集合
        UserDetails felordcn = User.withUsername("Felordcn").password("{noop}12345").authorities(AuthorityUtils.NO_AUTHORITIES).build();
        userDetailsRepository.createUser(felordcn);
        return userDetailsRepository;
    }

为了方便测试 我们也内置一个名称为Felordcn 密码为12345UserDetails用户,密码采用明文 当你在密码12345上使用了前缀{noop} 意味着你的密码不使用加密,这里我们并没有指定密码加密方式你可以使用PasswordEncoder 来指定一种加密方式。通常推荐使用Bcrypt作为加密方式。默认Spring Security使用的也是此方式。authorities 一定不能为null 这代表用户的角色权限集合。接下来我们实现一个UserDetailsManager 并注入Spring 容器:

    @Bean
    public UserDetailsManager userDetailsManager(UserDetailsRepository userDetailsRepository) {
        return new UserDetailsManager() {
            @Override
            public void createUser(UserDetails user) {
                userDetailsRepository.createUser(user);
            }

            @Override
            public void updateUser(UserDetails user) {
                userDetailsRepository.updateUser(user);
            }

            @Override
            public void deleteUser(String username) {
                userDetailsRepository.deleteUser(username);
            }

            @Override
            public void changePassword(String oldPassword, String newPassword) {
                userDetailsRepository.changePassword(oldPassword, newPassword);
            }

            @Override
            public boolean userExists(String username) {
                return userDetailsRepository.userExists(username);
            }

            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                return userDetailsRepository.loadUserByUsername(username);
            }
        };
    }

这样实际执行委托给了UserDetailsRepository 来做。我们重复 章节3. 的动作进入登陆页面分别输入Felordcn12345 成功进入。

3.5 数据库管理用户

经过以上的配置,你已经知道如何使用数据库来管理用户了 。只需要将 UserDetailsRepository 中的 users 属性替代为抽象的Dao接口就行了,无论你使用Jpa还是Mybatis来实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/880427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

同比增长近4倍!5G智能座舱爆发

5G智能座舱&#xff0c;正在进入爆发期。 高工智能汽车研究院监测数据显示&#xff0c;2023年1-6月中国市场&#xff08;不含进出口&#xff09;乘用车前装标配5G智能座舱交付63.18万辆&#xff08;含选装&#xff09;&#xff0c;同比增长370.09%。 同时&#xff0c;5G与车载智…

透镜天线的分类、特点及龙伯球透镜天线原理

透镜天线&#xff0c;一种能够通过电磁波&#xff0c;将点源或线源的球面波或柱面波转换为平面波从而获得笔形、扇形或其他形状波束的天线。通过合适设计透镜表面形状和折射率n&#xff0c;调节电磁波的相速以获得辐射口径上的平面波前。透镜天线吸收了许多光信息工程技术&…

蓝牙资讯|苹果智能戒指可以通过多个戒指控制用户界面

近日&#xff0c;美国专利商标局公布了苹果公司的一项专利申请&#xff0c;涉及使用单个或多个智能指环来控制用户界面的各个方面。Apple Vision Pro 使用眼动追踪和摄像头来监控用户手指的空中手势控制 visionOS&#xff0c;就像鼠标使用 Mac 一样&#xff0c;戒指专利有一个明…

问道管理:三大股指触底反弹 AI算力方向再度崛起

周一&#xff0c;受人民币汇率动摇等要素影响&#xff0c;A股三大股指早盘深度回撤&#xff0c;沪指盘中创出1月中旬以来新低。午间休市前后&#xff0c;券商与人工智能板块相继发力&#xff0c;带动股指止跌回升&#xff0c;大盘终究以全天的相对高点报收。 截至14日收盘&…

【微服务技术一】Eureka、Nacos、Ribbon(配置管理、注册中心、负载均衡)

微服务技术一 技术栈图一、注册中心Eureka概念&#xff1a;搭建EurekaServer服务注册服务发现&#xff08;消费者对提供者的远程调用&#xff09; 二、Ribbon负载均衡负载均衡的原理&#xff1a;LoadBalanced负载均衡的策略&#xff1a;IRule懒加载 三、Nacos注册中心Nacos的安…

国内芯片厂商创新突破,助力国产替代持续加速

近日&#xff0c;中商产业研究院发布最新研究报告显示&#xff0c;今年1~5月份中国进口集成电路为1865亿件&#xff0c;同比下降19.6%&#xff0c;同比去年5个月累计少进口了455亿颗&#xff0c;平均每天少进口3亿颗。与此同时&#xff0c;英特尔、AMD、美光、三星、SK海力士等…

面试题解析 | 为什么Redis使用单线程性能会优于多线程?

大家好&#xff0c;我是小米&#xff01;今天我要和大家聊一个有关Redis的热门面试题&#xff1a;为什么Redis使用单线程性能会优于多线程&#xff1f;相信这个问题在很多同学心中都曾经纠结过&#xff0c;那么接下来&#xff0c;就让我们一起来揭开这个技术之谜吧&#xff01;…

flac格式如何转mp3?简单的音频格式转换方法分享

FLAC格式音频的缺点主要在于文件大小较大&#xff0c;相比于MP3和AAC等有损压缩格式&#xff0c;FLAC的压缩率较低&#xff0c;因此占用的存储空间更多。此外&#xff0c;由于FLAC格式相对较新&#xff0c;不是所有的音频设备都支持该格式。那么我们怎么将FLAC格式音频转成MP3格…

python质检工具(pylint)安装使用总结

1、Pylint Pylint工具主要类似java中的checkStyle和findbugs,是检查代码样式和逻辑规范的工具。 1.1、Pylint安装流程: 打开PyCharm软件,打开如图1.1所示Terminal终端窗口,先查看python版本和pip版本,pip是19.0.3,python是2.7 图1.1 运行pip install pylint安装pylin…

2023全球创见者大会|企企通总架构师杨华:基于SRM的电子发票解决方案, 破局企业开票困局

01、2023全球创见者大会 2023年8月8日&#xff0c;金蝶30周年庆典&2023全球创见者大会在深圳国际会展中心隆重举行。现场吸引了4000与会来宾齐聚一堂&#xff0c;超过100位演讲嘉宾组成豪华阵容&#xff0c;举办16场行业峰会&#xff0c;呈现了一场数字化领域备受瞩目…

PCIe接口的PCB布局布线要求

PCI-Express&#xff0c;简称“PCI-e”是一种高速串行计算机扩展总线标准&#xff0c;PCI-E属于高速串行点对点双通道高带宽传输&#xff0c;所连接的设备分配独享通道带宽&#xff0c;不共享总线带宽&#xff0c;它的主要优势就是数据传输速率高。 PCI-E2.0和PCI-E3.0主要存在…

国家唯一认证的祛斑产品,安全好用温和的一款

国家唯一认证的祛斑产品露卡菲娅祛斑套装效果好么&#xff1f;对于减少肌肤暗沉&#xff0c;美白祛斑&#xff0c;这是护肤界经久不衰的话题。拥有白嫩无瑕的肌肤&#xff0c;整个人的气质也是会提高很多&#xff0c;所以在追求美白的道路上&#xff0c;我们一直执着追求&#…

Unity ARFoundation 配置工程 (Android)

注意&#xff1a; 1、AR Core是Google的产品&#xff0c;因为谷歌制裁华为&#xff0c;所以 有些 华为机可能不支持AR Core的软件&#xff1b; 2、手机在设置里搜索Google Play&#xff0c;看看是否已经安装上了&#xff0c;如果没有装此服务&#xff0c;去商城里搜索Google Pl…

使用Docker搭建MySQL主从复制(一主一从)

Docker安装MySQL docker pull mysql:5.7 docker images mysql安装步骤 1.新建主服务器容器实例3307 docker run -p 3307:3306 --name mysql-master -v /usr/local/docker/mysql5.7/data/mysql-master/logs:/var/log/mysql -v /usr/local/docker/mysql5.7/data/mysql-master/…

Java # 类加载子系统

一、概述 1、 类加载器子系统负责从文件系统或者网络中加载.Class文件 2、classloader只负责类的加载&#xff0c;至于他是否能够运行由执行引擎来决定 3、加载的类的信息会存放在方法区&#xff08;元空间&#xff09;中 二、加载过程 ​​​​​​​ 1、加载阶段 1、通…

Docker Desktop - WSL kernel version too low

win10命令行运行 wsl --update 如果报启动docker还是报网络连接错误&#xff0c;命令行执行以下命令并重启 netsh winsock reset

PackageNotFoundError: No package metadata was found for bitsandbytes解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

ESLint是什么?

ESLint 介绍 ESLint 是一款插件&#xff0c;主要用来检测编写的&#xff08; JavaScript &#xff09;代码是否符合规范。当然在一个团队中也会自定义一些规范条件。另外正常情况下我们不需要单独安装 ESLint 去使用&#xff0c;这里只是为了做演示。例如 vue-cli 脚手架搭建的…

CSS自学框架之表单

首先我们看一下表单样式&#xff0c;下面共有5张截图 一、CSS代码 /*表单*/fieldset{border: none;margin-bottom: 2em;}fieldset > *{ margin-bottom: 1em }fieldset:last-child{ margin-bottom: 0 }fieldset legend{ margin: 0 0 1em }/* legend标签是CSS中用于定义…

邀请函|澎峰科技邀您参加CCF HPC China2023

一年一度的全球超算盛会&#xff01; 以“算力互联智领未来”为主题的第十九届全国高性能计算学术年会&#xff08;CCF HPC China 2023&#xff09;将于8月24-26日&#xff08;展览23-25日&#xff09;在青岛红岛国际会议展览中心举办。 九大院士领衔 打造顶级超算盛会 力邀…