SpringSecurity(三):自定义认证数据源(源码+落地实现)。

news2024/11/15 8:23:04

自定义认证数据源

    • 前言
    • 认证流程分析
      • 易混梳理
        • AuthenticationManager 与 ProviderManager
        • ProviderManager 与 AuthenticationProvider
          • 难点:为什么ProviderManager会有一个parent?
    • 数据源的获取
    • 配置AuthenticationManager
      • 默认的全局 AuthenticationManager
      • 自定义AuthenticationManager
    • 库表设计
    • 代码实现
      • 踩坑点
      • 引入依赖
      • 配置 springboot 配置⽂件
      • 创建entity
        • 创建User对象
        • 创建Role对象
        • 创建UserDao接口
        • 创建UserMapper实现
        • 创建 UserDetailService 实例
        • 配置 authenticationManager 使⽤⾃定义UserDetailService
        • 最后启动测试

前言

看这篇文章前,请确保有一定security基础或者看过上篇文章,没基础看不太好理解。

认证流程分析

流程分析我们在上一篇每一步都debug看一了下,这里我们进行一个梳理。
我们先看一下这张图:
在这里插入图片描述

  1. 发起认证请求,请求中携带⽤户名、密码,该请求会被
    UsernamePasswordAuthenticationFilter 拦截
  2. UsernamePasswordAuthenticationFilterattemptAuthentication⽅法
    中将请求中⽤户名和密码,封装为Authentication对象,并交给
    AuthenticationManager 进⾏认证
  3. 认证成功,将认证信息存储到SecurityContextHodler以及调⽤记住我等,并回调AuthenticationSuccessHandler处理
  4. 认证失败,清除SecurityContextHodler以及 记住我中信息,回调AuthenticationFailureHandler 处理

易混梳理

不知道在上一篇我们debug时,你是不是有点迷,搞不清AuthenticationManagerProviderManagerAuthenticationProvider 的关系,
AuthenticationManager 是认证的核⼼类,但实际上在底层真正认
证时还离不开 ProviderManager 以及 AuthenticationProvider 。下面我们来梳理一下:

  • AuthenticationManager 是⼀个认证管理器,它定义了 Spring Security 过滤
    器要执⾏认证操作。
public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}
  • ProviderManagerAuthenticationManager接⼝的实现类。Spring Security
    认证时默认使⽤就是 ProviderManager
//代码太多,只列举较关键的代码
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();

	private List<AuthenticationProvider> providers = Collections.emptyList();

	private AuthenticationManager parent;
	
    @Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
//默认为空
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) 
				((CredentialsContainer) result).eraseCredentials();
			}
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}

		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

  • AuthenticationProvider 就是针对不同的身份类型执⾏的具体的身份认证。
public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	boolean supports(Class<?> authentication);
}

AuthenticationManager 与 ProviderManager

先看一下结构图:
请添加图片描述
为什么列表有五个,但是我只显示了ProviderManager,因为其他四个都是内部类(感兴趣可以去看看)。
所以我们可以看出来:ProviderManagerAuthenticationManager的唯⼀实现,也是 Spring Security默认使⽤实现。从这⾥不难看出默认情况下AuthenticationManager 就是⼀个ProviderManager。

ProviderManager 与 AuthenticationProvider

看一下官网的介绍图:
在这里插入图片描述
Spring Seourity中,允许系统同时⽀持多种不同的认证⽅式,例如同时⽀持⽤
户名/密码认证、ReremberMe 认证、⼿机号码动态认证等,⽽不同的认证⽅式对应了不同
AuthenticationProvider,所以⼀个完整的认证流程可能由多个
AuthenticationProvider 来提供。
多个 AuthenticationProvider 将组成⼀个列表,这个列表将由
ProviderManager 代理。换句话说,在ProviderManager 中存在⼀个
AuthenticationProvider 列表,在Provider Manager 中遍历列表中的每⼀个
AuthenticationProvider 去执⾏身份认证,最终得到认证结果。

ProviderManager 本身也可以再配置⼀个 AuthenticationManager 作为
parent,这样当ProviderManager 认证失败之后,就可以进⼊到 parent 中再次进⾏认
证。

理论上来说,ProviderManager的 parent 可以是任意类型的
AuthenticationManager,但是通常都是由
ProviderManager 来扮演 parent 的⻆⾊,也就是 ProviderManager
ProviderManager 的 parent。

难点:为什么ProviderManager会有一个parent?

ProviderManager 本身也可以有多个,多个ProviderManager 共⽤同⼀个
parent。

因为有时,⼀个应⽤程序有受保护资源的逻辑组(例如,所有符合路径模式的⽹络资
源,如/api!!*),每个组可以有⾃⼰的专⽤ AuthenticationManager。通常,每个组
都是⼀个ProviderManager,它们共享⼀个⽗级。然后,⽗级是⼀种 全局资源,作为所有
提供者的后备资源。

根据上⾯的介绍,我们绘出新的 AuthenticationManagerProvideManager
AuthentictionProvider关系:
在这里插入图片描述

数据源的获取

默认情况下AuthenticationProvider 是由 DaoAuthenticationProvider 类来实现认证的(上篇文章有提到过,不清楚的可以去看一下debug的过程,这个是parent的默认provider),在DaoAuthenticationProvider 认证时⼜通过 UserDetailsService 完成数据源的
校验。
下面我们debug看一下这个流程:

  1. 首先我们执行到DaoAuthenticationProvider这块内容:
    在这里插入图片描述
  2. 执行到authenticate方法下:
    请添加图片描述
  3. 执行authenticate方法,跳转到AbstractUserDetailsAuthenticationProvider这个方法下的authenticate
    为什么会跳转到跳转到AbstractUserDetailsAuthenticationProvider,因为DaoAuthenticationProvider没有覆盖这个方法,所以用的是父类AbstractUserDetailsAuthenticationProvider的。
    在这里插入图片描述
  4. 一路执行到执行retrieveUser方法并进入到方法里面:
    请添加图片描述
  5. 往下执行到loadUserByUsername方法并进入,通过调用内存中的数据完成认证:
    在这里插入图片描述
    下面是一个流程图:
    请添加图片描述
    总结:
    AuthenticationManager 是认证管理器,在 Spring Security 中有全局
    AuthenticationManager,也可以有局部AuthenticationManager
    全局的AuthenticationManager⽤来对全局认证进⾏处理,局部的AuthenticationManager⽤来对某些特殊资源认证处理。
    当然⽆论是全局认证管理器还是局部认证管理器都是由ProviderManger 进⾏实现。
    每⼀个ProviderManger中都代理⼀个AuthenticationProvider的列表,列表中每⼀个实现代表⼀种身份认证⽅式。认证时底层数据源需要调⽤ UserDetailService来实现。

配置AuthenticationManager

上面我们了解到AuthenticationManager的作用是什么,接下来我们去写一下这个,如果业务工作有需求,可以自定义配置。

首先,我们之前在application.properties里面配置的账号信息(名称,密码,权限)注释一下。
请添加图片描述

默认的全局 AuthenticationManager

springboot 对 security 进⾏⾃动配置时⾃动在⼯⼚中创建⼀个全局AuthenticationManager
代码例子如下(不优化版本):

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
 public void initialize(AuthenticationManagerBuilder builder) throws Exception {
     System.out.println("SpringBoot默认配置:"+ builder);
     InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
     userDetailsService.createUser(User.withUsername("aaa").password("{noop}123").roles("admin").build());
     builder.userDetailsService(userDetailsService);
 }
 //configue方法省略 
 }

结果如下:
请添加图片描述
第二种(优化后版本):
优化思路:
AuthenticationManagerBuilder是默认配置对象,它默认找当前项目中是否存在自定义UserDetailService实例自动将当前项目UserDetailService实例设置为数据源。
我们看一下UserDetailServiceAutoConfigutation源码:

@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
		value = { AuthenticationManager.class,
		          AuthenticationProvider.class,   
		          UserDetailsService.class,
				  AuthenticationManagerResolver.class },
public class UserDetailsServiceAutoConfiguration {
//...
	@Bean
	@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());
	}
//...

我们在最上面的注解@ConditionalOnMissingBean中发现 UserDetailsService.class,意思是如果有这个类,那么就不调用下面
inMemoryUserDetailsManager这个方法,就会直接用我们的给到AuthenticationManagerBuilder。

那么我们可以优化,代码如下:

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(
                User.withUsername("aaa")
                .password("{noop}123")
                .roles("admin")
                .build());
     return userDetailsService;
    }

//    @Autowired
//    public void initialize(AuthenticationManagerBuilder builder) throws Exception {
//        System.out.println("SpringBoot默认配置:" + builder);
//        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
//        userDetailsService.createUser(User.withUsername("aaa").password("{noop}123").roles("admin").build());
//        builder.userDetailsService(userDetailsService);
//    }

只要我们有自定义的UserDetailService,工厂默认的就不生效了。而且SpringBoot会自动将我们创建的Bean—userDetailsService赋值给默认创建出来的AuthenticationManagerAuthenticationManager会自动检测)。

自定义AuthenticationManager

自定义代码(错误版本)如下:

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(
                User.withUsername("aaa")
                .password("{noop}123")
                .roles("admin")
                .build());
     return userDetailsService;
    }
    
   @Override
    public void configure(AuthenticationManagerBuilder builder){
        System.out.println("自定义Authenticationmanager:"+builder);
    }

这样会配置失败,因为自定义时,会覆盖掉工厂默认方法,SpringBoot就不会再自动配置了,所以此时我们的AuthenticationManagerBuilder就不会自动去找userDetailsService了,需要手动设置。

正确版本:

  @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(
                User.withUsername("aaa")
                .password("{noop}123")
                .roles("admin")
                .build());
     return userDetailsService;
    }

    //自定义AuthenticationManager
    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        System.out.println("自定义Authenticationmanager:"+builder);
        builder.userDetailsService(userDetailsService());
    }

结果如下:
请添加图片描述
但是上面还存在一个问题——自定义的AuthenticationManager并没有在工厂中暴露出来,只能这个类中使用。

如果我们想让它在工厂中暴露,可以在任何位置注入,添加覆盖下面的方法即可:

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

我们进去简单看看,它会调用父类的实现:

protected AuthenticationManager authenticationManager() throws Exception {

		if (!this.authenticationManagerInitialized) {
			configure(this.localConfigureAuthenticationBldr);
			如果是本地的,暴露本地的
			if (this.disableLocalConfigureAuthenticationBldr) {
				this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
			}
			
			else {
				this.authenticationManager = this.localConfigureAuthenticationBldr.build();
			}
			this.authenticationManagerInitialized = true;
		}
		//如果不是,暴露是springboot默认的
		return this.authenticationManager;
	}

库表设计

从上面的内容我们可以看出,真正帮我们做底层用户认证的是UserDetailsService,它有一个loadUserByUsername方法,传入一个用户名,默认是在内存中。
所以我们自己写实现这个方法,并设置给AuthenticationManager,实现更换数据源。

我们来看一下UserDetailsServiceloadUserByUsername方法:

	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

它的返回值是UserDetails,我们来看看它(注意,这是一个接口:

public interface UserDetails extends Serializable {

	Collection<? extends GrantedAuthority> getAuthorities();

	String getPassword();

	String getUsername();

	boolean isAccountNonExpired();

	boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

	boolean isEnabled();

}

它最底层的实现类我们可以返回一个User‘实例:
public class User implements UserDetails, CredentialsContainer

因为我们后面数据源要切换为数据库实现,所以需要一张用户表,那么我们就参考User来设计表,User源码如下:

public class User implements UserDetails, CredentialsContainer {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

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

	private String password;

	private final String username;

	private final Set<GrantedAuthority> authorities;

	private final boolean accountNonExpired;

	private final boolean accountNonLocked;

	private final boolean credentialsNonExpired;

	private final boolean enabled;
//...

根据上面信息,设计出表结构,如下:

// ⽤户表
CREATE TABLE `user`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`enabled` tinyint(1) DEFAULT NULL,
`accountNonExpired` tinyint(1) DEFAULT NULL,
`accountNonLocked` tinyint(1) DEFAULT NULL,
`credentialsNonExpired` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
// ⻆⾊表
CREATE TABLE `role`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`name_zh` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
// ⽤户⻆⾊关系表
CREATE TABLE `user_role`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(11) DEFAULT NULL,
`rid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
KEY `rid` (`rid`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

------------------------
//插入数据
BEGIN;
INSERT INTO `user`
VALUES (1, 'root', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user`
VALUES (2, 'admin', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user`
VALUES (3, 'blr', '{noop}123', 1, 1, 1, 1);
COMMIT;
// 插⼊⻆⾊数据
BEGIN;
INSERT INTO `role`
VALUES (1, 'ROLE_product', '商品管理员');
INSERT INTO `role`
VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO `role`
VALUES (3, 'ROLE_user', '⽤户管理员');
COMMIT;
// 插⼊⽤户⻆⾊数据
BEGIN;
INSERT INTO `user_role`
VALUES (1, 1, 1);
INSERT INTO `user_role`
VALUES (2, 1, 2);
INSERT INTO `user_role`
VALUES (3, 2, 2);
INSERT INTO `user_role`
VALUES (4, 3, 3);
COMMIT;

代码实现

踩坑点

  1. 请确认你的MySQL版本是否为8.0以上,如果为8.0以上,那么mysql引入的依赖注意版本参数,配置 springboot 配置⽂件时,spring.datasource.driver-class-name参数为:com.mysql.cj.jdbc.Driver
  2. mapper的xml文件中,如果resultTppe爆红,使用全路径即可。

引入依赖

  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        这里我用的是8.0版本的,请注意!!!
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

配置 springboot 配置⽂件

server.port= 9090

 # 关闭thymeleaf 缓存
spring.thymeleaf.cache= false

# 配置数据源
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/security?characterEncoding=UTF-8&useSSL=false&&serverTimezone=CST
spring.datasource.username= 输入你的用户名
spring.datasource.password= 输入你的密码

# Mybatis配置
# 注意mapper目录必须用"/"
mybatis.mapper-locations= classpath:com/wang/mapper/*.xml
mybatis.type-aliases-package=com.example.eneity

# 日志处理
logging.level.com.example = debug

创建entity

创建User对象


import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.*;


public class User implements UserDetails {
        private Integer id;
        private String username;
        private String password;
        private Boolean enabled;
        private Boolean accountNonExpired;
        private Boolean accountNonLocked;
        private Boolean credentialsNonExpired;
        private List<Role> roles = new ArrayList();
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {

           Set<GrantedAuthority> authorities = new HashSet();
            roles.forEach(role->{
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role.getName());
                authorities.add(simpleGrantedAuthority);
            });
            return authorities;
        }
        @Override
        public String getPassword() {
            return password;
        }
        @Override
        public String getUsername() {
            return username;
        }
        @Override
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }
        @Override
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }
        @Override
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }
        @Override
        public boolean isEnabled() {
            return enabled;
        }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Integer getId() {
        return id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Boolean getAccountNonExpired() {
        return accountNonExpired;
    }

    public void setAccountNonExpired(Boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public Boolean getAccountNonLocked() {
        return accountNonLocked;
    }

    public void setAccountNonLocked(Boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public Boolean getCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public List<Role> getRoles() {
        return roles;
    }
}

创建Role对象


public class Role {
    private Integer id;
    private String name;
    private String nameZh;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameZh() {
        return nameZh;
    }

    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }
}

创建UserDao接口

//注意这里引用的是我们自定义的类型
import com.example.entity.Role;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserDao {

    //提供根据用户名返回方法
    User loadUserByUsername(String username);
    
    //提供根据用户id查询用户角色信息方法
    List<Role> getRoleByUid(Integer id);
}

创建UserMapper实现

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserDao">

<!--        更具用户名查询用户方法-->
        <select id="loadUserByUsername" resultType="com.example.entity.User">
                select id,
                       username,
                       password,
                       enabled,
                       accountNonExpired,
                       accountNonLocked,
                       credentialsNonExpired
                from user
                where username = #{username}
        </select>
<!--        查询指定⾏数据-->
        <select id="getRoleByUid" resultType="com.example.entity.Role">
        select r.id,
        r.name,
        r.name_zh nameZh
        from role r,
        user_role ur
        where r.id = ur.rid
        and ur.uid = #{uid}
        </select>
</mapper>

创建 UserDetailService 实例

import com.example.dao.UserDao;
import com.example.entity.Role;
import com.example.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.List;

@Component
public class MyUserDetailService implements UserDetailsService {

    private final UserDao userDao;

    @Autowired
    public MyUserDetailService(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1. 查询用户
       User user = userDao.loadUserByUsername(username);
       if (ObjectUtils.isEmpty(user)) {
           throw new UsernameNotFoundException("用户名不正确");
       }
       //2. 查询权限信息
        List<Role> roles = userDao.getRoleByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

配置 authenticationManager 使⽤⾃定义UserDetailService

   private final MyUserDetailService myUserDetailService;


//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
//        userDetailsService.createUser(
//                User.withUsername("aaa")
//                .password("{noop}123")
//                .roles("admin")
//                .build());
//     return userDetailsService;
//    }

    @Autowired
    public WebSecurityConfigurer(MyUserDetailService myUserDetailService){
        this.myUserDetailService = myUserDetailService;
    }

    //自定义AuthenticationManager
    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        System.out.println("自定义Authenticationmanager:"+builder);
        builder.userDetailsService(myUserDetailService);
    }

最后启动测试

  1. 请确保你的数据库版本
  2. 确保你的数据在数据库里面
    最后结果:
    请添加图片描述

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

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

相关文章

RabbitMQ:高效传递消息的魔法棒,一篇带你助力构建可靠的分布式系统(上篇)

目录 一 什么是MQ1.1 MQ的概念1.2 MQ的流量消峰1.3 MQ的应用解耦1.4 MQ的异步处理1.5 MQ的分类以及如何选择1.5.1 ActiveMQ1.5.2 Apache Kafka1.5.3 RabbitMQ1.5.4 RocketMQ1.5.5 四种MQ的区别 1.6 MQ的核心概念1.6.1 MQ四大核心概念1.6.2 MQ六大核心部分 1.7 安装RabbitMQ 二.…

5.3 Web服务器简介及HTTP协议

5.3 Web服务器简介及HTTP协议 Web Server&#xff08;网页服务器&#xff09; 一个Web Server就是一个服务器软件&#xff08;程序&#xff09;&#xff0c;或者是运行这个服务器软件的硬件&#xff08;计算机&#xff09;。其主要功能是通过HTTP协议与客户端&#xff08;通常…

【洛谷】B3643 图的存储(邻接矩阵+邻接表)

思路简单&#xff0c;话不多说直接上ACcode(详细注释): ACcode:(安全饲用)&#xff1a; #include<bits/stdc.h> using namespace std; #define int long long const int N1e310; int mp[N][N],n,m; vector<int>v[N]; void fun1(){//邻接矩阵打印 for(int i1;i<…

学成在线----项目优化

1、项目优化-redis缓存 2、缓存穿透 使用缓存后代码的性能有了很大的提高&#xff0c;虽然性能有很大的提升但是控制台打出了很多“从数据库查询”的日志&#xff0c;明明判断了如果缓存存在课程信息则从缓存查询&#xff0c;为什么要有这么多从数据库查询的请求的&#xff1f…

RedmiBook Pro 15S AMD Ryzen 7 5800H电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件配置 硬件型号驱动情况 主板RedmiBook Pro 15S 2021 处理器AMD Ryzen™ 7 5800H已驱动 内存16 GB 3200 MHz DDR4已驱动 硬盘Samsung 970EVO 512GB已驱动 显卡HD …

frida编译

官方过程 frida编译 Windows Make sure you have: Visual Studio 2022 Git on your PATH Python 3.10 on your PATH Select Add Python 3.10 to PATH Set the installation directory to C:\Program Files\Python310\, or edit releng\frida.props to change the PythonLocat…

Java安全——加密介绍

Java安全 加密介绍 在网络传输中&#xff0c;数据的加密鉴别是非常重要的&#xff0c;它可以保护数据的安全性&#xff0c;防止数据被窃取或篡改&#xff0c;并通过鉴别机制确保数据的完整性和真实性。Java安全中的加密鉴别主要包括对称加密算法、非对称加密算法、数字签名和证…

Pytroch本地安装方法

1 查看电脑安装的cuda版本 nvidia-smi这里的红圈标注的是cuda 最高版本&#xff0c;虚拟环境中安装&#xff0c;只要不超过12.0就可以 第二步 去官网看torch, torchvision等版本的匹配关系 https://pytorch.org/get-started/previous-versions/ 比如这里&#xff1a; &…

低功耗设计

功耗影响 便携性 功耗越低&#xff0c;同等电量下电子产品工作时间越长&#xff0c;便携性设备的电池容量和体积设计的困难度也会降低。例如&#xff0c;手机越做越薄&#xff0c;性能还不受影响&#xff0c;就是因为低功耗设计发挥了至关重要的作用。 性能 功耗越大&#…

自然语言处理从入门到应用——预训练模型总览:迁移学习与微调

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 相关文章&#xff1a; 预训练模型总览&#xff1a;从宏观视角了解预训练模型 预训练模型总览&#xff1a;词嵌入的两大范式 预训练模型总览&#xff1a;两大任务类型 预训练模型总览&#xff1a;预训练模型的拓展 …

Linux文件系统概述

本文已收录至《Linux知识与编程》专栏&#xff01; 作者&#xff1a;ARMCSKGT 演示环境&#xff1a;CentOS 7 文件系统概述 前言正文文件与磁盘磁盘介绍与机械硬盘机械硬盘基础结构机械硬盘数据存储与管理 文件操作的细节创建文件访问文件删除文件恢复文件其他情况 最后 前言 …

【案例】--日志管理分析平台案例

目录 一、前言1.1、日志架构思考二、日志平台具体实现2.1、日志采集--采集系统日志2.2、日志采集--采集接口日志三、ES频繁插入优化3.1、大量写入如何提高性能3.2、单线程bulk批量写入3.3、多线程bulk批量写入一、前言 目前接触的项目系统架构如上,属于分布式微服务架构。规模…

综合评价算法 | Matlab实现基于熵权法的综合评价算法

文章目录 效果一览文章概述研究内容源码设计参考资料效果一览 文章概述 综合评价算法 | Matlab实现基于熵权法的综合评价算法 研究内容 信息量:信息量是度量弄清楚一个未知事物需要查询的信息的多少,单位是比特。随机变量取某个值时,其概率倒数的对数就是信息量。通俗的说就…

基于卷积神经网络的狗猫数据集分类实验

目录 一、环境配置1、anaconda安装2、配置TensorFlow、Keras 二、数据集分类1、分类源码2、训练流程 三、模型调整1、图像增强2、网络模型添加dropout层 四、使用VGG19优化提高猫狗图像分类五、总结六、参考资料 一、环境配置 1、anaconda安装 下载链接&#xff1a;anaconda …

Selenium--做任何你想做的事情

大家好&#xff0c;今天为大家介绍Selenium自动化浏览器。就是这样&#xff01;你可以通过这种力量做任何你想做的事情。 “getDevTools() 方法返回新的 Chrome DevTools 对象&#xff0c;允许您使用 send() 方法发送针对 CDP 的内置 Selenium 命令。这些命令是包装方法&#x…

【C语言】实用调试技巧(vs2019)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 什么是bug&#xff1f; 2. 调试是什么&#xff1f; 2.1 调试定义 2.2 调试的基本步骤 2.3 Debug 和 Release 的介绍 3. Windows 环境调试介绍 3.1 调试环境的准备 3.2 学会快捷键 3.3 调试的时候查看程序当前信息…

找不到msvcp140.dll无法继续执行代码,请重新安装软件MSVCP140.dll,怎么解决?

计算机在运行软件程序或者游戏的时候&#xff0c;提示“找不到msvcp140.dll无法继续执行代码,请重新安装软件”&#xff0c;无法正常启动运行。这个是因为电脑系统中的msvcp140.dll文件丢失或者损坏了&#xff0c;msvcp140.dll是一种动态链接库文件&#xff0c;它是由Microsoft…

显卡资源使用

1.首先&#xff0c;使用校园网访问http://202.206.212.218:8000/&#xff0c;&#xff08;测试用的ip&#xff09;&#xff0c;部署后为http://59.67.235.242:8000/&#xff0c;出现如下登录界面。 2.目前有9个用户&#xff0c;用户名和密码设置如下&#xff1a; UsernamePass…

最强优化指令大全 | 【Linux技术专题】「系统性能调优实战」终极关注应用系统性能调优及原理剖析(下册)

Linux命令相关查看指标 CPU 指标 vmstat指令 vmstat -n m该命令用于每隔n秒采集系统的性能统计信息&#xff0c;共采集m次。 [rootsvr01]$ vmstat 1 3procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----r b swpd free buff cache …

基于JSP+Servlet的文件上传与下载

基于JSPServlet的文件上传与下载 一、系统介绍二、功能展示1.项目骨架2.单文件上传3.多文件上传4.下载文件1.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java web项目 项目名称&#xff1a;基于JSPServlet的文件上传与下载案例 项目架构&#xff1a;B/S架构…