身份验证绕过

现在的网站中所有敏感操作都应要求用户经过身份验证(也就是说识别他们是谁)并获得授权(具有足够的权限来执行该操作)。如果 Web 路由无法检查用户身份,那么攻击者将能够访问敏感资源。

JavaScript 框架通常使用路由库来更新浏览器 URL 和页面内容以响应用户操作。在允许用户访问敏感路由之前,各位师傅们应该检查用户是否已通过身份验证,举例。

Angular 中的路由通常通过以下方式完成AppRoutingModule:

@NgModule({
    imports: [RouterModule.forRoot([
        
      // These paths are available to all users.
      { path: '',         component: HomeComponent     },
      { path: 'features', component: FeaturesComponent },
      { path: 'login',    component: LoginComponent    },
      
      // These routes are only available to users after logging in.
      { path: 'feed',     component: FeedComponent,    canActivate: [ AuthGuard ]},
      { path: 'profile',  component: ProfileComponent, canActivate: [ AuthGuard ]},
        
      // This is the fall-through component when the route is not recognized.
      { path: '**',       component: PageNotFoundComponent}
        
    ])],
    exports: [RouterModule]
  })
  export class AppRoutingModule {}

React 中的路由通常使用react-router库完成

function Routes(props) {
      return (
        <Router>
          <Switch>
    
            <!-- These routes are publicly available. -->
            <Route path="/login">
              <Login/>
            </Route>
            <Route path="/news">
              <News/>
            </Route>
            <Route path="/about">
              <About/>
            </Route>
    
            <!-- These routes are only available to authenticated user. -->
            <AuthenticatedRoute path="/feed" loggedIn={props.loggedIn}>
              <Feed/>
            </AuthenticatedRoute>
    
            <AuthenticatedRoute path="/profile" loggedIn={props.loggedIn}>
              <Profile/>
            </AuthenticatedRoute>
    
            <Route path="*">
              <NotFound/>
            </Route>
            
          </Switch>
        </Router>
      )
    }
    
    /**
* A wrapper for <Route> that redirects to the login screen if the user is not yet authenticated.
*/
    function AuthenticatedRoute(props) {
      if (props.loggedIn) {
        return <Route {...props} />
      }
    
      return <Redirect to={{ pathname: `/login` }} />
    }

但是,客户端的授权检查可能会被攻击者覆盖,因此在将数据加载到应用程序时检查用户是否登录非常重要。这些授权检查必须在服务器端代码中执行,并且客户端代码应该处理身份验证失败

export class AuthGuard implements CanActivate {
      constructor(private authService: AuthService, private router: Router) {}
    
      canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<true | UrlTree> {
        
        // Check the user is logged in, redirect them to the login screen if they are not.
        return this.authService.isLoggedIn().pipe(
          map(isLoggedIn => {
            if (!isLoggedIn) {
              return this.router.parseUrl('/login')
            }
    
            return true
          })
        );
      }
    }
    
    export class AuthService {
      constructor(private http: HttpClient) {}
    
      // Whether the user is currently logged in.
      loggedIn: boolean | null = null
    
      // The user object.
      user: User | null = null
    
      // Check whether the user is logged in.
      isLoggedIn(): Observable<boolean> {
        return this.getCurrentUser().pipe(map(user => {
          return user != null
        }))
      }
    
      // Get the user definition from local state, or the server (if this is the first time we are checking).
      getCurrentUser(): Observable<User | null> {
        if (this.loggedIn !== null) {
          return of(this.user)
        }
    
        return this.http.get<User>('/api/auth', {
          responseType: 'json'
        }).pipe(
         tap({
           next: user => {
             // If we get a user definition from the server it indicates this user is logged in.
             this.user     = user
             this.loggedIn = true
           },
           error: error => {
             // A 401 response from the server indicates this user is not logged in.
             this.user     = null
             this.loggedIn = false
           }
         }),
         catchError(() => {
           return of(null)
         })
        )
      }
    }
    
    export interface User {
      username: string;
    }
class Feed extends React.Component {
      constructor(props) {
        super(props)
        
        this.state = {
          loading : true,
          posts   : [],
          error   : null
        }
      }
    
      async componentDidMount() {
        
        // Load the data for this component.
        const response = await fetch('/api/feed')
        const data     = await response.json()
    
        if (response.ok && data.success) {
          
          // Set the data in the component state.
          this.setState({
            loading : false,
            posts   : data.posts
          })
        }
        else {
          
          // Shown an error message if the feed cannot be loaded - for instance,
          // if the user is not authenticated.
          this.setState({
            loading : false,
            error   : data.error || 'There was a problem loading your feed.'
          })
        }
      }
    
      render() {
        const error = this.state.error ? <div className="error">{this.state.error}</div> : null
    
        let body
        if (this.state.loading) {
          body = <div className="loading">Loading...</div>
        }
        else {
          // Render the component based on the data returned from the server.
          body = this.state.posts.map(post => {
            return <Post post={post}/>
          })
        }
    
        return (
          <div>
            {error}
            {body}
          </div>
        )
      }
    }