【问题标题】:How to Manage Authentication between an Angular 4 Application and an Application (Spring Boot / security)?如何管理 Angular 4 应用程序和应用程序(Spring Boot / 安全)之间的身份验证?
【发布时间】:2018-01-10 22:33:33
【问题描述】:

我们在选择理想的方法来处理分布式应用程序中的身份验证和权限时遇到了问题:

  • 后端由 JAVA EE (Spring Boot) / Tomcat Server 开发

  • 前端由 Angular 4 / NodeJS Server 开发

我们使用 Spring Security Framework,到目前为止,我们已经使用了 3 个可用选项(HTTP Basic、JWT 和 Oauth2),同时尝试调整 Jhipster 项目生成的代码,但不幸的是,其中一个仍然停留在信息的恢复中想要进行身份验证的用户。

换句话说,用户输入他的登录名和密码,服务器通过 URL (http://localhost:8080/api/authenticate) 接收此信息

通过在 java 和客户端的调试,我看到服务器已经恢复(用户名/密码),并以以下形式向浏览器发送令牌(对于 Oauth2 的情况):

{"access_token":"1b0ac85d-b4ed-463f-af3a-acbce7d28353","token_type":"bearer","re​​fresh_token":"ed2f9041-7726-4939-93ba-4816503e3859","expires_in":1799, "范围":"读写"}

但在那之后我必须检索该用户的信息以将它们存储在 LocalStorage (Angular 4) 中并重用它们,这就是为什么在调用上面引用的第一个 Web 服务之后,我调用第二个 Web 服务: (http://localhost:8080/auth/account)

此时,检索数据库信息的链接函数发送一个用户名 / pwd = null 的查询,这给了我们一个 500 消息

在java应用程序中:

/**
 * GET /authenticate : check if the user is authenticated, and return its
 * login.
 *
 * @param request
 *            the HTTP request
 * @return the login if the user is authenticated
 */

@RequestMapping(value = ApiConstants.API_ANONYME + "/authenticate", method = RequestMethod.GET)
public String isAuthenticated(HttpServletRequest request) {
    log.debug("REST request to check if the current user is authenticated");

    String remoteUser = request.getRemoteUser();
    return remoteUser;
}

/**
 * GET /account : get the current user.
 *
 * @return the ResponseEntity with status 200 (OK) and the current user in
 *         body, or status 500 (Internal Server Error) if the user couldn't
 *         be returned
 */
// @GetMapping("/account")

@RequestMapping(value = ApiConstants.API_AUTH + "/account", method = RequestMethod.GET)

public ResponseEntity<Utilisateur> getAccount() {

    Utilisateur userWithAuthorities = userService.getUserWithAuthorities();

    return Optional.ofNullable(userWithAuthorities)
            .map(user -> new ResponseEntity<>(new Utilisateur(), HttpStatus.OK))
            .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}

在 Angular 4 应用程序中:

export class ConnexionBodyComponent implements OnInit {

  // model = new Login("", "");
     authenticationError: boolean;
     password: string;
     rememberMe: boolean;
     username: string;
     credentials: any;

     constructor(private loginService: LoginService, private router: Router, 
     private eventManager: EventManager, private stateStorageService: 
     StateStorageService) {

                          }

     ngOnInit() {
     }


    login() {
    this.loginService.login({
    username: this.username,
    password: this.password,
    rememberMe: this.rememberMe
    }).then(() => {
    this.authenticationError = false;
    /*if (this.router.url === '/register' || 
    (/activate/.test(this.router.url)) ||
    this.router.url === '/finishReset' || this.router.url === 
    '/requestReset') {
    this.router.navigate(['']);
    }*/

  console.log(this.authenticationError);
  this.eventManager.broadcast({
    name: 'authenticationSuccess',
    content: 'Sending Authentication Success'
  });

  // // previousState was set in the authExpiredInterceptor before being 
  redirected to login modal.
  // // since login is succesful, go to stored previousState and clear 
  previousState
  const redirect = this.stateStorageService.getUrl();
  if (redirect) {
    this.router.navigate([redirect]);
  }
  }).catch(() => {
  this.authenticationError = true;
  });
  }

  }

帐户服务:

     @Injectable()
     export class AccountService  {
         constructor(private http: Http) { }

         get(): Observable<any> {
             return 
     this.http.get(LocalhostSettings.API_ENDPOINT+'/auth/account').map((res: 
     Response) => res.json());
         }

         save(account: any): Observable<Response> {
             return 
     this.http.post(LocalhostSettings.API_ENDPOINT+'/auth/account', 
     account);
         }
     }

auth.service.ts:

          @Injectable()
          export class AuthService {

              constructor(
                  private principal: Principal,
                  private stateStorageService: StateStorageService,

                  private router: Router
              ) {}

              authorize(force) {
                  const authReturn = this.principal.identity(force).then(authThen.bind(this));

                  return authReturn;

                  function authThen() {
                      const isAuthenticated = this.principal.isAuthenticated();
                      const toStateInfo = this.stateStorageService.getDestinationState().destination;

                      // an authenticated user can't access to login and register pages
                      if (isAuthenticated && (toStateInfo.name === 'register')) {
                          this.router.navigate(['']);
                          return false;
                      }

                      // recover and clear previousState after external login redirect (e.g. oauth2)
                      const fromStateInfo = this.stateStorageService.getDestinationState().from;
                      const previousState = this.stateStorageService.getPreviousState();
                      if (isAuthenticated && !fromStateInfo.name && previousState) {
                          this.stateStorageService.resetPreviousState();
                          this.router.navigate([previousState.name], { queryParams:  previousState.params  });
                          return false;
                      }

                      if (toStateInfo.data.authorities && toStateInfo.data.authorities.length > 0) {
                          return this.principal.hasAnyAuthority(toStateInfo.data.authorities).then((hasAnyAuthority) => {
                              if (!hasAnyAuthority) {
                                  if (isAuthenticated) {
                                      // user is signed in but not authorized for desired state
                                      this.router.navigate(['accessdenied']);
                                  } else {
                                      // user is not authenticated. Show the state they wanted before you
                                      // send them to the login service, so you can return them when you're done
                                      const toStateParamsInfo = this.stateStorageService.getDestinationState().params;
                                      this.stateStorageService.storePreviousState(toStateInfo.name, toStateParamsInfo);
                                      // now, send them to the signin state so they can log in
                                      this.router.navigate(['accessdenied']).then(() => {
                                          console.log('accessdenied');
                                      });
                                  }
                              }
                              return hasAnyAuthority;
                          });
                      }
                      return true;
                  }
              }
          }

auth-oauth2.service.ts :

        @Injectable()
        export class AuthServerProvider {

            constructor(
                private http: Http,
                private base64: Base64,
                private $localStorage: LocalStorageService
            ) {}

            getToken() {
                return this.$localStorage.retrieve('authenticationToken');
            }

            login(credentials): Observable<any> {
                const data = 'username=' +  encodeURIComponent(credentials.username) + '&password=' +
                    encodeURIComponent(credentials.password) + '&grant_type=password&scope=read%20write&' +
                    'client_secret=my-secret-token-to-change-in-production&client_id=directinfoapp';
                const headers = new Headers ({
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Accept': 'application/json',
                    'Authorization': 'Basic ' + this.base64.encode('directinfoapp' + ':' + 'my-secret-token-to-change-in-production')
                });


              /*
               {"access_token":"ff102054-2072-4c29-91a8-c8f43246a3b7","token_type":"bearer","refresh_token":"94b68064-98a6-49e5-9dbc-0cac34e434f3","expires_in":1799,"scope":"read write"}
               */
                console.log("headers : " + headers);
                return this.http.post(LocalhostSettings.API_ENDPOINT+'/oauth/token', data, {

                    headers
                }).map(authSuccess.bind(this));

                function authSuccess(resp) {
                    const response = resp.json();

                  console.log("authSuccess " + resp.json())
                    const expiredAt = new Date();
                    expiredAt.setSeconds(expiredAt.getSeconds() + response.expires_in);
                    response.expires_at = expiredAt.getTime();
                    this.$localStorage.store('authenticationToken', response);
                    return response;
                }
            }

            logout(): Observable<any> {
                return new Observable(observer => {
                    this.http.post('api/logout', {});
                    this.$localStorage.clear('authenticationToken');
                    observer.complete();
                });
            }
        }

login.service.ts

              @Injectable()
              export class LoginService {

                  constructor(

                      private principal: Principal,
                      private authServerProvider: AuthServerProvider
                  ) {}

                  login(credentials, callback?) {
                      const cb = callback || function() {};

                      return new Promise((resolve, reject) => {
                          this.authServerProvider.login(credentials).subscribe((data) => {
                              this.principal.identity(true).then((account) => {
                                  // After the login the language will be changed to
                                  // the language selected by the user during his registration
                                  if (account !== null) {
                                      account.langKey;
                                  }
                                  resolve(data);
                              });
                              return cb();
                          }, (err) => {
                              this.logout();
                              reject(err);
                              return cb(err);
                          });
                      });
                  }

                  logout() {
                      this.authServerProvider.logout().subscribe();
                      this.principal.authenticate(null);
                  }
              }

主要服务:

             @Injectable()
             export class Principal {
                 private userIdentity: any;
                 private authenticated = false;
                 private authenticationState = new Subject<any>();

                 constructor(
                     private account: AccountService
                 ) {}

                 authenticate(identity) {
                     this.userIdentity = identity;
                     this.authenticated = identity !== null;
                     this.authenticationState.next(this.userIdentity);
                 }

                 hasAnyAuthority(authorities: string[]): Promise<boolean> {
                     if (!this.authenticated || !this.userIdentity || !this.userIdentity.authorities) {
                         return Promise.resolve(false);
                     }

                     for (let i = 0; i < authorities.length; i++) {
                         if (this.userIdentity.authorities.indexOf(authorities[i]) !== -1) {
                             return Promise.resolve(true);
                         }
                     }

                     return Promise.resolve(false);
                 }

                 hasAuthority(authority: string): Promise<boolean> {
                     if (!this.authenticated) {
                        return Promise.resolve(false);
                     }

                     return this.identity().then((id) => {
                         return Promise.resolve(id.authorities && id.authorities.indexOf(authority) !== -1);
                     }, () => {
                         return Promise.resolve(false);
                     });
                 }

                 identity(force?: boolean): Promise<any> {
                     if (force === true) {
                         this.userIdentity = undefined;
                     }

                     // check and see if we have retrieved the userIdentity data from the server.
                     // if we have, reuse it by immediately resolving
                     if (this.userIdentity) {
                         return Promise.resolve(this.userIdentity);
                     }

                     // retrieve the userIdentity data from the server, update the identity object, and then resolve.
                     return this.account.get().toPromise().then((account) => {
                         if (account) {
                             this.userIdentity = account;
                             this.authenticated = true;
                         } else {
                             this.userIdentity = null;
                             this.authenticated = false;
                         }
                         this.authenticationState.next(this.userIdentity);
                         return this.userIdentity;
                     }).catch((err) => {
                         this.userIdentity = null;
                         this.authenticated = false;
                         this.authenticationState.next(this.userIdentity);
                         return null;
                     });
                 }

                 isAuthenticated(): boolean {
                     return this.authenticated;
                 }

                 isIdentityResolved(): boolean {
                     return this.userIdentity !== undefined;
                 }

                 getAuthenticationState(): Observable<any> {
                     return this.authenticationState.asObservable();
                 }

                 getImageUrl(): String {
                     return this.isIdentityResolved() ? this.userIdentity.imageUrl : null;
                 }
             }

有什么想法可以完成这项工作吗?

【问题讨论】:

  • 恐怕你的问题太长而且不太清楚。也许您可以编辑它并添加您想要实现的高级概述(谁需要 OAuth2 令牌,您使用什么 OAuth2 流程)以及您尝试过的内容。为了描述身份验证过程,您可以使用一个步骤列表,其中每个步骤都说明由谁执行了哪些操作以及结果是什么。
  • 我的目标是管理 Angular 4 应用程序和 Spring Boot 应用程序之间的安全部分,但我不知道执行此操作的理想选择(Ouath 2、HTTP Basic 或 JWT),如果你有教程或链接我可以在哪里找到更多详细信息我会很感激你

标签: angular spring-boot spring-security oauth-2.0 jhipster


【解决方案1】:

我的建议(一些可能的场景):

如果您想要进行外部身份验证,请使用 OpenID Connect(OAuth2 扩展)及其 ID 令牌。使用 OAuth2 隐式流程,因此身份验证重定向由您的 Angular 应用程序处理,而不是其服务器。然后,您可以使用 ID 令牌作为后端的身份验证(将令牌作为请求标头发送 Authorization: Bearer tokenvalue),或者您的后端可以使用 ID 令牌发出自己的 JWT,其中包含您需要的任何其他信息(角色、权限和这样的)。您不会使用 OAuth2 进行授权。您的后端将使用自己的方式来检查访问权限。

另一种方法是实现您自己的身份验证方法(例如,只是一个带有凭据的简单表单)而不是 OpenID Connect。但是 OpenID Connect 为您提供了一些优势:

  • 可能比您自己的实现更安全
  • 您不会在应用程序中保留用户机密
  • 您可以使用多个身份验证提供程序

【讨论】:

    猜你喜欢
    • 2021-04-17
    • 2018-02-20
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 2018-04-15
    • 2017-01-13
    • 1970-01-01
    • 2018-10-31
    相关资源
    最近更新 更多