Spring OAuth2 - 无法获取客户端令牌

Posted

技术标签:

【中文标题】Spring OAuth2 - 无法获取客户端令牌【英文标题】:Spring OAuth2 - Can't get client token 【发布时间】:2015-06-02 12:16:17 【问题描述】:

我不能 100% 确定我正在执行正确的请求,但我似乎无法为客户获取令牌。我的解决方案基于本教程5 minutes with spring oauth 2.0。

我正在执行邮递员的这个请求:

/oauth/token?grant_type=client_credentials&client_id=mysupplycompany&client_secret=mycompanyk

该请求以异常响应:

org.springframework.security.authentication.InsufficientAuthenticationException: 没有客户端身份验证。尝试添加适当的 身份验证过滤器。 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(TokenEndpoint.java:91)

以下是一些您可能需要帮助我找出问题的文件/类。

WebInitializer

package com.squirrels.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;

public class WebInitializer implements WebApplicationInitializer 

    public void onStartup(ServletContext servletContext) throws ServletException           
        WebApplicationContext context = getContext();
        servletContext.addListener(new ContextLoaderListener(context));
        servletContext.addListener(new RequestContextListener());    

        Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));  
        servlet.addMapping("/");  
        servlet.setLoadOnStartup(1);  

        DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");
        filter.setContextAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher");
        servletContext.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null, false, "/*");
      

    private AnnotationConfigWebApplicationContext getContext() 
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation("com.squirrels.config");
        return context;
    


PersistenceJPAConfig

package com.squirrels.config;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import liquibase.integration.spring.SpringLiquibase;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@ComponentScan( "com.squirrels.controller", "com.squirrels.services", "com.squirrels.persistence.dao", "com.squirrels.auth" )
@PropertySource(value =  "classpath:squirrel.properties" )
@ImportResource("classpath:spring-security.xml")
public class PersistenceJPAConfig 

    @Autowired
    private Environment environment;

    @Bean
    public SpringLiquibase liquibase() 
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource());
        liquibase.setDefaultSchema(environment.getRequiredProperty("db_schema"));
        liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.xml");

        return liquibase;
    

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() 
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPersistenceUnitName("SquirrelAuth");
        em.setPackagesToScan(new String[]  "com.squirrels.persistence.model" );

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());

        return em;
    

    @Bean
    public DataSource dataSource() 
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(environment.getRequiredProperty("db_driverClass"));
        dataSource.setUrl(environment.getRequiredProperty("db_jdbcUrl"));
        dataSource.setUsername(environment.getRequiredProperty("db_user"));
        dataSource.setPassword(environment.getRequiredProperty("db_password"));
        return dataSource;
    

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) 
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() 
        return new PersistenceExceptionTranslationPostProcessor();
    

    Properties additionalProperties() 
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", environment.getRequiredProperty("db_hibernateDialect"));
        return properties;
    



security-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
    xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd 
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">


    <sec:http pattern="/oauth/token" create-session="stateless"
        authentication-manager-ref="authenticationManager">
        <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
        <sec:anonymous enabled="false" />
        <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
        <sec:custom-filter ref="clientCredentialsTokenEndpointFilter"
            before="BASIC_AUTH_FILTER" />
        <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
    </sec:http>

    <sec:http pattern="/api/**" create-session="never"
        entry-point-ref="oauthAuthenticationEntryPoint">
        <sec:anonymous enabled="false" />
        <sec:intercept-url pattern="/api/**" method="GET"
            access="IS_AUTHENTICATED_FULLY" />
        <sec:custom-filter ref="resourceServerFilter"
            before="PRE_AUTH_FILTER" />
        <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
    </sec:http>

    <bean id="oauthAuthenticationEntryPoint"
        class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    </bean>

    <bean id="clientAuthenticationEntryPoint"
        class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
        <property name="realmName" value="springsec/client" />
        <property name="typeName" value="Basic" />
    </bean>

    <bean id="oauthAccessDeniedHandler"
        class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
    </bean>

    <bean id="clientCredentialsTokenEndpointFilter"
        class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
        <property name="authenticationManager" ref="authenticationManager" />
    </bean>

    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider
            user-service-ref="clientDetailsUserService" />
    </sec:authentication-manager>

    <bean id="clientDetailsUserService"
        class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
        <constructor-arg ref="clientDetails" />
    </bean>

    <bean id="clientDetails" class="com.squirrels.auth.OAuthClientDetailsImpl">
        <property name="id" value="mysupplycompany" />
        <property name="secretKey" value="mycompanykey" />
    </bean>

    <sec:authentication-manager id="userAuthenticationManager">
        <sec:authentication-provider ref="customUserAuthenticationProvider" />
    </sec:authentication-manager>

    <bean id="customUserAuthenticationProvider" class="com.squirrels.auth.OAuthAuthenticationProvider">
    </bean>

    <oauth:authorization-server
        client-details-service-ref="clientDetails" token-services-ref="tokenServices">
        <oauth:authorization-code />
        <oauth:implicit />
        <oauth:refresh-token />
        <oauth:client-credentials />
        <oauth:password authentication-manager-ref="userAuthenticationManager" />
    </oauth:authorization-server>

    <oauth:resource-server id="resourceServerFilter"
        resource-id="springsec" token-services-ref="tokenServices" />

    <bean id="tokenStore"
        class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />

    <bean id="tokenServices"
        class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
        <property name="tokenStore" ref="tokenStore" />
        <property name="supportRefreshToken" value="true" />
        <property name="accessTokenValiditySeconds" value="120"></property>
        <property name="clientDetailsService" ref="clientDetails" />
    </bean>

    <mvc:annotation-driven />
</beans>

OAuthAuthenticationProvider

package com.squirrels.auth;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;

public class OAuthAuthenticationProvider implements AuthenticationProvider 

    @Autowired
    private OAuthProxy proxy;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
        boolean result = proxy.isValidUser("1", authentication.getPrincipal().toString(), authentication.getCredentials().toString());

        if (result) 
            List<GrantedAuthority> grantedAuthorities =

            new ArrayList<GrantedAuthority>();
            OAuthAuthenticationToken auth = new OAuthAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), grantedAuthorities);

            return auth;
         else 
            throw new BadCredentialsException("Bad User Credentials.");
        
    

    @Override
    public boolean supports(Class<?> arg0) 
        return true;
    


WebMvcConfig

package com.squirrels.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter 


    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) 
        configurer.enable();
    ;

    /*
     * Configure ContentNegotiatingViewResolver
     */
    @Bean
    public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) 
        ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
        resolver.setContentNegotiationManager(manager);

        // Define all possible view resolvers
        List<ViewResolver> resolvers = new ArrayList<ViewResolver>();
        resolvers.add(jsonViewResolver());         
        resolver.setViewResolvers(resolvers);
        return resolver;
    

    /*
     * Configure View resolver to provide JSON output using JACKSON library to
     * convert object in JSON format.
     */
    @Bean
    public ViewResolver jsonViewResolver() 
        return new JsonViewResolver();
    


OAuthenticationToken

package com.squirrels.auth;

import java.util.Collection;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

public class OAuthAuthenticationToken extends AbstractAuthenticationToken 

    private static final long serialVersionUID = -1092219614309982278L;
    private final Object principal;
    private Object credentials;

    public OAuthAuthenticationToken(Object principal, Object credentials,
            Collection<? extends GrantedAuthority> authorities) 
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    

    @Override
    public Object getCredentials() 
        return credentials;
    

    @Override
    public Object getPrincipal() 
        return principal;
    


OAuthClientDetailsImpl

package com.squirrels.auth;

import java.util.ArrayList;
import java.util.List;

import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;

public class OAuthClientDetailsImpl implements ClientDetailsService 

    private String id;
    private String secretKey;

    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException 
        if (clientId.equals(id))
        
            List<String> authorizedGrantTypes = new ArrayList<String>();
            authorizedGrantTypes.add("password");
            authorizedGrantTypes.add("refresh_token");
            authorizedGrantTypes.add("client_credentials");

            BaseClientDetails clientDetails = new BaseClientDetails();
            clientDetails.setClientId(id);
            clientDetails.setClientSecret(secretKey);
            clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);

            return clientDetails;
        
        else 
            throw new NoSuchClientException("No client recognized with id: " + clientId);
        
    

    public String getId() 
        return id;
    

    public void setId(String id) 
        this.id = id;
    

    public String getSecretKey() 
        return secretKey;
    

    public void setSecretKey(String secretKey) 
        this.secretKey = secretKey;
    


OAuthProxy

package com.squirrels.auth;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.squirrels.dto.UserDTO;
import com.squirrels.persistence.dao.UserDao;
import com.squirrels.persistence.model.UserModel;
import com.squirrels.services.UserUtil;


@Service
public class OAuthProxy 

    protected static final Logger logger = LogManager.getLogger(OAuthProxy.class);

    @Autowired
    private UserDao userDaoImpl;

    @Autowired
    private UserUtil userUtil;  

    @Transactional
    public boolean isValidUser(String organizationId, String username, String password) 

        try
            UserModel user = userDaoImpl.findByOrganizationAndUserName(organizationId, username);
            UserDTO userDto = userUtil.getDTO(user);

            //TODO validate password (or other means of auth)

            return true;            
        catch(Exception e)
            logger.error("No user: " + username + " found in organization: " + organizationId, e);
        

        return false;
    


【问题讨论】:

不确定这是否真的是您的问题,但您发送到令牌端点的参数不应该是 URL 的一部分。它们应该是 POST 请求的一部分。 @LukeTaylor 我有同样的问题,但我正在发布请求。尚无解决方案,正在搜索... 【参考方案1】:

我遇到了同样的问题。 在我的情况下是因为我像这样映射了我的 DispatcherServlet:

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/dispatcher/*</url-pattern>
</servlet-mapping>

这导致了问题,因为在 "/oauth/token" 而不是 "/dispatcher/oauth/token" 上配置了安全性:

<security:http pattern="/oauth/token" ...

所以我只需要这样做:

<security:http pattern="/dispatcher/oauth/token" 

问题就解决了。

如果您不是这种情况,可能是因为您没有配置DelegatingFilterProxy

尝试在 web.xml 中配置它:

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

如果你使用java config而不是web.xml,扩展类org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer 将对其进行配置。

【讨论】:

现在去试试这个其实很有意义。我将调度程序映射到 /api/*。我还将编辑我的问题并包含我的 JavaConfig 文件类。我所有的 spring 东西都是通过 java 配置的,除了 spring 安全的东西。 谢谢!这个答案和将我的调度程序从 /api/* 切换到 /* 一样有效。我终于运行了一个完全可操作的 oauth2 提供程序 很高兴为您提供帮助 :) 如果您想保留“/api/*”,也许this thread 可以帮助您。它说如果你覆盖默认的 oauth 端点 url,你可能需要配置另一个 DelegatingFilterProxy。

以上是关于Spring OAuth2 - 无法获取客户端令牌的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot OAuth2 - 无法从令牌获取用户详细信息

Spring Boot OAuth2 - GoogleApi - 无法从令牌获取用户详细信息

尝试使用spring oauth2中的刷新令牌获取新的访问令牌时出现无效的客户端错误

Spring安全中的Oauth2客户端

如何在 Spring Boot 2 oauth2 中获取令牌?

Spring Security Oauth2 客户端获取访问令牌失败,请求代码无效=415,消息=不支持的媒体类型