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应用的基础。

相关推荐
paopaokaka_luck1 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts
RisunJan1 小时前
Linux命令-shutdown(安全关闭或重启系统)
linux·服务器·安全
山东科恩光电3 小时前
如何通过安全地毯确保工业现场的人员防护?
安全
小小代码狗4 小时前
PHP 常用函数与变量参考文档
开发语言·安全·php
笨鸟先飞,勤能补拙4 小时前
下一个十年:网络安全正在被重写
网络·人工智能·安全·web安全·网络安全·github
月光船幽幽5 小时前
安全域公理:连续与脉冲扰动的量化约束
科技·安全
各类产品分享6 小时前
化工园区做AR安全巡检推荐哪家供应商
安全·ar·ar智慧运维解决方案
米瑞格门窗6 小时前
上海系统门窗哪个靠谱
大数据·人工智能·安全·家装·家装建材·系统门窗
热心市民lcj6 小时前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存
Nuoyunjituan7 小时前
IPv6单栈、智能体互联、安全合规全面升级——互联网“新底座”政策对企业意味着什么?
安全