大家好,我是小悟。
一、安全认证与授权概述
1.1 基本概念
认证(Authentication):验证用户身份的过程,确认"你是谁"。
- 身份标识:用户名、邮箱、手机号等
- 凭证验证:密码、验证码、证书、生物特征等
- 多因素认证:组合多种验证方式增强安全性
授权(Authorization):确定用户权限的过程,确认"你能做什么"。
- 角色基础:基于用户角色分配权限
- 权限粒度:细粒度到具体操作(增删改查)
- 资源访问:控制对特定资源的访问
1.2 核心安全原则
- 最小权限原则:用户只拥有完成工作所需的最小权限
- 纵深防御:多层次的安全防护机制
- 失败安全:认证失败时保持安全状态
- 完整分离:认证和授权逻辑与业务逻辑分离
- 会话管理:安全的会话创建和维护机制
1.3 常见认证方式
- Session认证:传统的服务端会话管理
- JWT认证:无状态、跨域友好的令牌机制
- OAuth2.0:第三方授权标准
- SSO:单点登录解决方案
二、详细实现步骤
2.1 环境准备
pom.xml依赖配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
application.yml配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/security_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
# JWT配置
jwt:
secret: mySecretKeyForJWTGenerationAndValidation2024
expiration: 86400000 # 24小时(毫秒)
header: Authorization
token-prefix: "Bearer "
server:
port: 8080
servlet:
context-path: /api
2.2 数据模型设计
用户实体类:
package com.example.security.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Data
@Entity
@Table(name = "users")
public class User implements UserDetails {
@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)
private String email;
private String phone;
private String fullName;
private Boolean enabled = true;
private Boolean accountNonExpired = true;
private Boolean accountNonLocked = true;
private Boolean credentialsNonExpired = true;
@Temporal(TemporalType.TIMESTAMP)
private Date lastLoginTime;
@Temporal(TemporalType.TIMESTAMP)
private Date createTime = new Date();
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.flatMap(role -> role.getPermissions().stream())
.map(permission -> new SimpleGrantedAuthority(permission.getName()))
.collect(Collectors.toList());
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
角色实体类:
package com.example.security.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.*;
import java.util.List;
@Data
@Entity
@Table(name = "roles")
public class Role implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name; // ROLE_ADMIN, ROLE_USER
private String description;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "role_permissions",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
private List<Permission> permissions;
@Override
public String getAuthority() {
return name;
}
}
权限实体类:
package com.example.security.entity;
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "permissions")
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name; // USER_CREATE, USER_READ, USER_UPDATE, USER_DELETE
private String description;
}
2.3 JWT工具类
package com.example.security.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Slf4j
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 从token中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从token中获取指定claim
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 获取token中的所有claim
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 检查token是否过期
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("authorities", userDetails.getAuthorities());
return doGenerateToken(claims, userDetails.getUsername());
}
/**
* 生成token的具体实现
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = new Date();
final Date expirationDate = new Date(createdDate.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 验证token是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* 刷新token
*/
public String refreshToken(String token) {
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(new Date());
return doGenerateToken(claims, claims.getSubject());
}
}
2.4 认证服务实现
UserDetailsService实现类:
package com.example.security.service;
import com.example.security.entity.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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
return user;
}
}
认证服务:
package com.example.security.service;
import com.example.security.dto.LoginRequest;
import com.example.security.dto.LoginResponse;
import com.example.security.entity.User;
import com.example.security.repository.UserRepository;
import com.example.security.util.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final AuthenticationManager authenticationManager;
private final UserDetailsServiceImpl userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
/**
* 用户登录
*/
public LoginResponse login(LoginRequest loginRequest) {
// 1. 认证用户
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
// 2. 设置认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 3. 获取用户详情
User user = (User) authentication.getPrincipal();
// 4. 更新最后登录时间
user.setLastLoginTime(new Date());
userRepository.save(user);
// 5. 生成token
String token = jwtTokenUtil.generateToken(user);
// 6. 构建响应
return LoginResponse.builder()
.token(token)
.username(user.getUsername())
.email(user.getEmail())
.fullName(user.getFullName())
.roles(user.getRoles().stream()
.map(role -> role.getName())
.collect(Collectors.toList()))
.build();
}
/**
* 用户注册
*/
public User register(User user) {
// 检查用户名是否存在
if (userRepository.findByUsername(user.getUsername()).isPresent()) {
throw new RuntimeException("用户名已存在");
}
// 加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
// 保存用户
return userRepository.save(user);
}
/**
* 刷新token
*/
public String refreshToken(String oldToken) {
String token = oldToken.replace("Bearer ", "");
String username = jwtTokenUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
return jwtTokenUtil.refreshToken(token);
}
throw new RuntimeException("无效的token");
}
}
2.5 JWT认证过滤器
package com.example.security.filter;
import com.example.security.service.UserDetailsServiceImpl;
import com.example.security.util.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsServiceImpl userDetailsService;
@Value("${jwt.header}")
private String header;
@Value("${jwt.token-prefix}")
private String tokenPrefix;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
// 1. 从请求中获取token
String jwt = getJwtFromRequest(request);
// 2. 验证token并设置认证信息
if (StringUtils.hasText(jwt)) {
String username = jwtTokenUtil.getUsernameFromToken(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("设置用户认证信息: {}", username);
}
}
}
} catch (Exception e) {
log.error("认证处理失败: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
/**
* 从请求头中获取token
*/
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader(header);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(tokenPrefix)) {
return bearerToken.substring(tokenPrefix.length());
}
return null;
}
}
2.6 安全配置类
package com.example.security.config;
import com.example.security.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true, // 启用@PreAuthorize注解
securedEnabled = true, // 启用@Secured注解
jsr250Enabled = true // 启用@RolesAllowed注解
)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final JwtAuthenticationEntryPoint unauthorizedHandler;
private final JwtAccessDeniedHandler accessDeniedHandler;
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 认证管理器
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 认证配置
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
/**
* 安全配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 禁用CSRF
.csrf().disable()
// 异常处理
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(accessDeniedHandler)
.and()
// 基于token,不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 请求授权配置
.authorizeRequests()
// 公开接口
.antMatchers("/auth/**").permitAll()
.antMatchers("/public/**").permitAll()
.antMatchers("/swagger-ui/**").permitAll()
.antMatchers("/v3/api-docs/**").permitAll()
// 静态资源
.antMatchers("/css/**", "/js/**", "/images/**").permitAll()
// 其他接口需要认证
.anyRequest().authenticated()
.and()
// 添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// 允许同源iframe加载
http.headers().frameOptions().sameOrigin();
}
}
2.7 异常处理
认证入口点:
package com.example.security.handler;
import com.example.security.dto.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
log.error("认证失败: {}", authException.getMessage());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
ApiResponse<?> apiResponse = ApiResponse.error(
HttpStatus.UNAUTHORIZED.value(),
"认证失败: " + authException.getMessage()
);
response.getWriter().write(objectMapper.writeValueAsString(apiResponse));
}
}
访问拒绝处理器:
package com.example.security.handler;
import com.example.security.dto.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
private final ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
log.error("访问拒绝: {}", accessDeniedException.getMessage());
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
ApiResponse<?> apiResponse = ApiResponse.error(
HttpStatus.FORBIDDEN.value(),
"没有权限访问: " + accessDeniedException.getMessage()
);
response.getWriter().write(objectMapper.writeValueAsString(apiResponse));
}
}
2.8 控制器实现
认证控制器:
package com.example.security.controller;
import com.example.security.dto.LoginRequest;
import com.example.security.dto.LoginResponse;
import com.example.security.entity.User;
import com.example.security.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest loginRequest) {
return ResponseEntity.ok(authService.login(loginRequest));
}
@PostMapping("/register")
public ResponseEntity<User> register(@Valid @RequestBody User user) {
return ResponseEntity.ok(authService.register(user));
}
@PostMapping("/refresh-token")
public ResponseEntity<String> refreshToken(@RequestHeader("Authorization") String token) {
return ResponseEntity.ok(authService.refreshToken(token));
}
@PostMapping("/logout")
public ResponseEntity<ApiResponse<Void>> logout(HttpServletRequest request) {
// 客户端需要清除token
return ResponseEntity.ok(ApiResponse.success("退出成功"));
}
}
用户控制器(演示权限控制):
package com.example.security.controller;
import com.example.security.entity.User;
import com.example.security.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/me")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<User> getCurrentUser(@AuthenticationPrincipal User user) {
return ResponseEntity.ok(user);
}
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userService.findAll());
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('USER_READ')")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@PostMapping
@PreAuthorize("hasAuthority('USER_CREATE')")
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.create(user));
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('USER_UPDATE')")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return ResponseEntity.ok(userService.update(id, user));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('USER_DELETE')")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.ok().build();
}
@PostMapping("/{userId}/roles/{roleId}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<User> assignRole(@PathVariable Long userId, @PathVariable Long roleId) {
return ResponseEntity.ok(userService.assignRole(userId, roleId));
}
}
2.9 DTO类
// 登录请求
package com.example.security.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
// 登录响应
package com.example.security.dto;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class LoginResponse {
private String token;
private String username;
private String email;
private String fullName;
private List<String> roles;
}
// 通用响应
package com.example.security.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp = System.currentTimeMillis();
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "成功", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> success(String message) {
return new ApiResponse<>(200, message, null, System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null, System.currentTimeMillis());
}
}
三、数据初始化
package com.example.security.config;
import com.example.security.entity.Permission;
import com.example.security.entity.Role;
import com.example.security.entity.User;
import com.example.security.repository.PermissionRepository;
import com.example.security.repository.RoleRepository;
import com.example.security.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {
private final PermissionRepository permissionRepository;
private final RoleRepository roleRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Override
public void run(String... args) {
// 1. 创建权限
Permission userCreate = createPermission("USER_CREATE", "创建用户");
Permission userRead = createPermission("USER_READ", "查看用户");
Permission userUpdate = createPermission("USER_UPDATE", "更新用户");
Permission userDelete = createPermission("USER_DELETE", "删除用户");
// 2. 创建角色
Role adminRole = createRole("ROLE_ADMIN", "管理员",
Arrays.asList(userCreate, userRead, userUpdate, userDelete));
Role userRole = createRole("ROLE_USER", "普通用户",
Arrays.asList(userRead));
// 3. 创建用户
createUser("admin", "admin123", "admin@example.com", "管理员",
Arrays.asList(adminRole));
createUser("user", "user123", "user@example.com", "普通用户",
Arrays.asList(userRole));
}
private Permission createPermission(String name, String description) {
return permissionRepository.findByName(name)
.orElseGet(() -> {
Permission permission = new Permission();
permission.setName(name);
permission.setDescription(description);
return permissionRepository.save(permission);
});
}
private Role createRole(String name, String description, List<Permission> permissions) {
return roleRepository.findByName(name)
.orElseGet(() -> {
Role role = new Role();
role.setName(name);
role.setDescription(description);
role.setPermissions(permissions);
return roleRepository.save(role);
});
}
private void createUser(String username, String password, String email,
String fullName, List<Role> roles) {
if (!userRepository.findByUsername(username).isPresent()) {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
user.setEmail(email);
user.setFullName(fullName);
user.setRoles(roles);
userRepository.save(user);
}
}
}
四、总结
4.1 核心概念回顾
认证(Authentication):
- 身份验证:确认用户身份的真实性
- 凭证管理:安全存储和验证用户凭证
- 会话控制:管理用户登录状态
授权(Authorization):
- 访问控制:基于角色和权限控制资源访问
- 权限粒度:支持方法级别、URL级别的权限控制
- 动态权限:可根据业务需求动态调整权限
4.2 技术实现要点
- Spring Security核心组件:
SecurityFilterChain:安全过滤器链AuthenticationManager:认证管理器UserDetailsService:用户详情服务PasswordEncoder:密码编码器
- JWT实现机制:
- 无状态认证:服务器不存储会话信息
- 自包含令牌:包含用户信息和权限
- 签名验证:确保令牌完整性
- 权限控制层次:
- URL级别:通过HttpSecurity配置
- 方法级别:通过注解控制
- 数据级别:在Service层实现
4.3 安全最佳实践
- 密码安全:
- 使用BCrypt强哈希算法
- 密码复杂度校验
- 防止暴力破解
- Token安全:
- 设置合理的过期时间
- 使用HTTPS传输
- Token刷新机制
- 会话管理:
- 限制并发登录
- 会话固定保护
- 登出清理机制
4.4 性能优化建议
- 缓存策略:
- 缓存用户权限信息
- 使用Redis存储token黑名单
- 数据库优化:
- 合理设计权限表结构
- 使用索引提升查询效率
- 过滤器优化:
- 只对需要认证的接口进行过滤
- 避免重复验证
4.5 扩展性考虑
- 支持多种认证方式:
- 短信验证码登录
- 第三方登录(OAuth2)
- 二维码登录
- 细粒度权限控制:
- 基于资源的权限(如文档所有者)
- 动态权限配置
- 数据级权限
- 审计日志:
- 记录敏感操作
- 登录失败监控
- 权限变更追踪
4.6 常见问题与解决方案
- 跨域问题:配置CORS策略
- 并发登录:使用Token管理限制
- 权限缓存:及时更新用户权限
- Token泄露:实现Token黑名单机制
这个Spring Boot安全认证与授权实现提供了一个企业级的解决方案,涵盖了从基础概念到具体实现的各个方面。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海