需求场景

实现其他系统的用户能够登录到系统B
之前系统A与系统B并无关联,简单说就是分别有一套用户体系,各自有各自的登录功能。
现在需要让系统A的用户使用账号密码也能登录到系统B。

实现

要点:

  1. 如何区分当前的登录用户是系统A的还是系统B的?因为不同的用户需要到对应的系统做验证。

可以通过用户名的特殊标识、配置(不太现实)、加载本地所有账号、前端传用户类型等等方式来判断当前的登录用户是系统A还是系统B。之后走不同的认证流程。

  1. 系统A用户登录到系统B之后,权限如何管理?

在系统B中创建一个用户来关联系统A的用户,利用系统B中的用户来管理权限。(同步用户)

项目使用的是spring security,通过扩展spring security 认证机制来实现此功能。(多 provider)

代码:
创建一个远程认证的Provider

// 远程认证 Provider
public class RemoteAuthenticationProvider implements AuthenticationProvider {
    private UserDetailsService userDetailsService;
    private RemoteService remoteService;// 远程认证服务
    public RemoteAuthenticationProvider(){
        this.userDetailsService = SpringContextUtil.getBean(UserDetailsService.class);
        this.remoteService= SpringContextUtil.getBean(RemoteService .class);
    }
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

		// 本地管理员,不做远程认证
        if("admin".equals(username)){
        	// 此处涉及spring security的认证机制,当前Provider返回null时,则会查找下一个Provider进行认证
            return null;
        }
        
        try {
            // 1、远程验证帐号密码
            Object resp = remoteService.verifyPwd(username, password);
            if(!resp.isSuccess()){
                throw new RuntimeException(resp.getMsg());
            }
            // 2、同步用户,便于管理权限
            remoteService.syncUserIfNotExist(username);
        } catch (Exception e) {
            log.error("远程验证失败", e);
            throw new InternalAuthenticationServiceException("远程验证失败", e);
        }
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
        return result;
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

配置多 Provider,先走远程认证的Provider,再走本地数据库认证的Provider

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private UserDetailsService userDetailsService;

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    RemoteAuthenticationProvider remoteAuthenticationProvider = new RemoteAuthenticationProvider();

    DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    daoAuthenticationProvider.setUserDetailsService(userDetailsService);
    daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
//    ProviderManager providerManager = new ProviderManager(Collections.singletonList(daoAuthenticationProvider));
//    auth.parentAuthenticationManager(providerManager);

    auth.authenticationProvider(remoteAuthenticationProvider);// 远程认证在前
    auth.authenticationProvider(daoAuthenticationProvider);// 本地认证在后
//    super.configure(auth);
  }
}

关于 RemoteAuthenticationProvider 中 return null;的说明:
spring security的认证机制是当前Provider返回null时,则会查找下一个Provider进行认证。所以也能解释在SecurityConfiguration 中把RemoteAuthenticationProvider 放在 DaoAuthenticationProvider 之前,当用户不需要远程认证时,只需要在RemoteAuthenticationProvider 返回null,则会进入DaoAuthenticationProvider进行本地认证。


end

Logo

一站式 AI 云服务平台

更多推荐