如何管理Angular 4应用程序和应用程序之间的身份验证(Spring Boot / security)?

时间:2017-08-03 10:55:49

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

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

  • 后端由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和客户端调试,我看到服务器已经恢复(用户名/ PWD)并且它以下面的形式向浏览器发送一个令牌(对于Oauth2的情况):

{"的access_token":" 1b0ac85d-b4ed-463f-af3a-acbce7d28353"" token_type":"承载"&# 34; refresh_token":" ed2f9041-7726-4939-93ba-4816503e3859"," expires_in":1799,"范围":"读写& #34;}

但之后我必须检索该用户的信息以将它们存储在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;
  });
  }

  }

AccountService:

     @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);
                  }
              }

principal.service:

             @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;
                 }
             }

我有什么想法可以做到这一点?

1 个答案:

答案 0 :(得分:0)

我的建议(一些可能的情况):

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

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

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