本文目录导读:

我来为您提供一个完整的Java微服务安全实现案例,包含JWT认证、OAuth2授权、Spring Security配置等核心内容。
项目结构
microservice-security/
├── pom.xml
├── src/main/java/com/example/security/
│ ├── SecurityApplication.java
│ ├── config/
│ │ ├── SecurityConfig.java
│ │ └── RedisConfig.java
│ ├── controller/
│ │ ├── AuthController.java
│ │ └── UserController.java
│ ├── dto/
│ │ ├── LoginRequest.java
│ │ ├── LoginResponse.java
│ │ └── UserDTO.java
│ ├── filter/
│ │ └── JwtAuthenticationFilter.java
│ ├── model/
│ │ ├── User.java
│ │ └── Role.java
│ ├── repository/
│ │ └── UserRepository.java
│ ├── service/
│ │ ├── UserService.java
│ │ ├── JwtService.java
│ │ └── RedisService.java
│ └── util/
│ └── JwtUtil.java
Maven依赖配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<groupId>com.example</groupId>
<artifactId>microservice-security</artifactId>
<version>1.0.0</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
核心配置类
SecurityConfig.java
package com.example.security.config;
import com.example.security.filter.JwtAuthenticationFilter;
import com.example.security.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 公开端点
.antMatchers("/api/auth/login", "/api/auth/refresh", "/api/auth/logout").permitAll()
.antMatchers("/api/public/**", "/actuator/health").permitAll()
// 用户端点
.antMatchers("/api/user/**").hasRole("USER")
// 管理员端点
.antMatchers("/api/admin/**").hasRole("ADMIN")
// 其他请求需要认证
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(401);
response.getWriter().json("{\"error\": \"Unauthorized\"}");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(403);
response.getWriter().json("{\"error\": \"Access Denied\"}");
});
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
JwtAuthenticationFilter.java
package com.example.security.filter;
import com.example.security.service.JwtService;
import com.example.security.service.UserService;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String token = extractToken(request);
if (StringUtils.hasText(token) && jwtService.validateToken(token)) {
Claims claims = jwtService.extractClaims(token);
String username = claims.getSubject();
Integer userId = (Integer) claims.get("userId");
List<String> roles = (List<String>) claims.get("roles");
UserDetails userDetails = userService.loadUserByUsername(username);
if (userDetails != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
// 将用户ID放入请求中
request.setAttribute("userId", userId);
request.setAttribute("username", username);
}
}
} catch (Exception e) {
log.error("JWT认证失败", e);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
JWT服务类
JwtService.java
package com.example.security.service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.refresh-expiration}")
private Long refreshExpiration;
private Key signingKey;
@PostConstruct
public void init() {
byte[] keyBytes = secret.getBytes();
this.signingKey = Keys.hmacShaKeyFor(keyBytes);
}
public String generateToken(String username, Integer userId, List<String> roles) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("roles", roles);
return createToken(claims, username, expiration);
}
public String generateRefreshToken(String username, Integer userId) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
return createToken(claims, username, refreshExpiration);
}
private String createToken(Map<String, Object> claims, String subject, Long expirationTime) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationTime * 1000);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(signingKey, SignatureAlgorithm.HS256)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(signingKey)
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(signingKey)
.build()
.parseClaimsJws(token)
.getBody();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractClaims(token);
return claimsResolver.apply(claims);
}
public boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
}
用户实体与服务
User.java
package com.example.security.model;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import javax.persistence.*;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String email;
@Column(name = "full_name")
private String fullName;
@Column(nullable = false)
private Boolean enabled = true;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toList());
}
// getters, setters
}
Role.java
package com.example.security.model;
import lombok.Data;
import javax.persistence.*;
@Entity
@Table(name = "roles")
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String name;
@Column
private String description;
// getters, setters
}
UserRepository.java
package com.example.security.repository;
import com.example.security.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
UserService.java
package com.example.security.service;
import com.example.security.model.User;
import com.example.security.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
}
@Transactional
public User registerUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
@Transactional(readOnly = true)
public User getUserByUsername(String username) {
return userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
}
@Transactional(readOnly = true)
public boolean checkPassword(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
认证控制器
AuthController.java
package com.example.security.controller;
import com.example.security.dto.LoginRequest;
import com.example.security.dto.LoginResponse;
import com.example.security.dto.UserDTO;
import com.example.security.model.User;
import com.example.security.service.JwtService;
import com.example.security.service.RedisService;
import com.example.security.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserService userService;
private final JwtService jwtService;
private final RedisService redisService;
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
User user = userService.getUserByUsername(loginRequest.getUsername());
List<String> roles = user.getRoles().stream()
.map(role -> role.getName())
.collect(Collectors.toList());
String token = jwtService.generateToken(user.getUsername(), user.getId().intValue(), roles);
String refreshToken = jwtService.generateRefreshToken(user.getUsername(), user.getId().intValue());
// 存储token到Redis
redisService.saveToken(user.getUsername(), token);
redisService.saveRefreshToken(user.getUsername(), refreshToken);
LoginResponse response = LoginResponse.builder()
.accessToken(token)
.refreshToken(refreshToken)
.tokenType("Bearer")
.userId(user.getId())
.username(user.getUsername())
.roles(roles)
.expiresIn(3600)
.build();
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body("登录失败: " + e.getMessage());
}
}
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String refreshToken) {
try {
if (refreshToken.startsWith("Bearer ")) {
refreshToken = refreshToken.substring(7);
}
if (!jwtService.validateToken(refreshToken)) {
return ResponseEntity.badRequest().body("无效的刷新令牌");
}
String username = jwtService.extractUsername(refreshToken);
User user = userService.getUserByUsername(username);
// 验证Redis中的刷新令牌
String storedToken = redisService.getRefreshToken(username);
if (storedToken == null || !storedToken.equals(refreshToken)) {
return ResponseEntity.badRequest().body("刷新令牌已过期或无效");
}
List<String> roles = user.getRoles().stream()
.map(role -> role.getName())
.collect(Collectors.toList());
String newToken = jwtService.generateToken(username, user.getId().intValue(), roles);
String newRefreshToken = jwtService.generateRefreshToken(username, user.getId().intValue());
// 更新Redis中的令牌
redisService.saveToken(username, newToken);
redisService.saveRefreshToken(username, newRefreshToken);
LoginResponse response = LoginResponse.builder()
.accessToken(newToken)
.refreshToken(newRefreshToken)
.tokenType("Bearer")
.userId(user.getId())
.username(user.getUsername())
.roles(roles)
.expiresIn(3600)
.build();
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body("刷新令牌失败: " + e.getMessage());
}
}
@PostMapping("/logout")
public ResponseEntity<?> logout(@RequestHeader("Authorization") String token) {
try {
if (token.startsWith("Bearer ")) {
token = token.substring(7);
}
String username = jwtService.extractUsername(token);
redisService.removeToken(username);
redisService.removeRefreshToken(username);
return ResponseEntity.ok("注销成功");
} catch (Exception e) {
return ResponseEntity.badRequest().body("注销失败: " + e.getMessage());
}
}
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody UserDTO userDTO) {
try {
User user = new User();
user.setUsername(userDTO.getUsername());
user.setPassword(userDTO.getPassword());
user.setEmail(userDTO.getEmail());
user.setFullName(userDTO.getFullName());
User registeredUser = userService.registerUser(user);
return ResponseEntity.ok("注册成功");
} catch (Exception e) {
return ResponseEntity.badRequest().body("注册失败: " + e.getMessage());
}
}
}
DTO类
package com.example.security.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50)
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100)
private String password;
}
package com.example.security.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponse {
private String accessToken;
private String refreshToken;
private String tokenType;
private Long userId;
private String username;
private List<String> roles;
private long expiresIn;
}
package com.example.security.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50)
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100)
private String password;
@NotBlank(message = "邮箱不能为空")
@Email
private String email;
@Size(max = 100)
private String fullName;
}
Redis服务
package com.example.security.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, String> redisTemplate;
private static final String TOKEN_PREFIX = "token:";
private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
private static final long TOKEN_EXPIRATION = 3600; // 1 hour
private static final long REFRESH_TOKEN_EXPIRATION = 86400; // 24 hours
public void saveToken(String username, String token) {
redisTemplate.opsForValue().set(
TOKEN_PREFIX + username,
token,
TOKEN_EXPIRATION,
TimeUnit.SECONDS
);
}
public String getToken(String username) {
return redisTemplate.opsForValue().get(TOKEN_PREFIX + username);
}
public void removeToken(String username) {
redisTemplate.delete(TOKEN_PREFIX + username);
}
public void saveRefreshToken(String username, String refreshToken) {
redisTemplate.opsForValue().set(
REFRESH_TOKEN_PREFIX + username,
refreshToken,
REFRESH_TOKEN_EXPIRATION,
TimeUnit.SECONDS
);
}
public String getRefreshToken(String username) {
return redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + username);
}
public void removeRefreshToken(String username) {
redisTemplate.delete(REFRESH_TOKEN_PREFIX + username);
}
public boolean isTokenValid(String username, String token) {
String storedToken = getToken(username);
return token != null && token.equals(storedToken);
}
}
用户控制器示例
package com.example.security.controller;
import com.example.security.model.User;
import com.example.security.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/user/profile")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<?> getUserProfile() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
User user = userService.getUserByUsername(username);
Map<String, Object> response = new HashMap<>();
response.put("username", user.getUsername());
response.put("email", user.getEmail());
response.put("fullName", user.getFullName());
return ResponseEntity.ok(response);
}
@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> getAllUsers() {
// 管理员接口
return ResponseEntity.ok("管理员接口访问成功");
}
@GetMapping("/public/health")
public ResponseEntity<?> healthCheck() {
return ResponseEntity.ok("服务正常");
}
}
应用配置文件
# application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/microservice_security?useSSL=false&serverTimezone=UTC
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
redis:
host: localhost
port: 6379
timeout: 5000ms
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
jwt:
secret: your-secret-key-must-be-at-least-256-bits-long-please-change-in-production
expiration: 3600
refresh-expiration: 86400
logging:
level:
com.example.security: DEBUG
org.springframework.security: WARN
org.hibernate: ERROR
主应用类
package com.example.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
数据库初始化脚本
-- 创建初始用户和角色 INSERT INTO roles (id, name, description) VALUES (1, 'ADMIN', '管理员'); INSERT INTO roles (id, name, description) VALUES (2, 'USER', '普通用户'); -- 创建用户 (密码是 'admin123' 的BCrypt加密) INSERT INTO users (id, username, password, email, full_name, enabled) VALUES (1, 'admin', '$2a$10$QVE8BMuBRXkDrCE3bvZ4eO2Q5ZkJASlmLtnLST0JVCQp5xkyJ1vCy', 'admin@example.com', '管理员', TRUE); INSERT INTO users (id, username, password, email, full_name, enabled) VALUES (2, 'user', '$2a$10$qVTb9SFD7lXq/oiyD/xFc.T2hIHPbQPGaFJdBVK3JEDWBoRsQ6Z9q', 'user@example.com', '普通用户', TRUE); -- 关联用户和角色 INSERT INTO user_roles (user_id, role_id) VALUES (1, 1); INSERT INTO user_roles (user_id, role_id) VALUES (1, 2); INSERT INTO user_roles (user_id, role_id) VALUES (2, 2);
测试用例
@SpringBootTest
@AutoConfigureMockMvc
class SecurityIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void testLoginSuccess() throws Exception {
LoginRequest loginRequest = new LoginRequest("user", "user123");
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginRequest)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.accessToken").exists())
.andExpect(jsonPath("$.tokenType").value("Bearer"));
}
@Test
void testLoginFailed() throws Exception {
LoginRequest loginRequest = new LoginRequest("user", "wrongpassword");
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginRequest)))
.andExpect(status().is4xxClientError());
}
@Test
void testProtectedEndpoint() throws Exception {
// 先获取token
LoginRequest loginRequest = new LoginRequest("user", "user123");
String response = mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginRequest)))
.andReturn()
.getResponse()
.getContentAsString();
LoginResponse loginResponse = objectMapper.readValue(response, LoginResponse.class);
// 访问受保护的接口
mockMvc.perform(get("/api/user/profile")
.header("Authorization", "Bearer " + loginResponse.getAccessToken()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("user"));
}
}
使用说明
-
环境要求:JDK 11+, Maven 3.6+, MySQL 8.0+, Redis 6.0+
-
启动步骤:
- 创建MySQL数据库和Redis服务
- 修改
application.yml中的数据库配置 - 运行数据库初始化脚本
- 启动Spring Boot应用
-
主要功能:
- JWT令牌认证
- 基于角色的访问控制
- Token刷新机制
- Redis存储令牌实现登出
- 密码加密存储
- CORS跨域配置
-
安全特性:
- 无状态认证机制
- Token自动过期
- Redis分布式会话管理
- BCrypt密码加密
- 角色权限控制
这个案例提供了完整的微服务安全解决方案,涵盖了认证、授权、令牌管理等核心功能,可以根据实际需求进行扩展和定制。