Spring OAuth2 - 授权服务器 - 在客户端区分用户

Spring OAuth2 - Authorization Server - Differentiate users on clients

我的申请卡住了。这听起来很简单:我在我的 OAuth AuthorizationServer 上注册了两个客户端和两个用户。用户 alpha 可以访问这两个应用程序("androidapp" 和 "angularapp"),但是用户 beta 只能访问其中一个应用程序(仅 "angularapp")。我如何区分 "androidapp" 应用程序的用户并阻止测试版?

这是我的 AuthServer 代码:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{

    @Autowired private DataSource dataSource;
    @Autowired private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()")
            ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("angularapp")
        .secret(passwordEncoder.encode("12345"))
        .scopes("read", "write")
        .authorizedGrantTypes("password", "refresh_token")
        .accessTokenValiditySeconds(20000)
        .refreshTokenValiditySeconds(20000)
        .and()
        .withClient("androidapp")
        .secret(passwordEncoder.encode("67890"))
        .scopes("read", "write")
        .authorizedGrantTypes("password", "refresh_token")
        .accessTokenValiditySeconds(20000)
        .refreshTokenValiditySeconds(20000);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
            .authenticationManager(authenticationManager)
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter())
            ;

    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
        jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
        jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
        return jwt;
    }

}

提前感谢您的回答。

我的解决方案:

When the loadClientByClientId method is executed, the Principal object stored in SecurityContext doesn't yet exist, but it does when the loadUserByUsername method is executed with a slight observation: The Principal object at this point contains the client_id, not the username, resulting in customizing the UserDetailsService object instead of ClientsDetailsService. Then, with a relational entity (JPA) I joined the client_id with the username giving the expected result.

所以,UserDetailsService 实现的代码是:

@Service
public class UsuarioService implements IUsuarioService, UserDetailsService{

    private Logger logger = LoggerFactory.getLogger(UsuarioService.class);

    @Autowired
    private IUsuarioDao usuarioDao;

    @Override
    @Transactional(readOnly=true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Usuario usuario = usuarioDao.findByUsername(username);
        if( usuario == null ) {
            logger.error("Login error: Username not found in storage");
            throw new UsernameNotFoundException("Login error: Username not found in storage");
        }
        List<GrantedAuthority> authorities = usuario.getRoles().stream().map( role -> new SimpleGrantedAuthority( role.getNombre() )).collect(Collectors.toList());

        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String applicationID = "";
        if (principal instanceof UserDetails) {
            applicationID = ((UserDetails)principal).getUsername();
        } else {
            applicationID = principal.toString();
        }
        logger.info("Application: {} ", applicationID);

        if( applicationID == null || applicationID.isEmpty() ) {
            logger.error("Application ID can't be empty");
            throw new InsufficientAuthenticationException("Application ID can't be empty");
        }

        OAuthClientDetails app = findApplicationByUsername( usuario.getClientes(), applicationID);
        if( app == null ) {
            logger.error("Unauthorized user for application {}", applicationID);
            throw new UnapprovedClientAuthenticationException("Unauthorized user for application " + applicationID);
        }

        return new User(username, usuario.getPassword(), usuario.getEnabled(), true, true, true, authorities);
    }

    private OAuthClientDetails findApplicationByUsername( final List<OAuthClientDetails> list, final String clientID ){
        return list.stream().filter( p -> p.getClientId().equals(clientID) ).findAny().orElse(null); } }

AuthorizationServer 配置为:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{

    @Autowired DataSource dataSource;
    @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
            .tokenKeyAccess("permitAll()") 
            .checkTokenAccess("isAuthenticated()") 
            ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
            .authenticationManager(authenticationManager)
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter())
            ;

    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
        jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
        jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
        return jwt;
    }

}

非常感谢您的帮助和想法。

我以前解决的方法是创建ResourceOwnerPasswordTokenGranter的子类并重写这个方法:

protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

如果您从 Spring 来源复制原始方法,您可以在某个时候访问 client_id (client.getClientId()) 和用户 (userAuth.getPrincipal() ).

如果用户的角色与客户端不匹配,我抛出一个InsufficientAuthenticationException以避免用户可以登录。

如果 Spring 安全部门能有某种回调以避免必须复制部分代码才能执行此操作,那就太好了。我为此打开了https://github.com/spring-projects/spring-security-oauth/issues/791