Spring Security对接多用户表

起因

项目需要对两个用户表进行配置,于是上网看到了这样的代码

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Primary
    UserDetailsService us1() {
        return new InMemoryUserDetailsManager(User.builder().username("javaboy").password("{noop}123").roles("admin").build());
    }
    @Bean
    UserDetailsService us2() {
        return new InMemoryUserDetailsManager(User.builder().username("sang").password("{noop}123").roles("user").build());
    }
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider();
        dao1.setUserDetailsService(us1());

        DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider();
        dao2.setUserDetailsService(us2());

        ProviderManager manager = new ProviderManager(dao1, dao2);
        return manager;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/hello").hasRole("user")
                .antMatchers("/admin").hasRole("admin")
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

可以见到,该作者声明了两个provider对象并分别设置了他们的userService最后把他们加入ProviderManager。但是当我使用同样的代码时,问题出现了

使用过程

// 说明:新版的好像不支持原作者那样单个单个provider初始化了,参数必须是一个colletion
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() {
        MyProvider studentDao = new MyProvider();
        studentDao.setHideUserNotFoundExceptions(false);
        studentDao.setUserDetailsService(studentService);
        studentDao.setPasswordEncoder(new MyPasswordEncoder());

        MyProvider teacherDao = new MyProvider();
        teacherDao.setHideUserNotFoundExceptions(false);
        teacherDao.setUserDetailsService(teacherService);
        teacherDao.setPasswordEncoder(new MyPasswordEncoder());

        List<AuthenticationProvider> daos = new LinkedList<>();
        daos.add(studentDao);
        daos.add(teacherDao);

        ProviderManager manager = new ProviderManager(daos);
        return manager;
    }

但是此时,当我进行密码验证的时候,发现SpringSecurity永远只能和studentDao中的数据进行比对,而无法访问到teacherDao,此时深入源码,查看ProviderManager类:

ProviderManager.java
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();

		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
			catch (InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				throw e;
			}
			catch (AuthenticationException e) {
				lastException = e;
			}
		}
		...以下从略
	}

可以看到这行代码for (AuthenticationProvider provider : getProviders()),Spring Security的确是遍历了ProviderManager,但是问题出在result = provider.authenticate(authentication);中,继续深入观察provider的authenticate代码, 最终会到它的顶层抽象类:

AbstractUserDetailsAuthenticationProvider.java
// 不需要看太认真
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

可以看到,这个方法主要进行一些密码比对的工作,关键的是最后一句,return createSuccessAuthentication(principalToReturn, authentication, user);,再看createSuccessAuthentication()方法:

protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		// Ensure we return the original credentials the user supplied,
		// so subsequent attempts are successful even with encoded passwords.
		// Also ensure we return the original getDetails(), so that future
		// authentication events after cache expiry contain the details
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());

		return result;
	}

可以看到,这个方法是无论如何都会返回一个对象而不是null的,此时就会回到ProviderManager类的authenticate方法,这个方法里的try代码块中

result = provider.authenticate(authentication);

if (result != null) {
	copyDetails(authentication, result);
	break;
}

当result不为null时就从循环break了,所以永远不可能找到第二个表,我不知道这是SpringSecurity本身的问题还是什么,最终的解决方案是创建一个UserDtailService类轮流查询两个表:

@Service
public class UserService implements UserDetailsService {
    @Autowired
    StudentClientFeign studentClientFeign;
    @Autowired
    TeacherClientFeign teacherClientFeign;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Student student = studentClientFeign.findStudentByName(username);
        Teacher teacher = teacherClientFeign.findTeacherByName(username);
        if(student != null) {
            return student;
        }
        else if(teacher != null) {
            return teacher;
        }
        else {
            return new User();
        }
    }
}

这样就只用配置一个UserService,不存在多service问题了。
但这样就得保证学生和老师的名字不同了,或者后期可能换一种登陆方式,比如用学号/工号登陆之类的。
有时候还是不要太依赖框架,用简单的框架代码自己实现代码逻辑比较复杂的部分比较好。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
SpringBoot Security是一个基于Spring框架的安全性解决方案,用于实现权限控制功能。它可以与SpringBoot无缝集成,提供了一套简单而强大的安全保护机制。[1]该权限框架支持多渠道对接,可以自动路由支付网关。同时,它还可以实现用户、角色和权限三个主要模块的管理,方便进行权限的控制和管理。在项目中集成了SpringBoot Security,可以使用其提供的功能来实现鉴权功能,同时还可以与其他技术栈如Redis、MyBatis等进行集成,以实现更加丰富的功能需求。总的来说,SpringBoot Security是一个强大且灵活的权限框架,可以满足项目的安全性需求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [通用权限管理系统+springboot+mybatis plus+spring security+jwt+redis+mysql](https://download.csdn.net/download/qq_37049128/87842802)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [全开源JAVA支付系统/jeepay聚合支付四方支付系统](https://download.csdn.net/download/weixin_36643308/88277311)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [springboot+security+mybatis+redis+jwt,鉴权框架搭建](https://download.csdn.net/download/weixin_42065235/85736885)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值