feat:
- 完善Spring Security
This commit is contained in:
parent
717395f8b3
commit
23f65a4e5d
@ -1,31 +0,0 @@
|
||||
package asia.yulinling.workflow.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* JWT配置类
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/11
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "jwt.config")
|
||||
@Data
|
||||
public class JwtConfig {
|
||||
/**
|
||||
* jwt 加密 key,默认值:kw.
|
||||
*/
|
||||
private String key = "kw";
|
||||
|
||||
/**
|
||||
* jwt 过期时间,默认值:600000 {@code 10 分钟}.
|
||||
*/
|
||||
private Long ttl = 600000L;
|
||||
|
||||
/**
|
||||
* 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天}
|
||||
*/
|
||||
private Long remember = 604800000L;
|
||||
}
|
||||
@ -1,16 +1,12 @@
|
||||
package asia.yulinling.workflow.config;
|
||||
|
||||
|
||||
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
|
||||
import asia.yulinling.workflow.security.AuthEntryPoint;
|
||||
import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint;
|
||||
import asia.yulinling.workflow.security.JwtAuthenticationFilter;
|
||||
import asia.yulinling.workflow.service.CustomerDetailsService;
|
||||
import asia.yulinling.workflow.security.JwtUserDetailsService;
|
||||
import asia.yulinling.workflow.utils.JwtUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@ -18,16 +14,10 @@ import org.springframework.security.config.annotation.authentication.configurati
|
||||
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.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@ -35,14 +25,14 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class SecurityConfig {
|
||||
|
||||
private final CustomerDetailsService customerDetailService;
|
||||
private final JwtUserDetailsService jwtUserDetailsService;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, AuthEntryPoint authEntryPoint, RequestMatcher requestMatcher) throws Exception {
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) throws Exception {
|
||||
http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(authEntryPoint)
|
||||
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
|
||||
.accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"))
|
||||
)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
@ -52,9 +42,10 @@ public class SecurityConfig {
|
||||
)
|
||||
.sessionManagement(AbstractHttpConfigurer::disable)
|
||||
.addFilterBefore(
|
||||
new JwtAuthenticationFilter(jwtUtil, authEntryPoint, requestMatcher),
|
||||
new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService),
|
||||
UsernamePasswordAuthenticationFilter.class
|
||||
);
|
||||
)
|
||||
.userDetailsService(jwtUserDetailsService);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
@ -67,29 +58,6 @@ public class SecurityConfig {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean RequestMatcher requestMatcher() {
|
||||
return new AntPathRequestMatcher("/login", "POST");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DaoAuthenticationProvider authenticationProvider() {
|
||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||
authProvider.setUserDetailsService(customerDetailService);
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
|
||||
UserPrincipal user = new UserPrincipal(
|
||||
1L,
|
||||
"admin",
|
||||
passwordEncoder.encode("123456"),
|
||||
List.of("ADMIN"),
|
||||
List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))
|
||||
);
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
||||
return configuration.getAuthenticationManager();
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package asia.yulinling.workflow.controller;
|
||||
|
||||
import asia.yulinling.workflow.dto.request.LoginRequest;
|
||||
import asia.yulinling.workflow.service.AuthService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 登录控制层
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/13
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authenticate;
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
|
||||
String token = authenticate.login(loginRequest);
|
||||
return ResponseEntity.ok(token);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
package asia.yulinling.workflow.controller;
|
||||
|
||||
import asia.yulinling.workflow.dto.request.LoginRequest;
|
||||
import asia.yulinling.workflow.utils.JwtUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
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.AuthenticationException;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 登录控制层
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/13
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LoginController {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
|
||||
try {
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
|
||||
|
||||
Authentication authentication = authenticationManager.authenticate(authToken);
|
||||
|
||||
log.info("{}", authentication.getPrincipal());
|
||||
String token = jwtUtil.generateToken(authentication, false); // 可根据需要传 true/false
|
||||
|
||||
return ResponseEntity.ok(token);
|
||||
} catch (AuthenticationException ex) {
|
||||
log.info("{}", ex.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名或密码错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
package asia.yulinling.workflow.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* JWT响应体
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/15
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class JWTAuthResponse {
|
||||
private String accessToken;
|
||||
private String tokenType = "Bearer";
|
||||
}
|
||||
@ -17,7 +17,7 @@ import java.io.IOException;
|
||||
* @since 2025/6/13
|
||||
*/
|
||||
@Component
|
||||
public class AuthEntryPoint implements AuthenticationEntryPoint {
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
@ -7,11 +7,12 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -28,39 +29,40 @@ import java.io.IOException;
|
||||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
private final JwtUtil jwtUtil;
|
||||
private final AuthenticationEntryPoint authenticationEntryPoint;
|
||||
private final RequestMatcher requestMatcher;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain
|
||||
)
|
||||
throws ServletException, IOException {
|
||||
log.info("{}", request.getRequestURI());
|
||||
if (requestMatcher.matches(request)) {
|
||||
log.info("{}", request.getRequestURI());
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String header = request.getHeader("Authorization");
|
||||
if (header == null || !header.startsWith("Bearer ")) {
|
||||
String token = null;
|
||||
if (header != null) {
|
||||
token = header.substring(7);
|
||||
}
|
||||
if (jwtUtil.validateToken(token)) {
|
||||
String username = jwtUtil.parseToken(token).getSubject();
|
||||
String roles = jwtUtil.parseToken(token).get("roles").toString();
|
||||
Authentication auth = new JwtAuthenticationToken(username, roles);
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
} else {
|
||||
authenticationEntryPoint.commence(request, response, new AuthenticationException("") {
|
||||
});
|
||||
return;
|
||||
}
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String token = getTokenFromRequest(request);
|
||||
log.info("token: {}", token);
|
||||
|
||||
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
|
||||
// get username from token
|
||||
String username = jwtUtil.parseToken(token).getSubject();
|
||||
|
||||
// load the user associated with token
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
UsernamePasswordAuthenticationToken authenticationToken
|
||||
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
package asia.yulinling.workflow.security;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* JWT认证
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/13
|
||||
*/
|
||||
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private final String principal;
|
||||
private final String role;
|
||||
|
||||
public JwtAuthenticationToken(String username, String role) {
|
||||
super(Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role)));
|
||||
this.principal = username;
|
||||
this.role = role;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,15 @@
|
||||
package asia.yulinling.workflow.service;
|
||||
package asia.yulinling.workflow.security;
|
||||
|
||||
import asia.yulinling.workflow.mapper.PermissionMapper;
|
||||
import asia.yulinling.workflow.mapper.RoleMapper;
|
||||
import asia.yulinling.workflow.mapper.UserMapper;
|
||||
import asia.yulinling.workflow.model.entity.Permission;
|
||||
import asia.yulinling.workflow.model.entity.Role;
|
||||
import asia.yulinling.workflow.model.entity.User;
|
||||
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
@ -18,6 +17,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -31,10 +31,9 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CustomerDetailsService implements UserDetailsService {
|
||||
public class JwtUserDetailsService implements UserDetailsService {
|
||||
private final UserMapper userMapper;
|
||||
private final RoleMapper roleMapper;
|
||||
private final PermissionMapper permissionMapper;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
@ -47,9 +46,15 @@ public class CustomerDetailsService implements UserDetailsService {
|
||||
|
||||
User user = userMapper.selectOne(queryWrapper);
|
||||
List<Role> roles = roleMapper.selectByIds(Collections.singleton(user.getId()));
|
||||
List<Long> roleIds = roles.stream().map(Role::getId).collect(Collectors.toList());
|
||||
List<Permission> permissions = permissionMapper.selectByIds(roleIds);
|
||||
return UserPrincipal.create(user, roles, permissions);
|
||||
|
||||
Set<GrantedAuthority> authorities = roles.stream()
|
||||
.map((role) -> new SimpleGrantedAuthority(role.getName()))
|
||||
.collect(Collectors.toSet());
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
username,
|
||||
user.getPassword(),
|
||||
authorities
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package asia.yulinling.workflow.service;
|
||||
|
||||
import asia.yulinling.workflow.dto.request.LoginRequest;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 账号权限类
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/14
|
||||
*/
|
||||
public interface AuthService {
|
||||
String login(LoginRequest loginRequest);
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package asia.yulinling.workflow.service.impl;
|
||||
|
||||
import asia.yulinling.workflow.dto.request.LoginRequest;
|
||||
import asia.yulinling.workflow.service.AuthService;
|
||||
import asia.yulinling.workflow.utils.JwtUtil;
|
||||
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.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 账号权限类实现
|
||||
* </p>
|
||||
*
|
||||
* @author YLL
|
||||
* @since 2025/6/14
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
@Override
|
||||
public String login(LoginRequest loginRequest) {
|
||||
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
||||
loginRequest.getUsername(), loginRequest.getPassword()
|
||||
));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
return jwtUtil.generateToken(authentication, false);
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,16 @@
|
||||
package asia.yulinling.workflow.utils;
|
||||
|
||||
import asia.yulinling.workflow.config.JwtConfig;
|
||||
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import io.jsonwebtoken.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -25,47 +20,27 @@ import java.util.List;
|
||||
* @author YLL
|
||||
* @since 2025/6/11
|
||||
*/
|
||||
@EnableConfigurationProperties(JwtConfig.class)
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtUtil {
|
||||
private final JwtConfig jwtConfig;
|
||||
/**
|
||||
* jwt 加密 key,默认值:kw.
|
||||
*/
|
||||
@Value("${jwt.config.key}")
|
||||
private String key = "daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb";
|
||||
|
||||
/**
|
||||
* 创建JWT
|
||||
*
|
||||
* @param isRememberMe 记住我
|
||||
* @param userId 用户id
|
||||
* @param subject 用户名
|
||||
* @param roles 用户角色
|
||||
* @param authorities 用户权限
|
||||
* @return JWT
|
||||
* jwt 过期时间,默认值:600000 {@code 10 分钟}.
|
||||
*/
|
||||
public String generateToken(boolean isRememberMe, Long userId, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) {
|
||||
// 1. 构建签名密钥
|
||||
Key key = getSigningKey();
|
||||
@Value("${jwt.config.ttl}")
|
||||
private Long ttl = 600000L;
|
||||
|
||||
// 2. 当前时间
|
||||
Date now = new Date();
|
||||
|
||||
// 3. 计算过期时间
|
||||
long ttl = isRememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();
|
||||
Date expiration = new Date(now.getTime() + ttl);
|
||||
|
||||
// 4. 构建 JWT
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.setId(String.valueOf(userId))
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.claim("roles", roles)
|
||||
.claim("authorities", authorities)
|
||||
.signWith(key);
|
||||
|
||||
// 5. 返回生成的 token 字符串
|
||||
return builder.compact();
|
||||
}
|
||||
/**
|
||||
* 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天}
|
||||
*/
|
||||
@Value("${jwt.config.remember}")
|
||||
private Long remember = 604800000L;
|
||||
|
||||
/**
|
||||
* 创建JWT
|
||||
@ -74,15 +49,27 @@ public class JwtUtil {
|
||||
* @param isRememberMe 记住我
|
||||
* @return JWT
|
||||
*/
|
||||
public String generateToken(Authentication authentication, Boolean isRememberMe) {
|
||||
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
|
||||
return generateToken(
|
||||
isRememberMe,
|
||||
userPrincipal.getId(),
|
||||
userPrincipal.getUsername(),
|
||||
userPrincipal.getRoles(),
|
||||
userPrincipal.getAuthorities()
|
||||
);
|
||||
public String generateToken(Authentication authentication, boolean isRememberMe) {
|
||||
// 1. 构建签名密钥
|
||||
Key key = key();
|
||||
|
||||
// 2. 当前时间
|
||||
Date now = new Date();
|
||||
|
||||
// 3. 计算过期时间
|
||||
long ttl = isRememberMe ? this.remember : this.ttl;
|
||||
Date expiration = new Date(now.getTime() + ttl);
|
||||
|
||||
// 4. 构建 JWT
|
||||
String username = authentication.getName();
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.setSubject(username)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.signWith(key);
|
||||
|
||||
// 5. 返回生成的 token 字符串
|
||||
return builder.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,7 +80,7 @@ public class JwtUtil {
|
||||
*/
|
||||
public Claims parseToken(String token) {
|
||||
try {
|
||||
Key key = getSigningKey();
|
||||
Key key = key();
|
||||
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
@ -120,7 +107,7 @@ public class JwtUtil {
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);
|
||||
Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (JwtException e) {
|
||||
return false;
|
||||
@ -132,11 +119,11 @@ public class JwtUtil {
|
||||
*
|
||||
* @return 返回key
|
||||
*/
|
||||
private Key getSigningKey() {
|
||||
String secret = jwtConfig.getKey();
|
||||
private Key key() {
|
||||
String secret = this.key;
|
||||
if (secret == null || secret.isEmpty()) {
|
||||
throw new IllegalStateException("JWT 签名密钥未配置");
|
||||
}
|
||||
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,6 @@ spring.mail.properties.mail.display.sendmail=spring-boot-demo
|
||||
# Jasypt配置
|
||||
jasypt.encryptor.password=abc
|
||||
# JWT配置
|
||||
jwt.config.key=kw
|
||||
jwt.config.key=daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb
|
||||
jwt.config.ttl=600000
|
||||
jwt.config.remember=604800000
|
||||
@ -45,41 +45,37 @@ COMMIT;
|
||||
|
||||
BEGIN;
|
||||
|
||||
INSERT INTO `wk_user` (
|
||||
id, username, nickname, password, email, birthday, sex, phone, status,
|
||||
create_time, update_time, last_login_time
|
||||
) VALUES (
|
||||
1072806377661009920,
|
||||
'admin',
|
||||
'管理员',
|
||||
'ff342e862e7c3285cdc07e56d6b8973b',
|
||||
'admin@xkcoding.com',
|
||||
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
||||
1,
|
||||
'17300000000',
|
||||
1,
|
||||
'2018-12-12 14:52:27', -- create_time
|
||||
'2018-12-12 14:52:27', -- update_time
|
||||
'2018-12-12 14:52:27' -- last_login_time
|
||||
);
|
||||
INSERT INTO `wk_user` (id, username, nickname, password, email, birthday, sex, phone, status,
|
||||
create_time, update_time, last_login_time)
|
||||
VALUES (1072806377661009920,
|
||||
'admin',
|
||||
'管理员',
|
||||
'$2a$10$iH/XRNn1Y5Gcyl1iG5zCD..Qq1L2YnL6xU0XmsdxmUs5SEsU/7jVe',
|
||||
'admin@xkcoding.com',
|
||||
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
||||
1,
|
||||
'17300000000',
|
||||
1,
|
||||
'2018-12-12 14:52:27', -- create_time
|
||||
'2018-12-12 14:52:27', -- update_time
|
||||
'2018-12-12 14:52:27' -- last_login_time
|
||||
);
|
||||
|
||||
INSERT INTO `wk_user` (
|
||||
id, username, nickname, password, email, birthday, sex, phone, status,
|
||||
create_time, update_time, last_login_time
|
||||
) VALUES (
|
||||
1072806378780889088,
|
||||
'user',
|
||||
'普通用户',
|
||||
'6c6bf02c8d5d3d128f34b1700cb1e32c',
|
||||
'user@xkcoding.com',
|
||||
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
||||
1,
|
||||
'17300001111',
|
||||
1,
|
||||
'2018-12-12 14:52:27', -- create_time
|
||||
'2018-12-12 14:52:27', -- update_time
|
||||
'2018-12-12 14:52:27' -- last_login_time
|
||||
);
|
||||
INSERT INTO `wk_user` (id, username, nickname, password, email, birthday, sex, phone, status,
|
||||
create_time, update_time, last_login_time)
|
||||
VALUES (1072806378780889088,
|
||||
'user',
|
||||
'普通用户',
|
||||
'$2a$10$iH/XRNn1Y5Gcyl1iG5zCD..Qq1L2YnL6xU0XmsdxmUs5SEsU/7jVe',
|
||||
'user@xkcoding.com',
|
||||
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
||||
1,
|
||||
'17300001111',
|
||||
1,
|
||||
'2018-12-12 14:52:27', -- create_time
|
||||
'2018-12-12 14:52:27', -- update_time
|
||||
'2018-12-12 14:52:27' -- last_login_time
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ CREATE TABLE `wk_user`
|
||||
`id` bigint(64) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
||||
`username` VARCHAR(32) NOT NULL UNIQUE COMMENT '用户名',
|
||||
`nickname` VARCHAR(32) NOT NULL UNIQUE COMMENT '昵称',
|
||||
`password` VARCHAR(32) NOT NULL COMMENT '加密后的密码',
|
||||
`password` VARCHAR(256) NOT NULL COMMENT '加密后的密码',
|
||||
`email` VARCHAR(32) NOT NULL UNIQUE COMMENT '邮箱',
|
||||
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
|
||||
`sex` INT(2) DEFAULT NULL COMMENT '性别,男-1,女-2',
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
package asia.yulinling.workflow.utils;
|
||||
|
||||
import asia.yulinling.workflow.config.JwtConfig;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -24,34 +27,35 @@ class JwtUtilTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 构造 JwtConfig 模拟配置
|
||||
JwtConfig config = new JwtConfig();
|
||||
config.setKey("your-256-bit-secret-key-which-needs-to-be-long-enough"); // 至少256位bit
|
||||
config.setTtl(3600000L); // 1小时
|
||||
config.setRemember(604800000L); // 7天
|
||||
|
||||
jwtUtil = new JwtUtil(config);
|
||||
jwtUtil = new JwtUtil();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateTokenAndParseToken() {
|
||||
Long userId = 123L;
|
||||
String username = "test_user";
|
||||
List<String> roles = List.of("USER", "ADMIN");
|
||||
List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
|
||||
String token = jwtUtil.generateToken(false, userId, username, roles, authorities);
|
||||
UserDetails user = User.withUsername("test")
|
||||
.password("test")
|
||||
.roles("USER")
|
||||
.build();
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
String token = jwtUtil.generateToken(authentication, false);
|
||||
assertNotNull(token);
|
||||
|
||||
Claims claims = jwtUtil.parseToken(token);
|
||||
assertEquals(userId.toString(), claims.getId());
|
||||
assertEquals(username, claims.getSubject());
|
||||
assertEquals(roles, claims.get("roles", List.class));
|
||||
assertEquals("test", claims.getSubject());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateToken() {
|
||||
String token = jwtUtil.generateToken(false, 1L, "valid_user", List.of("USER"), List.of());
|
||||
UserDetails user = User.withUsername("test")
|
||||
.password("test")
|
||||
.roles("USER")
|
||||
.build();
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
String token = jwtUtil.generateToken(authentication, false);
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
String rawPassword = "admin";
|
||||
String encodedPassword = encoder.encode(rawPassword);
|
||||
System.out.println(encodedPassword);
|
||||
assertTrue(jwtUtil.validateToken(token));
|
||||
}
|
||||
|
||||
@ -75,7 +79,6 @@ class JwtUtilTest {
|
||||
|
||||
Claims claims = jwtUtil.parseToken(token);
|
||||
assertEquals("admin", claims.getSubject());
|
||||
assertEquals("100", claims.getId());
|
||||
}
|
||||
|
||||
// Mock UserPrincipal 用于测试
|
||||
|
||||
Loading…
Reference in New Issue
Block a user