使用OAuth2使用authorization_code grant和JWT

时间:2017-04-10 13:03:01

标签: spring-boot spring-security jwt spring-security-oauth2

我正试图在服务器/客户端上使用JWT& OAuth2用户。

除了用户名(我打算刚刚嵌入jwt)之外,我没有资源可以从客户端外部请求。

我的服务器正在工作并且正好处理jwt令牌(使用邮递员的oauth2令牌身份验证进行测试)。我不知道如何处理客户。

我需要让我的客户端成为ResourceServer吗?或者我应该使用@ EnableOAuth2Sso教程吗?

对于我的客户,我有以下规格:

  • 我想使用“authorization_code”授权类型
  • 我想隐藏oauth2登录后面的客户端的所有页面
    • 用户请求一个页面,如果他们没有登录(没有带有效jwt令牌的auth头),他们应该被重定向到authserver / oauth / authorize路径
    • 显示登录表单,用户使用用户名密码
    • 登录
    • 如果登录正确,则会将其重定向回所请求的客户页面
  • 如果用户在JWT过期之前回来,他们不应该再次登录。
  • 我想尽可能少地使用Spring Security。

我认为这是可能的,而且正是Single Sign On应该是什么。

我的第一个想法是使用@ EnableOAuth2Sso,但这不能正常工作:

  1. 我被重定向到服务器上的登录页面
  2. 我正确登录
  3. 我被重定向到:http://localhost:9000/login?code=fmzhJ5&state=bn9077
  4. 我收到此错误:出现意外错误(type = Unauthorized,status = 401)。 身份验证失败:无法获取访问令牌
  5. 更新

    我发现以下错误消息:检测到可能的CSRF - 状态参数是必需的,但找不到状态

    我猜有一个错误配置保存/使用/转换“state”参数。不过我想用它。

    UPDATE2

    我找到this comment并为客户端添加了不同的上下文路径。这将错误消息更改为:

    org.springframework.security.authentication.BadCredentialsException:无法获取访问令牌 引起:org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException:请求访问令牌时出错。 引起:org.springframework.web.client.HttpClientErrorException:401 null

    代码:

    客户端应用

    @SpringBootApplication
    @EnableOAuth2Sso
    public class TestClientApplication
    {
        public static void main(String[] args) {
            SpringApplication.run(TestClientApplication.class, args);
        }
    }
    

    application.yml:

    security:
      oauth2:
        client:
          clientId: client
          clientSecret: secret
          accessTokenUri: http://localhost:8080/oauth/token
          userAuthorizationUri: http://localhost:8080/oauth/authorize
          authenticationScheme: query
          clientAuthenticationScheme: form
        resource:
          jwt:
            keyValue:
              -----BEGIN PUBLIC KEY-----
              ...
    

    这是我的服务器实现:

    OAuth2Config:

    @Configuration
    @EnableAuthorizationServer
    public class OAuth2Config extends AuthorizationServerConfigurerAdapter
    {
    
        @Value("${resource.id:spring-boot-application}")
        private String resourceId;
    
        @Value("${access_token.validity_period:3600}")
        int accessTokenValiditySeconds = 3600;
    
        //todo
        private static final String JWTSecretKey = "mySecretKey";
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Bean
        public TokenEnhancer tokenEnhancer()
        {
            return new JwtTokenEnhancer();
        }
    
        @Bean
        protected JwtAccessTokenConverter accessTokenConverter()
        {
            //todo use secure cert
            KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), JWTSecretKey.toCharArray());
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    
            converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt"));
            return converter;
        }
    
        @Bean
        public TokenStore tokenStore()
        {
            return new JwtTokenStore(accessTokenConverter());
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
        {
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            tokenEnhancerChain.setTokenEnhancers(
                    Arrays.asList(tokenEnhancer(), accessTokenConverter()));
    
            endpoints
                    .tokenStore(tokenStore())
                    .tokenEnhancer(tokenEnhancerChain)
                    .authenticationManager(this.authenticationManager);      //only needed for password grant, which we should only use in case of native/client side apps
    
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception
        {
            //todo add redirect urls
            clients.inMemory()
                    .withClient("trusted-app")          //todo for postman testing only, remove in production!
                    .secret("secret")
                    .authorizedGrantTypes("client_credentials")
                    .authorities("ROLE_TRUSTED_CLIENT")
                    .scopes("read", "write")
                    .resourceIds(resourceId, "myprintforce")
                    .autoApprove(true)
                    .accessTokenValiditySeconds(accessTokenValiditySeconds)
                    .and()
                    .withClient("client")
                    .secret("secret")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                    .scopes("read", "write")
                    .resourceIds("client", resourceId)
                    .autoApprove(true)
                    .accessTokenValiditySeconds(accessTokenValiditySeconds);
            ;
        }
    }
    

    TokenEnhancer

    public class JwtTokenEnhancer implements TokenEnhancer
    {
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication)
        {
            Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("user", authentication.getName());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        }
    }
    

    WebSecurityConfig

    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter
    {
    
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception
        {
            auth.inMemoryAuthentication().withUser("u").password("p").roles("USER");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception
        {
            http
                    .authorizeRequests()
                        .anyRequest().authenticated()
                        .and()
                    .formLogin()
                        .loginPage("/login")
                        .permitAll()
                        .and();
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception
        {
            return super.authenticationManagerBean();
        }
    
    }
    

    应用

    @SpringBootApplication
    public class OAuthServerApplication extends WebMvcConfigurerAdapter
    {
        public static void main(String[] args)
        {
            SpringApplication.run(OAuthServerApplication.class, args);
        }
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/login").setViewName("login");
        }
    }
    

1 个答案:

答案 0 :(得分:0)

问题是2台服务器使用相同的(localhost)机器。当他们有不同的contextPaths时,问题几乎就解决了。

通过更改配置文件中的某些条目,我得到了它的工作。

我删除了authenticationScheme: queryclientAuthenticationScheme: form并添加了scope=read

现在看起来像这样:

security:
  oauth2:
    client:
      clientId: client
      clientSecret: secret
      accessTokenUri: http://localhost:12000/authentication/oauth/token
      userAuthorizationUri: http://localhost:12000/authentication/oauth/authorize
      scope: read
    resource:
      jwt:
        keyValue:
          -----BEGIN PUBLIC KEY-----