代码示例集¶
目录¶
基础用户名密码认证¶
完整项目结构¶
user-auth-demo/
├── src/main/java/com/example/auth/
│ ├── config/
│ │ └── SecurityConfig.java
│ ├── controller/
│ │ ├── AuthController.java
│ │ └── UserController.java
│ ├── entity/
│ │ ├── User.java
│ │ └── Role.java
│ ├── repository/
│ │ ├── UserRepository.java
│ │ └── RoleRepository.java
│ ├── service/
│ │ ├── UserService.java
│ │ └── CustomUserDetailsService.java
│ └── dto/
│ ├── RegisterRequest.java
│ └── LoginResponse.java
└── src/main/resources/
├── application.yml
└── schema.sql
实体类¶
/**
* 用户实体
*/
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(unique = true, nullable = false)
private String email;
private boolean enabled = true;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
/**
* 角色实体
*/
@Entity
@Table(name = "roles")
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name; // ADMIN, USER, MANAGER
private String description;
}
Repository¶
/**
* 用户仓库
*/
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
/**
* 角色仓库
*/
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(String name);
}
UserDetailsService实现¶
/**
* 自定义UserDetailsService
*/
@Service
@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(getAuthorities(user))
.accountExpired(!user.isAccountNonExpired())
.accountLocked(!user.isAccountNonLocked())
.credentialsExpired(!user.isCredentialsNonExpired())
.disabled(!user.isEnabled())
.build();
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
return user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toSet());
}
}
Service层¶
/**
* 用户服务
*/
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 注册新用户
*/
public User registerUser(RegisterRequest request) {
// 检查用户名是否已存在
if (userRepository.existsByUsername(request.getUsername())) {
throw new IllegalArgumentException("用户名已存在");
}
// 检查邮箱是否已存在
if (userRepository.existsByEmail(request.getEmail())) {
throw new IllegalArgumentException("邮箱已被使用");
}
// 创建用户
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setEmail(request.getEmail());
user.setEnabled(true);
// 分配默认角色
Role userRole = roleRepository.findByName("USER")
.orElseThrow(() -> new RuntimeException("角色USER不存在"));
user.getRoles().add(userRole);
return userRepository.save(user);
}
/**
* 更新用户
*/
public User updateUser(Long id, User updatedUser) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
user.setEmail(updatedUser.getEmail());
return userRepository.save(user);
}
/**
* 修改密码
*/
public void changePassword(Long userId, String oldPassword, String newPassword) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
// 验证旧密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new IllegalArgumentException("旧密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
/**
* 禁用用户
*/
public void disableUser(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
user.setEnabled(false);
userRepository.save(user);
}
}
Controller¶
/**
* 认证控制器
*/
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用户注册
*/
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
try {
User user = userService.registerUser(request);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "注册成功");
response.put("userId", user.getId());
return ResponseEntity.ok(response);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(Map.of("success", false, "message", e.getMessage()));
}
}
/**
* 用户登录
*/
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "登录成功");
response.put("username", userDetails.getUsername());
response.put("authorities", userDetails.getAuthorities());
return ResponseEntity.ok(response);
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("success", false, "message", "用户名或密码错误"));
}
}
/**
* 退出登录
*/
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return ResponseEntity.ok(Map.of("success", true, "message", "登出成功"));
}
}
/**
* 用户控制器
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 获取当前用户信息
*/
@GetMapping("/me")
public ResponseEntity<?> getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return ResponseEntity.ok(Map.of(
"username", userDetails.getUsername(),
"authorities", userDetails.getAuthorities()
));
}
/**
* 修改密码
*/
@PutMapping("/me/password")
public ResponseEntity<?> changePassword(
@AuthenticationPrincipal UserDetails userDetails,
@RequestBody ChangePasswordRequest request) {
User user = userRepository.findByUsername(userDetails.getUsername())
.orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
userService.changePassword(user.getId(), request.getOldPassword(), request.getNewPassword());
return ResponseEntity.ok(Map.of("success", true, "message", "密码修改成功"));
}
/**
* 管理员获取所有用户
*/
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> getAllUsers() {
List<User> users = userRepository.findAll();
return ResponseEntity.ok(users);
}
}
DTO¶
/**
* 注册请求
*/
@Data
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码长度至少6位")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}
/**
* 登录请求
*/
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
/**
* 修改密码请求
*/
@Data
public class ChangePasswordRequest {
@NotBlank(message = "旧密码不能为空")
private String oldPassword;
@NotBlank(message = "新密码不能为空")
@Size(min = 6, message = "密码长度至少6位")
private String newPassword;
}
配置类¶
/**
* Security配置
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**")
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.expiredUrl("/login?expired")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
}
数据库初始化¶
-- schema.sql
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
account_non_expired BOOLEAN DEFAULT TRUE,
account_non_locked BOOLEAN DEFAULT TRUE,
credentials_non_expired BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE roles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
description VARCHAR(255)
);
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
-- 初始化角色
INSERT INTO roles (name, description) VALUES
('USER', '普通用户'),
('ADMIN', '管理员'),
('MANAGER', '管理者');
-- 初始化管理员账户 (密码: admin123)
INSERT INTO users (username, password, email) VALUES
('admin', '$2a$12$LQv3c1yqBWVHxkn0LHAHr.qnwk5Qm5xPFgKCEfKdJt6FxM6i9.p5W', 'admin@example.com');
INSERT INTO user_roles (user_id, role_id) VALUES
(1, 1), (1, 2);
JWT Token实现¶
JWT完整项目¶
jwt-auth-demo/
├── src/main/java/com/example/jwt/
│ ├── config/
│ │ ├── SecurityConfig.java
│ │ └── JwtConfig.java
│ ├── security/
│ │ ├── JwtTokenProvider.java
│ │ ├── JwtAuthenticationFilter.java
│ │ └── JwtAuthenticationEntryPoint.java
│ ├── controller/
│ │ └── AuthController.java
│ └── dto/
│ ├── TokenResponse.java
│ └── RefreshTokenRequest.java
JWT Token Provider¶
/**
* JWT Token提供者
*/
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.access-token-expiration:3600000}") // 1小时
private long accessTokenExpiration;
@Value("${jwt.refresh-token-expiration:604800000}") // 7天
private long refreshTokenExpiration;
private Key key;
@PostConstruct
public void init() {
byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
/**
* 生成Access Token
*/
public String generateAccessToken(Authentication authentication) {
return generateToken(authentication, accessTokenExpiration, "access");
}
/**
* 生成Refresh Token
*/
public String generateRefreshToken(Authentication authentication) {
return generateToken(authentication, refreshTokenExpiration, "refresh");
}
/**
* 生成Token
*/
private String generateToken(Authentication authentication, long expiration, String tokenType) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
Map<String, Object> claims = new HashMap<>();
claims.put("type", tokenType);
claims.put("authorities", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
/**
* 从Token获取用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
/**
* 验证Token
*/
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
logger.error("Invalid JWT signature");
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token");
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token");
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty");
}
return false;
}
/**
* 检查Token类型
*/
public boolean isRefreshToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return "refresh".equals(claims.get("type"));
}
}
JWT认证过滤器¶
/**
* JWT认证过滤器
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
try {
String token = extractTokenFromRequest(request);
if (token != null && tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
private String extractTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
认证入口点¶
/**
* JWT认证入口点(处理认证异常)
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Autowired
private ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
Map<String, Object> error = new HashMap<>();
error.put("status", 401);
error.put("error", "Unauthorized");
error.put("message", "认证失败,请重新登录");
error.put("path", request.getRequestURI());
error.put("timestamp", LocalDateTime.now().toString());
response.getWriter().write(objectMapper.writeValueAsString(error));
}
}
Security配置¶
/**
* JWT Security配置
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(Customizer.withDefaults())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(exception -> exception
.authenticationEntryPoint(authenticationEntryPoint)
)
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
认证控制器¶
/**
* JWT认证控制器
*/
@RestController
@RequestMapping("/api/auth")
public class JwtAuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserService userService;
@Autowired
private RefreshTokenService refreshTokenService;
/**
* 登录
*/
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String accessToken = tokenProvider.generateAccessToken(authentication);
String refreshToken = tokenProvider.generateRefreshToken(authentication);
// 保存Refresh Token到数据库
refreshTokenService.save(refreshToken, request.getUsername());
return ResponseEntity.ok(TokenResponse.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.tokenType("Bearer")
.expiresIn(3600)
.build());
}
/**
* 刷新Token
*/
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {
String refreshToken = request.getRefreshToken();
// 验证Refresh Token
if (!tokenProvider.validateToken(refreshToken) ||
!tokenProvider.isRefreshToken(refreshToken)) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Invalid refresh token"));
}
// 检查Refresh Token是否在数据库中
if (!refreshTokenService.exists(refreshToken)) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Refresh token not found"));
}
// 生成新的Access Token
String username = tokenProvider.getUsernameFromToken(refreshToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
String newAccessToken = tokenProvider.generateAccessToken(authentication);
return ResponseEntity.ok(TokenResponse.builder()
.accessToken(newAccessToken)
.refreshToken(refreshToken)
.tokenType("Bearer")
.expiresIn(3600)
.build());
}
/**
* 登出
*/
@PostMapping("/logout")
public ResponseEntity<?> logout(@RequestBody LogoutRequest request) {
// 删除Refresh Token
refreshTokenService.delete(request.getRefreshToken());
// 清除Security Context
SecurityContextHolder.clearContext();
return ResponseEntity.ok(Map.of("message", "Logout successful"));
}
}
DTO¶
/**
* Token响应
*/
@Data
@Builder
public class TokenResponse {
private String accessToken;
private String refreshToken;
private String tokenType;
private long expiresIn;
}
/**
* 刷新Token请求
*/
@Data
public class RefreshTokenRequest {
@NotBlank(message = "Refresh token不能为空")
private String refreshToken;
}
Refresh Token服务¶
/**
* Refresh Token服务
*/
@Service
@Transactional
public class RefreshTokenService {
@Autowired
private RefreshTokenRepository repository;
public void save(String token, String username) {
RefreshToken refreshToken = new RefreshToken();
refreshToken.setToken(token);
refreshToken.setUsername(username);
refreshToken.setExpiryDate(LocalDateTime.now().plusDays(7));
repository.save(refreshToken);
}
public boolean exists(String token) {
return repository.findByToken(token)
.filter(rt -> rt.getExpiryDate().isAfter(LocalDateTime.now()))
.isPresent();
}
public void delete(String token) {
repository.deleteByToken(token);
}
/**
* 清理过期Token
*/
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void cleanExpiredTokens() {
repository.deleteByExpiryDateBefore(LocalDateTime.now());
}
}
前端使用示例¶
/**
* API客户端(使用Axios)
*/
class ApiClient {
constructor() {
this.baseURL = 'http://localhost:8080/api';
this.accessToken = localStorage.getItem('accessToken');
this.refreshToken = localStorage.getItem('refreshToken');
}
/**
* 登录
*/
async login(username, password) {
const response = await fetch(`${this.baseURL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.accessToken;
this.refreshToken = data.refreshToken;
localStorage.setItem('accessToken', this.accessToken);
localStorage.setItem('refreshToken', this.refreshToken);
return data;
} else {
throw new Error('Login failed');
}
}
/**
* 刷新Token
*/
async refreshAccessToken() {
const response = await fetch(`${this.baseURL}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken: this.refreshToken })
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.accessToken;
localStorage.setItem('accessToken', this.accessToken);
return data.accessToken;
} else {
// Refresh token过期,重新登录
this.logout();
window.location.href = '/login';
throw new Error('Refresh token expired');
}
}
/**
* 调用API
*/
async call(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.accessToken}`,
...options.headers
};
let response = await fetch(url, {
...options,
headers
});
// 如果401,尝试刷新Token
if (response.status === 401) {
await this.refreshAccessToken();
// 重试请求
headers['Authorization'] = `Bearer ${this.accessToken}`;
response = await fetch(url, {
...options,
headers
});
}
return response;
}
/**
* 登出
*/
logout() {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
this.accessToken = null;
this.refreshToken = null;
}
}
// 使用示例
const client = new ApiClient();
// 登录
await client.login('user', 'password');
// 调用API
const response = await client.call('/users/me');
const userInfo = await response.json();
console.log(userInfo);
OAuth2授权码模式¶
完整的OAuth2实现见 06-SpringSecurity-OAuth2.md
微服务认证架构¶
架构图¶
┌──────────────┐
│ Gateway │ ──── JWT验证
└──────┬───────┘
│
┌───┴────┬────────┬────────┐
│ │ │ │
┌──▼──┐ ┌──▼──┐ ┌──▼──┐ ┌──▼──┐
│UserMS│ │OrderMS│ │Product│ │Payment│
└─────┘ └─────┘ └──────┘ └──────┘
API Gateway配置¶
/**
* Spring Cloud Gateway + JWT
*/
@Configuration
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf(csrf -> csrf.disable())
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/auth/**").permitAll()
.pathMatchers("/public/**").permitAll()
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders.fromIssuerLocation("http://auth-server:9000");
}
}
微服务配置¶
# 微服务application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
服务间调用¶
/**
* 服务间调用(使用Feign + JWT传递)
*/
@Component
public class JwtRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getCredentials() instanceof Jwt) {
Jwt jwt = (Jwt) authentication.getCredentials();
template.header("Authorization", "Bearer " + jwt.getTokenValue());
}
}
}
前后端分离认证方案¶
完整的前后端分离方案见 JWT Token实现 章节。
总结¶
本章提供了完整的代码示例:
- 基础认证:用户名密码、Session管理
- JWT认证:Token生成、验证、刷新
- OAuth2:授权服务器、资源服务器、客户端
- 微服务:网关认证、服务间调用
- 前后端分离:Token存储、自动刷新
源码地址(参考): - GitHub: spring-security-samples - Spring官方: Spring Security Reference
继续学习: - 上一章:Spring Security OAuth2集成 - 下一章:高级主题与最佳实践