为啥 OAuth2 身份验证服务器使用 jdbc 数据源返回 401 错误?

Posted

技术标签:

【中文标题】为啥 OAuth2 身份验证服务器使用 jdbc 数据源返回 401 错误?【英文标题】:Why does OAuth2 authentication server returns 401 error with jdbc datasource?为什么 OAuth2 身份验证服务器使用 jdbc 数据源返回 401 错误? 【发布时间】:2019-05-03 23:16:57 【问题描述】:

我正在使用 JWT 实现 OAUTH2 身份验证服务器。 如果我使用 inMemory () 令牌,我可以正常访问。 但是,如果我使用 jdbc (dataSource),它总是返回错误 401。有人可以帮忙吗?

我的 AuthorizationServerConfigurerAdapter

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
                .tokenEnhancer(jwtAccessTokenConverter()).userDetailsService(userDetailsService)
                .requestFactory(customOauth2RequestFactory.requestFactory());

    

我的令牌商店

@Bean
    public TokenStore tokenStore() 
        return new JwtTokenStore(jwtAccessTokenConverter());
    

还有我的 jwtAccessTokenConverter

@Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() 
        var converteToken = new CustomToken();
        converteToken.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "password".toCharArray())
                .getKeyPair("jwt"));
        return converteToken;
    

我的 CustomToken 扩展了 JwtAccessTokenConverter

    @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) 
            var user = userRepository.findByLogin(authentication.getName());
            Map<String, Object> additionalInformation = new HashMap<>() 

            put("idFuncionario", usuario.getIdFuncionario());
            put("idEmpresa", usuario.getIdEmpresa());
            put("perfis", usuario.descricaoPerfil());
            put("login", usuario.getLogin());
        ;
        var defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);
        defaultOAuth2AccessToken.setAdditionalInformation(additionalInformation);
        return super.enhance(defaultOAuth2AccessToken, authentication);
    

现在,我在 AuthorizationServerConfigurerAdapter 中的 JDBC

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

我的 CustomFactory 扩展了 DefaultOAuth2RequestFactory

@Override
    public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) 
        if (requestParameters.get("grant_type").equals("refresh_token")) 
            var authentication = tokenStore.readAuthenticationForRefreshToken(tokenStore.readRefreshToken(requestParameters
                    .get("refresh_token")));
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
                    authentication.getName(), null, userDetailsService.loadUserByUsername(authentication.getName())
                    .getAuthorities()));
        
        return super.createTokenRequest(requestParameters, authenticatedClient);
    

还有我的 JWT 实体

@Entity
@Table(name = "oauth_client_details")
public class OAuthClientDetails extends AbstractEntity 

    private String clientId;
    private String clientSecret;
    private String resourceIds;
    private String scope;
    private String authorizedGrantTypes;
    private String webServerRedirectUri;
    private String authorities;
    private Integer accessTokenValidity;
    private Integer refreshTokenValidity;
    private String additionalInformation;
    private String autoapprove;

    contructor / geters / seters

【问题讨论】:

您使用什么密码编码器?您是否在数据库中加密了密码?使用用户名和密码显示您的数据库行。 【参考方案1】:

我的解决方案:

我在一个扩展 AuthorizationServerConfigurerAdapter 的类中创建了这个方法

 @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
        clients.withClientDetails(clientService);
    

还有这项服务:

@Service
public class ClientService implements ClientDetailsService 

    @Autowired
    private ClienteRepository clienteRepository;

    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException 

        var cliente = clienteRepository.findById(clientId);


        var clienteDetail = new ClienteDetalhes(
                cliente.get().getClientId(),
                String.join(",", cliente.get().getResourceIds()),
                cliente.get().getClientSecret(),
                String.join(",", cliente.get().getScope()),
                String.join(",", cliente.get().getAuthorizedGrantTypes()),
                String.join(",", cliente.get().getRegisteredRedirectUri()),
                null,
                cliente.get().getAccessTokenValiditySeconds(),
                cliente.get().getRefreshTokenValiditySeconds(),
                cliente.get().getAutoApproveScope(),
                null);
        return clienteDetail;

    

还有这个实体

@Getter
@Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(schema = "cliente")
public class ClienteDetalhes implements Serializable, ClientDetails 

    private static final ObjectMapper mapper = new ObjectMapper();

    @Id
    @Column(nullable = false, unique = true)
    private String clientId;
    private String resourceIds;
    private String clientSecret;
    private String scope;
    @Column(nullable = false)
    private String authorizedGrantTypes;
    private String registeredRedirectUri;
    private String authorities;
    @Column(nullable = false)
    private Integer accessTokenValiditySeconds;
    @Column(nullable = false)
    private Integer refreshTokenValiditySeconds;
    private String autoApproveScope;
    private String additionalInformation;

    @Override
    public boolean isSecretRequired() 
        return !StringUtils.isEmpty(this.clientSecret);
    

    @Override
    public boolean isAutoApprove(String scope) 
        return false;
    

    @Override
    public Map<String, Object> getAdditionalInformation() 
        try 
            return mapper.readValue(this.additionalInformation, Map.class);
         catch (IOException e) 
            return new HashMap<>();
        
    

    @Override
    public Collection<GrantedAuthority> getAuthorities() 
        Set<String> set = StringUtils.commaDelimitedListToSet(this.authorities);
        Set<GrantedAuthority> result = new HashSet<>();
        set.forEach(authority -> result.add(new GrantedAuthority() 
            @Override
            public String getAuthority() 
                return authority;
            
        ));
        return result;
    

    @Override
    public Set<String> getRegisteredRedirectUri() 
        return StringUtils.commaDelimitedListToSet(this.registeredRedirectUri);
    

    @Override
    public Set<String> getAuthorizedGrantTypes() 
        return StringUtils.commaDelimitedListToSet(this.authorizedGrantTypes);
    

    @Override
    public boolean isScoped() 
        return this.getScope().size() > 0;
    

    @Override
    public Set<String> getScope() 
        return StringUtils.commaDelimitedListToSet(this.scope);
    

    @Override
    public Set<String> getResourceIds() 
        if (StringUtils.isEmpty(this.resourceIds)) 
            return new HashSet<>();
         else 
            return StringUtils.commaDelimitedListToSet(this.resourceIds);
        
    


现在一切正常!

【讨论】:

以上是关于为啥 OAuth2 身份验证服务器使用 jdbc 数据源返回 401 错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Spring Security OAuth2 DefaultTokenServices 在加载身份验证时检查 clientId?

oauth2 身份验证成功后获取 404 和匿名令牌

当我在授权标头中发送有效的不记名令牌时,为啥我的 Spring-Cloud Gateway / OAuth2-Client 没有通过身份验证?

具有所有 4 种授权类型的 Oauth2 服务器示例

使用 OAuth2 进行地理服务器身份验证

如何使用 oauth2 通过 REST Web 服务进行身份验证