1

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

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

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

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

所以我不想使用客户端密码从 auth-server 获取访问令牌。

但是,如果不将客户端密码共享给身份验证服务器,我将无法继续。

以下是我检索令牌的 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;
    }
}
4

2 回答 2

1

我还没有看到实际的问题,但是如果您只想隐藏客户端密码,为什么不创建自己的 API,这就是您处理所有 OAuth2 内容的地方,从而保持您的客户端密码,很好的秘密。

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

通过根本不在 JavaScript 中使用它,您避免了完全公开令牌的需要。

是的,这是一个额外的步骤,但如果你想保证任何东西的安全,这是完全值得的。

于 2017-10-02T13:48:40.717 回答
0

如果您的应用程序的客户端类型公共的,则在RFC 6749的4.1. 授权代码授予中定义的授权代码流程不需要。client_secret

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

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

于 2017-10-03T02:52:22.617 回答