集成spring security和jwt验证

master
daixiaoyi 4 weeks ago
parent 8f94a6022f
commit fe32142027

@ -78,22 +78,4 @@
<version>2.15.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,73 @@
package com.supervision.ai.service.hub.config;
import com.supervision.ai.service.hub.filter.JwtAuthenticationFilter;
import com.supervision.ai.service.hub.service.impl.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractHttpConfigurer;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private SysUserService sysUserService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // 禁用CSRF
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
// 👇 正确禁用方式(推荐)
.formLogin(Customizer.withDefaults()) // 启用默认配置
.httpBasic(Customizer.withDefaults()); // 启用默认配置
return http.build();
}
@Bean
public AuthenticationProvider authenticationProvider() {
// 使用DaoAuthenticationProvider并注入自定义的UserDetailsService和PasswordEncoder
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(sysUserService); // 从数据库读取用户进行认证
authProvider.setPasswordEncoder(passwordEncoder()); // 使用BCrypt密码器验证密码
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
// 从AuthenticationConfiguration中获取AuthenticationManager实例
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 使用BCryptPasswordEncoder作为密码加密器
return new BCryptPasswordEncoder();
}
}

@ -0,0 +1,6 @@
package com.supervision.ai.service.hub.constant;
public class UserConstant {
public static final String USER_STATUS_ENABLED = "1"; // 用户状态启用
public static final String USER_STATUS_DISABLED = "0"; // 用户状态禁用
}

@ -0,0 +1,37 @@
package com.supervision.ai.service.hub.controller;
import com.supervision.ai.service.hub.service.impl.AuthService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
String token = authService.login(request.getUsername(), request.getPassword());
Map<String, String> response = new HashMap<>();
response.put("token", token);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
}
@Data
public static class LoginRequest {
private String username;
private String password;
}
}

@ -0,0 +1,39 @@
package com.supervision.ai.service.hub.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.supervision.ai.service.hub.domain.SysUser;
import com.supervision.ai.service.hub.service.impl.SysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
*/
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class SysUserController {
private final SysUserService sysUserService;
/**
*
*
* @return 404
*/
@GetMapping("/me")
public ResponseEntity<?> getCurrentUserDetails() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
SysUser user = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
if (user == null) {
return ResponseEntity.status(404).body("用户不存在");
}
return ResponseEntity.ok(user);
}
}

@ -0,0 +1,16 @@
package com.supervision.ai.service.hub.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
@Data
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.AUTO)
private Long id; // 主键ID
private String username; // 用户名
private String password; // 密码 (加密存储)
private String status; // 状态 (1表示正常0表示禁用)
}

@ -0,0 +1,62 @@
package com.supervision.ai.service.hub.filter;
import com.supervision.ai.service.hub.service.impl.SysUserService;
import com.supervision.ai.service.hub.util.JwtUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
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.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final SysUserService sysUserService;
@Override
protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
// 提取JWT Token去掉前缀"Bearer "
String token = authHeader.substring(7);
String username;
try {
// 从JWT中解析用户名
username = jwtUtils.getUsernameFromToken(token);
} catch (Exception e) {
// 如果JWT格式不正确或过期直接放行后续的过滤器会处理认证失败
filterChain.doFilter(request, response);
return;
}
// 如果成功提取到用户名,并且当前没有已认证的用户
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 根据用户名从数据库加载用户信息
UserDetails userDetails = sysUserService.loadUserByUsername(username);
// 验证Token的有效性是否未过期用户名是否匹配
if (jwtUtils.validateToken(token, userDetails)) {
// 将用户信息封装到Authentication对象中标记为已认证
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将Authentication对象放入SecurityContext表示当前请求已通过认证
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
}
filterChain.doFilter(request, response);
}
}

@ -0,0 +1,10 @@
package com.supervision.ai.service.hub.mapper;
import com.supervision.ai.service.hub.domain.SysUser;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
// 继承BaseMapper后CRUD方法可直接使用无需额外定义
}

@ -0,0 +1,36 @@
package com.supervision.ai.service.hub.service.impl;
import com.supervision.ai.service.hub.util.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;
/**
* JWT Token
*/
public String login(String username, String password) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return jwtUtils.generateToken(userDetails);
} catch (BadCredentialsException e) {
throw new RuntimeException("用户名或密码错误");
} catch (DisabledException e) {
throw new RuntimeException("用户已被禁用");
}
}
}

@ -0,0 +1,48 @@
package com.supervision.ai.service.hub.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.ai.service.hub.domain.SysUser;
import com.supervision.ai.service.hub.mapper.SysUserMapper;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 java.util.Collections;
import java.util.List;
import static com.supervision.ai.service.hub.constant.UserConstant.USER_STATUS_DISABLED;
import static com.supervision.ai.service.hub.constant.UserConstant.USER_STATUS_ENABLED;
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> implements UserDetailsService {
/**
*
* Spring Security
*
* @param username
* @return UserDetails
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = this.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
if (USER_STATUS_DISABLED.equals(user.getStatus())) {
throw new DisabledException("用户已被禁用");
}
// 将查询到的用户信息组装成UserDetails对象
// **扩展点**:如需加载用户角色权限,可在此处查询 sys_user_role 表关联的角色,并将角色加入 authorities 列表
List<GrantedAuthority> authorities = Collections.emptyList();
// 使用Spring Security提供的User对象作为UserDetails返回
return new User(user.getUsername(), user.getPassword(), authorities);
}
}

@ -0,0 +1,71 @@
package com.supervision.ai.service.hub.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String Secret;
@Value("${jwt.expiration}")
private long Expiration; // 1天
private SecretKey secretKey;
@PostConstruct
public void initKey() {
this.secretKey = Keys.hmacShaKeyFor(Base64.getDecoder().decode(Secret));
}
// 生成Token
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + Expiration))
.signWith(secretKey, Jwts.SIG.HS256)
.compact();
}
// 从 Token 中解析用户名
public String getUsernameFromToken(String token) {
Jws<Claims> jws = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
return jws.getPayload().getSubject();
}
// 校验Token是否有效
public boolean validateToken(String token, UserDetails userDetails) {
try {
String username = getUsernameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
} catch (Exception e) {
return false;
}
}
// 判断是否过期
private boolean isTokenExpired(String token) {
Jws<Claims> jws = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
return jws.getPayload().getExpiration().before(new Date());
}
}

@ -17,6 +17,10 @@ mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
jwt:
secret: "DlHaPUePiN6MyvpMpsMq/t6swzMHqtrRFd2YnofKz4k=" # JWT密钥 使用官方推荐方式生成 Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded());
expiration: 3600000 # 1小时3600000 1天86400000
heygem:
server:
ip: 192.168.10.96

Loading…
Cancel
Save