Spring Boot + Angular 实现安全登录注册系统:全栈开发指南

引言:现代Web应用认证的重要性

在当今数字化时代,用户认证是Web应用的基石。无论是电商平台、社交媒体还是企业系统,安全的登录注册功能都至关重要。本文将手把手教你使用Spring Boot 作为后端、Angular作为前端,构建一个完整的登录注册系统。

系统整体架构设计

我们的系统采用经典的前后端分离架构:

graph TD subgraph Frontend[Angular前端] A[登录组件] -->|调用| B[认证服务] C[注册组件] -->|调用| B D[路由守卫] -->|保护| E[受保护路由] F[HTTP拦截器] -->|添加Token| G[HTTP请求] end subgraph Backend[Spring Boot后端] H[认证控制器] -->|处理| I[注册端点] H -->|处理| J[登录端点] K[安全配置] -->|保护| L[受保护API] M[JWT工具] -->|生成/验证| N[认证] O[用户仓库] -->|数据操作| P[数据库] end Frontend -->|HTTP API调用| Backend

架构核心组件:

  1. 前端:Angular应用,包含登录/注册组件、认证服务和路由守卫
  2. 后端:Spring Boot应用,提供REST API,处理认证和用户管理
  3. 通信:HTTPS协议,JSON数据格式
  4. 认证:基于JWT(JSON Web Token)的无状态认证机制

后端实现:Spring Boot安全认证

技术栈

  • Spring Security
  • Spring Data JPA
  • JJWT库
  • H2数据库(开发环境)
  • Lombok

关键代码实现

1. 用户实体类
java 复制代码
@Entity
@Data
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true)
    private String email;
    
    private String password;
    private String name;
    private String role = "USER";
    private LocalDateTime createdAt = LocalDateTime.now();
}
2. JWT工具类
java 复制代码
@Component
public class JwtUtil {
    private final String SECRET_KEY = "your-strong-secret-key-here";
    private final long EXPIRATION_MS = 10 * 60 * 60 * 1000; // 10小时
    
    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
    
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}
3. 安全配置
java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/**", "/h2-console/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtUtil))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        // 允许H2控制台的帧访问
        http.headers().frameOptions().disable();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
4. 认证控制器
java 复制代码
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @PostMapping("/register")
    public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
        if (userRepository.existsByEmail(request.getEmail())) {
            return ResponseEntity.badRequest().body(
                Map.of("message", "Email already exists")
            );
        }
        
        User user = new User();
        user.setEmail(request.getEmail());
        user.setName(request.getName());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        userRepository.save(user);
        
        return ResponseEntity.ok(Map.of("message", "User registered successfully"));
    }
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    request.getEmail(),
                    request.getPassword()
                )
            );
            
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            String jwt = jwtUtil.generateToken(userDetails);
            
            return ResponseEntity.ok(new AuthResponse(jwt));
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
                Map.of("message", "Invalid credentials")
            );
        }
    }
}

前端实现:Angular认证系统

项目结构

复制代码
src/
├── app/
│   ├── core/
│   │   ├── guards/          # 路由守卫
│   │   ├── interceptors/     # HTTP拦截器
│   │   └── services/         # 核心服务
│   ├── modules/
│   │   ├── auth/             # 认证模块
│   │   └── dashboard/        # 主应用模块
│   ├── shared/               # 共享模块
│   ├── app-routing.module.ts # 路由配置
│   └── app.module.ts         # 主模块

关键组件实现

1. 认证服务
typescript 复制代码
@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly apiUrl = `${environment.apiUrl}/auth`;
  private currentUserSubject = new BehaviorSubject<User | null>(null);
  public currentUser$ = this.currentUserSubject.asObservable();

  constructor(
    private http: HttpClient,
    private tokenService: TokenService,
    private router: Router
  ) {
    const user = localStorage.getItem('currentUser');
    if (user) {
      this.currentUserSubject.next(JSON.parse(user));
    }
  }

  login(credentials: { email: string; password: string }): Observable<any> {
    return this.http.post<{ token: string }>(`${this.apiUrl}/login`, credentials).pipe(
      tap(response => {
        this.tokenService.setToken(response.token);
        this.fetchCurrentUser();
      })
    );
  }

  fetchCurrentUser(): void {
    this.http.get<User>(`${environment.apiUrl}/users/me`).subscribe({
      next: user => {
        this.currentUserSubject.next(user);
        localStorage.setItem('currentUser', JSON.stringify(user));
      },
      error: () => this.logout()
    });
  }

  logout(): void {
    this.tokenService.removeToken();
    this.currentUserSubject.next(null);
    localStorage.removeItem('currentUser');
    this.router.navigate(['/login']);
  }
}
2. 登录组件
typescript 复制代码
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  loginForm: FormGroup;
  isLoading = false;
  errorMessage: string | null = null;
  returnUrl: string | null = null;
  showPassword = false;

  constructor(
    private fb: FormBuilder,
    private authService: AuthService,
    private router: Router,
    private route: ActivatedRoute
  ) {
    this.loginForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required],
      rememberMe: [false]
    });
  }

  onSubmit(): void {
    if (this.loginForm.invalid) return;

    this.isLoading = true;
    this.errorMessage = null;

    const { email, password } = this.loginForm.value;

    this.authService.login({ email, password }).subscribe({
      next: () => {
        this.router.navigateByUrl(this.returnUrl || '/dashboard');
      },
      error: (err) => {
        this.errorMessage = '登录失败,请检查您的凭据';
        this.isLoading = false;
      }
    });
  }
}
3. 登录组件模板
html 复制代码
<div class="login-container">
  <mat-card class="login-card">
    <mat-card-header>
      <mat-card-title>欢迎回来</mat-card-title>
      <mat-card-subtitle>请登录您的账户</mat-card-subtitle>
    </mat-card-header>

    <mat-card-content>
      <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
        <mat-form-field appearance="outline">
          <mat-label>电子邮箱</mat-label>
          <input matInput formControlName="email" type="email">
          <mat-icon matSuffix>mail</mat-icon>
          <mat-error *ngIf="loginForm.get('email')?.hasError('required')">
            邮箱为必填项
          </mat-error>
        </mat-form-field>

        <mat-form-field appearance="outline">
          <mat-label>密码</mat-label>
          <input 
            matInput 
            [type]="showPassword ? 'text' : 'password'" 
            formControlName="password"
          >
          <button 
            type="button" 
            mat-icon-button 
            matSuffix
            (click)="togglePasswordVisibility()"
          >
            <mat-icon>{{ showPassword ? 'visibility_off' : 'visibility' }}</mat-icon>
          </button>
        </mat-form-field>

        <button 
          mat-raised-button 
          color="primary" 
          type="submit"
          [disabled]="loginForm.invalid || isLoading"
        >
          <span *ngIf="!isLoading">登录</span>
          <mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
        </button>
      </form>
    </mat-card-content>
  </mat-card>
</div>
4. HTTP拦截器
typescript 复制代码
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(
    private tokenService: TokenService,
    private authService: AuthService,
    private router: Router
  ) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const token = this.tokenService.getToken();
    let authReq = request;
    
    if (token) {
      authReq = request.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }

    return next.handle(authReq).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          this.authService.logout();
          this.router.navigate(['/login'], { queryParams: { expired: true } });
        }
        return throwError(() => error);
      })
    );
  }
}

系统数据流分析

登录流程

User Angular SpringBoot Database 输入凭据并提交 POST /api/auth/login 查询用户 返回用户数据 生成JWT 200 OK (含JWT) 存储Token 跳转到主页 401 Unauthorized 显示错误 alt 认证成功 认证失败 User Angular SpringBoot Database

注册流程

User Angular SpringBoot Database 填写注册表单 POST /api/auth/register 检查邮箱唯一性 密码加密 保存用户 保存成功 200 OK 显示成功消息 400 Bad Request 显示错误 alt 邮箱可用 邮箱已存在 User Angular SpringBoot Database

安全架构设计

HTTPS Angular前端 Spring Boot后端 Spring Security JWT认证 Token生成 Token验证 密码加密 BCrypt CORS配置 CSRF防护

安全措施:

  1. 密码安全:BCrypt强哈希算法存储密码
  2. 传输安全:强制使用HTTPS
  3. 令牌安全
    • JWT设置合理有效期(建议2小时)
    • 使用强密钥(256位以上)
  4. 跨域控制:严格的白名单策略
  5. 输入验证:前后端双重验证

部署架构

HTTPS 静态文件 API代理 用户 Nginx Angular应用 Spring Boot应用 MySQL数据库集群

部署要点:

  1. 使用Nginx作为反向代理和静态文件服务器
  2. Spring Boot应用使用内嵌Tomcat
  3. 数据库主从复制提高可用性
  4. 使用环境变量管理敏感信息
  5. 配置监控和日志系统

总结与扩展

我们实现了一个完整的登录注册系统,具有以下特点:

✅ 前后端分离架构

✅ JWT无状态认证

✅ 响应式表单验证

✅ 路由级权限控制

✅ 多层安全防护

扩展方向:

  1. 添加社交登录(OAuth2)
  2. 实现双因素认证
  3. 集成短信/邮箱验证
  4. 添加RBAC权限管理系统
  5. 实现密码重置功能

项目源码
GitHub - Spring Boot后端
GitHub - Angular前端

通过本文,你应该已经掌握了使用Spring Boot和Angular构建登录注册系统的核心知识和技能。这个架构不仅适用于登录注册功能,还可以作为任何需要用户认证的Web应用的基础。

相关推荐
starrysky8101 天前
Hermes Agent 的 70+ 工具不是硬编码的:一套自注册的注册表引擎 [04]
angular.js
Aphasia3112 天前
VPN 与内网穿透
安全
用户3521802454753 天前
当 Prompt 学会"热更新":Spring Boot × Nacos3 AI 实战
java·spring boot·ai编程
昵称为空C3 天前
手撸一个动态 SQL 执行引擎:不重启服务,在线增删改查任意数据库
spring boot·后端
巴勒个啦3 天前
Pinia 源码解析:响应式状态管理是如何工作的
angular.js
Mr_愚人派3 天前
当"Claude"不再是 Claude:一次第三方 API 代理引发的 AI 身份伪造排查实录
人工智能·安全
霸道流氓气质4 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
于先生吖4 天前
SpringBoot对接大模型开发AI命理测算系统:八字排盘与AI解析接口源码全解
人工智能·spring boot·后端
DaLi Yao4 天前
【无标题】
人工智能·安全