本文目录导读:

我来为您提供一个完整的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密码加密
- 角色权限控制
这个案例提供了完整的微服务安全解决方案,涵盖了认证、授权、令牌管理等核心功能,可以根据实际需求进行扩展和定制。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播