Spring REST API + OAuth2 + Angular (using the Spring Security OAuth legacy stack) – Spring REST API + OAuth2 + Angular(使用Spring Security OAuth遗留堆栈)

最后修改: 2020年 3月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll secure a REST API with OAuth and consume it from a simple Angular client.

在本教程中,我们将用OAuth保证REST API的安全,并通过一个简单的Angular客户端来消费它。

The application we’re going to build out will consist of four separate modules:

我们要建立的应用程序将由四个独立的模块组成。

  • Authorization Server
  • Resource Server
  • UI implicit – a front end app using the Implicit Flow
  • UI password – a front end app using the Password Flow

 

Note: this article is using the Spring OAuth legacy project. For the version of this article using the new Spring Security 5 stack, have a look at our article Spring REST API + OAuth2 + Angular.

注意:本文使用的是Spring OAuth 传统项目。对于使用新的Spring Security 5栈的本文版本,请查看我们的文章Spring REST API + OAuth2 + Angular

 

Alright, let’s jump right in.

好吧,让我们直接跳进去。

2. The Authorization Server

2.授权服务器

First, let’s start setting up an Authorization Server as a simple Spring Boot application.

首先,让我们开始将授权服务器设置为一个简单的Spring Boot应用程序。

2.1. Maven Configuration

2.1.Maven配置

We’ll set up the following set of dependencies:

我们将设置以下一组依赖关系。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>  
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

Note that we’re using spring-jdbc and MySQL because we’re going to use a JDBC backed implementation of the token store.

注意,我们使用spring-jdbc和MySQL是因为我们要使用JDBC支持的令牌存储的实现。

2.2. @EnableAuthorizationServer

2.2.@EnableAuthorizationServer

Now, let’s start configuring the authorization server responsible for managing access tokens:

现在,让我们开始配置负责管理访问令牌的授权服务器。

@Configuration
@EnableAuthorizationServer
public class AuthServerOAuth2Config
  extends AuthorizationServerConfigurerAdapter {
 
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) 
      throws Exception {
        clients.jdbc(dataSource())
          .withClient("sampleClientId")
          .authorizedGrantTypes("implicit")
          .scopes("read")
          .autoApprove(true)
          .and()
          .withClient("clientIdPassword")
          .secret("secret")
          .authorizedGrantTypes(
            "password","authorization_code", "refresh_token")
          .scopes("read");
    }

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

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource());
    }
}

Note that:

请注意,。

  • In order to persist the tokens, we used a JdbcTokenStore
  • We registered a client for the “implicit” grant type
  • We registered another client and authorized the “password“, “authorization_code” and “refresh_token” grant types
  • In order to use the “password” grant type we need to wire in and use the AuthenticationManager bean

2.3. Data Source Configuration

2.3.数据源配置

Next, let’s configure our data source to be used by the JdbcTokenStore:

接下来,让我们配置我们的数据源,使其被JdbcTokenStore使用。

@Value("classpath:schema.sql")
private Resource schemaScript;

@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
    DataSourceInitializer initializer = new DataSourceInitializer();
    initializer.setDataSource(dataSource);
    initializer.setDatabasePopulator(databasePopulator());
    return initializer;
}

private DatabasePopulator databasePopulator() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScript(schemaScript);
    return populator;
}

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

Note that, as we are using JdbcTokenStore we need to initialize database schema, so we used DataSourceInitializer – and the following SQL schema:

请注意,由于我们使用的是JdbcTokenStore,我们需要初始化数据库模式,所以我们使用了DataSourceInitializer-和以下SQL模式。

drop table if exists oauth_client_details;
create table oauth_client_details (
  client_id VARCHAR(255) PRIMARY KEY,
  resource_ids VARCHAR(255),
  client_secret VARCHAR(255),
  scope VARCHAR(255),
  authorized_grant_types VARCHAR(255),
  web_server_redirect_uri VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(255)
);

drop table if exists oauth_client_token;
create table oauth_client_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255)
);

drop table if exists oauth_access_token;
create table oauth_access_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255),
  authentication LONG VARBINARY,
  refresh_token VARCHAR(255)
);

drop table if exists oauth_refresh_token;
create table oauth_refresh_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication LONG VARBINARY
);

drop table if exists oauth_code;
create table oauth_code (
  code VARCHAR(255), authentication LONG VARBINARY
);

drop table if exists oauth_approvals;
create table oauth_approvals (
	userId VARCHAR(255),
	clientId VARCHAR(255),
	scope VARCHAR(255),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);

drop table if exists ClientDetails;
create table ClientDetails (
  appId VARCHAR(255) PRIMARY KEY,
  resourceIds VARCHAR(255),
  appSecret VARCHAR(255),
  scope VARCHAR(255),
  grantTypes VARCHAR(255),
  redirectUrl VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(255)
);

Note that we don’t necessarily need the explicit DatabasePopulator bean – we could simply use a schema.sql – which Spring Boot makes use of by default.

请注意,我们不一定需要明确的DatabasePopulator Bean – 我们可以简单地使用schema.sql – Spring Boot默认使用的

2.4. Security Configuration

2.4.安全配置

Finally, let’s secure the Authorization Server.

最后,让我们确保授权服务器的安全。

When the client application needs to acquire an Access Token, it will do so after a simple form-login driven auth process:

当客户端应用程序需要获得一个访问令牌时,它将在一个简单的表单登录驱动的认证过程后进行。

@Configuration
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) 
      throws Exception {
        auth.inMemoryAuthentication()
          .withUser("john").password("123").roles("USER");
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll();
    }
}

A quick note here is that the form login configuration isn’t necessary for the Password flow – only for the Implicit flow – so you may be able to skip it depending on what OAuth2 flow you’re using.

这里需要注意的是,表单登录配置对于密码流来说是不必要的 – 只有隐式流才需要 – 所以你可以跳过它,这取决于你使用的OAuth2流。

3. The Resource Server

3.资源服务器

Now, let’s discuss the resource server; this is essentially the REST API which we ultimately want to be able to consume.

现在,让我们讨论一下资源服务器;这基本上是我们最终希望能够消费的REST API。

3.1. Maven Configuration

3.1.Maven配置

Our Resource Server configuration is the same as the previous Authorization Server application configuration.

我们的资源服务器配置与之前的授权服务器应用配置相同。

3.2. Token Store Configuration

3.2.令牌库配置

Next, we will configure our TokenStore to access the same database that the authorization server uses to store access tokens:

接下来,我们将配置我们的TokenStore,以访问授权服务器用来存储访问令牌的同一个数据库。

@Autowired
private Environment env;

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

@Bean
public TokenStore tokenStore() {
    return new JdbcTokenStore(dataSource());
}

Note that, for this simple implementation, we’re sharing the SQL backed token store even though the Authorization and Resource servers are separate applications.

请注意,对于这个简单的实现,我们正在共享SQL支持的令牌存储,尽管授权和资源服务器是独立的应用程序。

The reason, of course, is that the Resource Server needs to be able to check the validity of the access tokens issued by the Authorization Server.

当然,原因是资源服务器需要能够检查由授权服务器发出的访问令牌的有效性

3.3. Remote Token Service

3.3.远程令牌服务

Instead of using a TokenStore in our Resource Server, we can use RemoteTokeServices:

在我们的资源服务器中不使用TokenStore,我们可以使用RemoteTokeServices

@Primary
@Bean
public RemoteTokenServices tokenService() {
    RemoteTokenServices tokenService = new RemoteTokenServices();
    tokenService.setCheckTokenEndpointUrl(
      "http://localhost:8080/spring-security-oauth-server/oauth/check_token");
    tokenService.setClientId("fooClientIdPassword");
    tokenService.setClientSecret("secret");
    return tokenService;
}

Note that:

请注意,。

  • This RemoteTokenService will use CheckTokenEndPoint on Authorization Server to validate AccessToken and obtain Authentication object from it.
  • The can be found at AuthorizationServerBaseURL +”/oauth/check_token
  • The Authorization Server can use any TokenStore type [JdbcTokenStore, JwtTokenStore, …] – this won’t affect the RemoteTokenService or Resource server.

3.4. A Sample Controller

3.4.一个控制器样本

Next, let’s implement a simple controller exposing a Foo resource:

接下来,让我们实现一个简单的控制器,暴露一个Foo资源。

@Controller
public class FooController {

    @PreAuthorize("#oauth2.hasScope('read')")
    @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
    @ResponseBody
    public Foo findById(@PathVariable long id) {
        return 
          new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }
}

Note how the client needs the “read” scope to access this Resource.

请注意客户需要“read”范围来访问这个资源。

We also need to enable global method security and configure MethodSecurityExpressionHandler:

我们还需要启用全局方法安全并配置MethodSecurityExpressionHandler

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig 
  extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new OAuth2MethodSecurityExpressionHandler();
    }
}

And here’s our basic Foo Resource:

这里是我们的基本Foo资源。

public class Foo {
    private long id;
    private String name;
}

3.5. Web Configuration

3.5.网络配置

Finally, let’s set up a very basic web configuration for the API:

最后,让我们为API设置一个非常基本的网络配置。

@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.web.controller" })
public class ResourceWebConfig implements WebMvcConfigurer {}

4. Front End – Setup

4.前端–设置

We’re now going to look at a simple front-end Angular implementation for the client.

我们现在要看一下客户端的一个简单的前端Angular实现。

First, we’ll use Angular CLI to generate and manage our front-end modules.

首先,我们将使用Angular CLI>来生成和管理我们的前端模块。

First, we’ll install node and npm – as Angular CLI is an npm tool.

首先,我们要安装node和npm – 因为Angular CLI是一个npm工具。

Then, we need to use the frontend-maven-plugin to build our Angular project using maven:

然后,我们需要使用frontend-maven-plugin来使用maven构建我们的Angular项目。

<build>
    <plugins>
        <plugin>
            <groupId>com.github.eirslett</groupId>
            <artifactId>frontend-maven-plugin</artifactId>
            <version>1.3</version>
            <configuration>
                <nodeVersion>v6.10.2</nodeVersion>
                <npmVersion>3.10.10</npmVersion>
                <workingDirectory>src/main/resources</workingDirectory>
            </configuration>
            <executions>
                <execution>
                    <id>install node and npm</id>
                    <goals>
                        <goal>install-node-and-npm</goal>
                    </goals>
                </execution>
                <execution>
                    <id>npm install</id>
                    <goals>
                        <goal>npm</goal>
                    </goals>
                </execution>
                <execution>
                    <id>npm run build</id>
                    <goals>
                        <goal>npm</goal>
                    </goals>
                    <configuration>
                        <arguments>run build</arguments>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

And finally, generate a new Module using Angular CLI:

最后,使用Angular CLI生成一个新模块:

ng new oauthApp

Note that we’ll have two front-end modules – one for password flow and the other for implicit flow.

请注意,我们将有两个前端模块–一个用于密码流,另一个用于隐式流。

In the following sections, we will discuss the Angular app logic for each module.

在下面的章节中,我们将讨论每个模块的Angular应用程序的逻辑。

5. Password Flow Using Angular

5.使用Angular的密码流程

We’re going to be using the OAuth2 Password flow here – which is why this is just a proof of concept, not a production-ready application. You’ll notice that the client credentials are exposed to the front end – which is something we’ll address in a future article.

我们将在这里使用OAuth2密码流程–这就是为什么这只是一个概念证明,而不是一个可用于生产的应用程序。你会注意到,客户端的凭证是暴露在前端的–这一点我们会在以后的文章中解决。

Our use case is simple: once a user provides their credentials, the front-end client uses them to acquire an Access Token from the Authorization Server.

我们的用例很简单:一旦用户提供了他们的凭证,前端客户端就会使用这些凭证从授权服务器获得一个访问令牌。

5.1. App Service

5.1.应用服务

Let’s start with our AppService – located at app.service.ts – which contains the logic for server interactions:

让我们从我们的AppService开始–位于app.service.ts–它包含服务器交互的逻辑。

  • obtainAccessToken(): to obtain Access token given user credentials
  • saveToken(): to save our access token in a cookie using ng2-cookies library
  • getResource(): to get a Foo object from server using its ID
  • checkCredentials(): to check if user is logged in or not
  • logout(): to delete access token cookie and log the user out
export class Foo {
  constructor(
    public id: number,
    public name: string) { }
} 

@Injectable()
export class AppService {
  constructor(
    private _router: Router, private _http: Http){}
 
  obtainAccessToken(loginData){
    let params = new URLSearchParams();
    params.append('username',loginData.username);
    params.append('password',loginData.password);    
    params.append('grant_type','password');
    params.append('client_id','fooClientIdPassword');
    let headers = 
      new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
      'Authorization': 'Basic '+btoa("fooClientIdPassword:secret")});
    let options = new RequestOptions({ headers: headers });
    
    this._http.post('http://localhost:8081/spring-security-oauth-server/oauth/token', 
      params.toString(), options)
      .map(res => res.json())
      .subscribe(
        data => this.saveToken(data),
        err => alert('Invalid Credentials')); 
  }

  saveToken(token){
    var expireDate = new Date().getTime() + (1000 * token.expires_in);
    Cookie.set("access_token", token.access_token, expireDate);
    this._router.navigate(['/']);
  }

  getResource(resourceUrl) : Observable<Foo>{
    var headers = 
      new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
      'Authorization': 'Bearer '+Cookie.get('access_token')});
    var options = new RequestOptions({ headers: headers });
    return this._http.get(resourceUrl, options)
                   .map((res:Response) => res.json())
                   .catch((error:any) => 
                     Observable.throw(error.json().error || 'Server error'));
  }

  checkCredentials(){
    if (!Cookie.check('access_token')){
        this._router.navigate(['/login']);
    }
  } 

  logout() {
    Cookie.delete('access_token');
    this._router.navigate(['/login']);
  }
}

Note that:

请注意,。

  • To get an Access Token we send a POST to the “/oauth/token” endpoint
  • We’re using the client credentials and Basic Auth to hit this endpoint
  • We’re then sending the user credentials along with the client id and grant type parameters URL encoded
  • After we obtain the Access Token – we store it in a cookie

The cookie storage is especially important here, because we’re only using the cookie for storage purposes and not to drive the authentication process directly. This helps protect against cross-site request forgery (CSRF) type of attacks and vulnerabilities.

cookie存储在这里特别重要,因为我们只是将cookie用于存储目的,而不是直接驱动认证过程。这有助于防止跨站请求伪造(CSRF)类型的攻击和漏洞。

5.2. Login Component

5.2.登录组件

Next, let’s take a look at our LoginComponent which is responsible for the login form:

接下来,让我们看一下我们的LoginComponent,它负责登录表单。

@Component({
  selector: 'login-form',
  providers: [AppService],  
  template: `<h1>Login</h1>
    <input type="text" [(ngModel)]="loginData.username" />
    <input type="password"  [(ngModel)]="loginData.password"/>
    <button (click)="login()" type="submit">Login</button>`
})
export class LoginComponent {
    public loginData = {username: "", password: ""};

    constructor(private _service:AppService) {}
 
    login() {
        this._service.obtainAccessToken(this.loginData);
    }

5.3. Home Component

5.3.主页组件

Next, our HomeComponent which is responsible for displaying and manipulating our Home Page:

接下来是我们的HomeComponent,负责显示和操作我们的主页。

@Component({
    selector: 'home-header',
    providers: [AppService],
  template: `<span>Welcome !!</span>
    <a (click)="logout()" href="#">Logout</a>
    <foo-details></foo-details>`
})
 
export class HomeComponent {
    constructor(
        private _service:AppService){}
 
    ngOnInit(){
        this._service.checkCredentials();
    }
 
    logout() {
        this._service.logout();
    }
}

5.4. Foo Component

5.4.Foo组件

Finally, our FooComponent to display our Foo details:

最后,我们的FooComponent显示我们的Foo细节。

@Component({
  selector: 'foo-details',
  providers: [AppService],  
  template: `<h1>Foo Details</h1>
    <label>ID</label> <span>{{foo.id}}</span>
    <label>Name</label> <span>{{foo.name}}</span>
    <button (click)="getFoo()" type="submit">New Foo</button>`
})

export class FooComponent {
    public foo = new Foo(1,'sample foo');
    private foosUrl = 'http://localhost:8082/spring-security-oauth-resource/foos/';  

    constructor(private _service:AppService) {}

    getFoo(){
        this._service.getResource(this.foosUrl+this.foo.id)
          .subscribe(
            data => this.foo = data,
            error =>  this.foo.name = 'Error');
    }
}

5.5. App Component

5.5.应用程序组件

Our simple AppComponent to act as the root component:

我们简单的AppComponent,作为根组件。

@Component({
    selector: 'app-root',
    template: `<router-outlet></router-outlet>`
})

export class AppComponent {}

And the AppModule where we wrap all our components, services and routes:

还有AppModule,我们把所有的组件、服务和路由包起来。

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    LoginComponent,
    FooComponent    
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
     { path: '', component: HomeComponent },
    { path: 'login', component: LoginComponent }])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

6. Implicit Flow

6.隐性流量

Next, we’ll focus on the Implicit Flow module.

接下来,我们将重点讨论隐式流模块。

6.1. App Service

6.1.应用服务

Similarly, we will start with our service, but this time we will use library angular-oauth2-oidc instead of obtaining access token ourselves:

同样,我们将从我们的服务开始,但这次我们将使用库angular-oauth2-oidc而不是自己获取访问令牌。

@Injectable()
export class AppService {
 
  constructor(
    private _router: Router, private _http: Http, private oauthService: OAuthService){
        this.oauthService.loginUrl = 
          'http://localhost:8081/spring-security-oauth-server/oauth/authorize'; 
        this.oauthService.redirectUri = 'http://localhost:8086/';
        this.oauthService.clientId = "sampleClientId";
        this.oauthService.scope = "read write foo bar";    
        this.oauthService.setStorage(sessionStorage);
        this.oauthService.tryLogin({});      
    }
 
  obtainAccessToken(){
      this.oauthService.initImplicitFlow();
  }

  getResource(resourceUrl) : Observable<Foo>{
    var headers = 
      new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
     'Authorization': 'Bearer '+this.oauthService.getAccessToken()});
    var options = new RequestOptions({ headers: headers });
    return this._http.get(resourceUrl, options)
      .map((res:Response) => res.json())
      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
  }

  isLoggedIn(){
    if (this.oauthService.getAccessToken() === null){
       return false;
    }
    return true;
  } 

  logout() {
      this.oauthService.logOut();
      location.reload();
  }
}

Note how, after obtaining the Access Token, we’re using it via the Authorization header whenever we consume protected resources from within the Resource Server.

请注意,在获得访问令牌后,每当我们从资源服务器内消费受保护的资源时,我们都会通过Authorization头来使用它。

6.2. Home Component

6.2.主页组件

Our HomeComponent to handle our simple Home Page:

我们的HomeComponent来处理我们的简单主页。

@Component({
    selector: 'home-header',
    providers: [AppService],
  template: `
    <button *ngIf="!isLoggedIn" (click)="login()" type="submit">Login</button>
    <div *ngIf="isLoggedIn">
        <span>Welcome !!</span>
        <a (click)="logout()" href="#">Logout</a>
        <br/>
        <foo-details></foo-details>
    </div>`
})
 
export class HomeComponent {
    public isLoggedIn = false;

    constructor(
        private _service:AppService){}
    
    ngOnInit(){
        this.isLoggedIn = this._service.isLoggedIn();
    }

    login() {
        this._service.obtainAccessToken();
    }

    logout() {
        this._service.logout();
    }
}

6.3. Foo Component

6.3.Foo组件

Our FooComponent is exactly the same as in the password flow module.

我们的FooComponent与密码流模块中的完全相同。

6.4. App Module

6.4.应用程序模块

Finally, our AppModule:

最后,我们的AppModule

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    FooComponent    
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    OAuthModule.forRoot(),    
    RouterModule.forRoot([
     { path: '', component: HomeComponent }])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

7. Run the Front End

7.运行前端

1. To run any of our front-end modules, we need to build the app first:

1.要运行我们的任何前端模块,我们需要先建立应用程序。

mvn clean install

2. Then we need to navigate to our Angular app directory:

2.然后我们需要导航到我们的Angular应用目录。

cd src/main/resources

3. Finally, we will start our app:

3.最后,我们将启动我们的应用程序。

npm start

The server will start by default on port 4200, to change the port of any module change the

服务器将默认在端口4200上启动,要改变任何模块的端口,请改变

"start": "ng serve"

in package.json to make it run on port 8086 for example:

例如,在package.json中,使其在8086端口运行。

"start": "ng serve --port 8086"

8. Conclusion

8.结论

In this article, we learned how to authorize our application using OAuth2.

在这篇文章中,我们学习了如何使用OAuth2对我们的应用程序进行授权。

The full implementation of this tutorial can be found in the GitHub project.

本教程的完整实现可以在GitHub项目中找到。