OAuth2授权代码流,无需共享客户端密钥

时间:2017-10-02 08:41:18

标签: angular spring-security oauth-2.0 spring-security-oauth2 spring-cloud-security

我使用Spring Security Cloud和Angular 2客户端在OAuth2的授权代码流程上做了一个小演示。

一切正常,我从服务器获得访问令牌响应。

然而,根据Aaron perecki的博客https://aaronparecki.com/oauth-2-simplified/

从网页加载源代码后,单页应用(或基于浏览器的应用)完全在浏览器中运行。由于整个源代码可供浏览器使用,因此无法保持其客户机密的机密性,因此在这种情况下不使用该机密。该流程与上面的授权代码流程完全相同,但在最后一步,授权代码将交换访问令牌而不使用客户端密钥。

因此,我不想使用客户端密钥从auth-server获取访问权限。

但是,如果没有与auth服务器共享客户端密钥,我将无法继续。

以下是我的Angular 2逻辑来检索令牌

import {Injectable} from '@angular/core';
import {IUser} from './user';
import {Router} from '@angular/router';
import {Http, RequestOptions, Headers, URLSearchParams} from '@angular/http';

@Injectable()
export class AuthService {
  currentUser: IUser;
  redirectUrl: string;
  state: string;
  tokenObj: any;

  constructor(private router: Router, private http: Http) {
    this.state = '43a5';
  }

  isLoggedIn(): boolean {
    return !!this.currentUser;
  }

  loginAttempt(username: string, password: string): void {
    const credentials: IUser = {
      username: username,
      password: password
    };

    const params = new URLSearchParams();
    params.append('client_id', 'webapp');
    params.append('redirect_uri', 'http://localhost:9090/callback');
    params.append('scope', 'read');
    params.append('grant_type', 'authorization_code');
    params.append('state', this.state);
    params.append('response_type', 'code');

    const headers = new Headers({
      'Authorization': 'Basic ' + btoa(username + ':' + password)
    });

    const options = new RequestOptions({headers: headers});

    this.http.post('http://localhost:9090/oauth/authorize', params, options)
      .subscribe(
        data => {
          const authresponse = data.json();
          this.tokenObj = this.getTokens(authresponse.code).json();
        },
        err => console.log(err)
      );
  }

  getTokens(code: string): any {
    const params = new URLSearchParams();
    params.append('grant_type', 'authorization_code');
    params.append('code', code);
    params.append('redirect_uri', 'http://localhost:9090/callback');

    const headers = new Headers({
      'Authorization': 'Basic ' + btoa('webapp:websecret')
    });

    const options = new RequestOptions({headers: headers});

    this.http.post('http://localhost:9090/oauth/token', params, options)
      .subscribe(
        data => {
          return data.json();
        },
        err => console.log(err)
      );
  }

  logout(): void {
    this.currentUser = null;
  }
}

这是我的 AuthorizationServerConfig 类源代码

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.accessTokenConverter(accessTokenConverter()).authenticationManager(authManager);
    }

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

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

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

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/oauth2?createDatabaseIfNotExist=true");
        dataSource.setUsername("root");
        dataSource.setPassword("chandra");
        return dataSource;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
}

WebConfig类的源代码

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user1").password("password1").roles("USER")
            .and().withUser("admin1").password("password1").roles("ADMIN");
        auth.eraseCredentials(false);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest().authenticated()
            .and().csrf().disable()
            .httpBasic();
    }
}

SpringBootApplication类

@SpringBootApplication
@EnableAuthorizationServer
@RestController
public class SpringMicroservicesOauthServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringMicroservicesOauthServerApplication.class, args);
    }

    @RequestMapping("callback")
    public AuthCodeResponse test(@RequestParam("code") String code, @RequestParam("state") String state) {
        return new AuthCodeResponse(code,state);
    }
}

AuthCodeResponse POJO

public class AuthCodeResponse {

    private String code;

    private String state;

    public AuthCodeResponse() {
    }

    public AuthCodeResponse(String code, String state) {
        this.code = code;
        this.state = state;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

2 个答案:

答案 0 :(得分:1)

我没有在那里看到一个实际的问题,但如果您只想隐藏客户端密码,为什么不创建自己的API,那就是您处理所有OAuth2的东西,从而保持客户秘密,很隐秘。

那是你从Angular前端调用的那个。

您完全不需要在JavaScript中使用它来完全暴露令牌。

是的,这是一个额外的步骤,但如果你想保持安全,那么完全值得。

答案 1 :(得分:0)

如果 4.1. Authorization Code Grant RFC 6749中client type中定义的授权代码流不需要client_secret >您的申请公开

但是,即使您的应用程序的客户端类型是公共的,您的授权服务器也需要一对API密钥和API密钥。为什么?这是因为WebSecurityConfig正在保护/oauth/**。即使/oauth/**不是OAuth端点,也会执行保护。

(a)客户ID和客户机密的保护和(b)通用方式的保护(在这种情况下,WebSecurityConfig的保护)是不同的事情。