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;
|
package asia.yulinling.workflow.config;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint;
|
||||||
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
|
|
||||||
import asia.yulinling.workflow.security.AuthEntryPoint;
|
|
||||||
import asia.yulinling.workflow.security.JwtAuthenticationFilter;
|
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 asia.yulinling.workflow.utils.JwtUtil;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
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.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
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
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@ -35,14 +25,14 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final CustomerDetailsService customerDetailService;
|
private final JwtUserDetailsService jwtUserDetailsService;
|
||||||
|
|
||||||
@Bean
|
@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
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.exceptionHandling(ex -> ex
|
.exceptionHandling(ex -> ex
|
||||||
.authenticationEntryPoint(authEntryPoint)
|
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
|
||||||
.accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"))
|
.accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"))
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
@ -52,9 +42,10 @@ public class SecurityConfig {
|
|||||||
)
|
)
|
||||||
.sessionManagement(AbstractHttpConfigurer::disable)
|
.sessionManagement(AbstractHttpConfigurer::disable)
|
||||||
.addFilterBefore(
|
.addFilterBefore(
|
||||||
new JwtAuthenticationFilter(jwtUtil, authEntryPoint, requestMatcher),
|
new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService),
|
||||||
UsernamePasswordAuthenticationFilter.class
|
UsernamePasswordAuthenticationFilter.class
|
||||||
);
|
)
|
||||||
|
.userDetailsService(jwtUserDetailsService);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
@ -67,29 +58,6 @@ public class SecurityConfig {
|
|||||||
return new BCryptPasswordEncoder();
|
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
|
@Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
|
||||||
return configuration.getAuthenticationManager();
|
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
|
* @since 2025/6/13
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class AuthEntryPoint implements AuthenticationEntryPoint {
|
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
@Override
|
@Override
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||||
AuthenticationException authException) throws IOException {
|
AuthenticationException authException) throws IOException {
|
||||||
@ -7,11 +7,12 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
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 org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -28,39 +29,40 @@ import java.io.IOException;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
private final JwtUtil jwtUtil;
|
private final JwtUtil jwtUtil;
|
||||||
private final AuthenticationEntryPoint authenticationEntryPoint;
|
private final UserDetailsService userDetailsService;
|
||||||
private final RequestMatcher requestMatcher;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request,
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
FilterChain filterChain
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
)
|
|
||||||
throws ServletException, IOException {
|
String token = getTokenFromRequest(request);
|
||||||
log.info("{}", request.getRequestURI());
|
log.info("token: {}", token);
|
||||||
if (requestMatcher.matches(request)) {
|
|
||||||
log.info("{}", request.getRequestURI());
|
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
|
||||||
filterChain.doFilter(request, response);
|
// get username from token
|
||||||
return;
|
String username = jwtUtil.parseToken(token).getSubject();
|
||||||
}
|
|
||||||
String header = request.getHeader("Authorization");
|
// load the user associated with token
|
||||||
if (header == null || !header.startsWith("Bearer ")) {
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
String token = null;
|
|
||||||
if (header != null) {
|
UsernamePasswordAuthenticationToken authenticationToken
|
||||||
token = header.substring(7);
|
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
}
|
|
||||||
if (jwtUtil.validateToken(token)) {
|
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
String username = jwtUtil.parseToken(token).getSubject();
|
|
||||||
String roles = jwtUtil.parseToken(token).get("roles").toString();
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
Authentication auth = new JwtAuthenticationToken(username, roles);
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
|
||||||
} else {
|
|
||||||
authenticationEntryPoint.commence(request, response, new AuthenticationException("") {
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
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.RoleMapper;
|
||||||
import asia.yulinling.workflow.mapper.UserMapper;
|
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.Role;
|
||||||
import asia.yulinling.workflow.model.entity.User;
|
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.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
@ -18,6 +17,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,10 +31,9 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomerDetailsService implements UserDetailsService {
|
public class JwtUserDetailsService implements UserDetailsService {
|
||||||
private final UserMapper userMapper;
|
private final UserMapper userMapper;
|
||||||
private final RoleMapper roleMapper;
|
private final RoleMapper roleMapper;
|
||||||
private final PermissionMapper permissionMapper;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
@ -47,9 +46,15 @@ public class CustomerDetailsService implements UserDetailsService {
|
|||||||
|
|
||||||
User user = userMapper.selectOne(queryWrapper);
|
User user = userMapper.selectOne(queryWrapper);
|
||||||
List<Role> roles = roleMapper.selectByIds(Collections.singleton(user.getId()));
|
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);
|
Set<GrantedAuthority> authorities = roles.stream()
|
||||||
return UserPrincipal.create(user, roles, permissions);
|
.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;
|
package asia.yulinling.workflow.utils;
|
||||||
|
|
||||||
import asia.yulinling.workflow.config.JwtConfig;
|
import io.jsonwebtoken.io.Decoders;
|
||||||
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
|
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import io.jsonwebtoken.*;
|
import io.jsonwebtoken.*;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@ -25,47 +20,27 @@ import java.util.List;
|
|||||||
* @author YLL
|
* @author YLL
|
||||||
* @since 2025/6/11
|
* @since 2025/6/11
|
||||||
*/
|
*/
|
||||||
@EnableConfigurationProperties(JwtConfig.class)
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtUtil {
|
public class JwtUtil {
|
||||||
private final JwtConfig jwtConfig;
|
/**
|
||||||
|
* jwt 加密 key,默认值:kw.
|
||||||
|
*/
|
||||||
|
@Value("${jwt.config.key}")
|
||||||
|
private String key = "daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建JWT
|
* jwt 过期时间,默认值:600000 {@code 10 分钟}.
|
||||||
*
|
|
||||||
* @param isRememberMe 记住我
|
|
||||||
* @param userId 用户id
|
|
||||||
* @param subject 用户名
|
|
||||||
* @param roles 用户角色
|
|
||||||
* @param authorities 用户权限
|
|
||||||
* @return JWT
|
|
||||||
*/
|
*/
|
||||||
public String generateToken(boolean isRememberMe, Long userId, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) {
|
@Value("${jwt.config.ttl}")
|
||||||
// 1. 构建签名密钥
|
private Long ttl = 600000L;
|
||||||
Key key = getSigningKey();
|
|
||||||
|
|
||||||
// 2. 当前时间
|
/**
|
||||||
Date now = new Date();
|
* 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天}
|
||||||
|
*/
|
||||||
// 3. 计算过期时间
|
@Value("${jwt.config.remember}")
|
||||||
long ttl = isRememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();
|
private Long remember = 604800000L;
|
||||||
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
|
* 创建JWT
|
||||||
@ -74,15 +49,27 @@ public class JwtUtil {
|
|||||||
* @param isRememberMe 记住我
|
* @param isRememberMe 记住我
|
||||||
* @return JWT
|
* @return JWT
|
||||||
*/
|
*/
|
||||||
public String generateToken(Authentication authentication, Boolean isRememberMe) {
|
public String generateToken(Authentication authentication, boolean isRememberMe) {
|
||||||
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
|
// 1. 构建签名密钥
|
||||||
return generateToken(
|
Key key = key();
|
||||||
isRememberMe,
|
|
||||||
userPrincipal.getId(),
|
// 2. 当前时间
|
||||||
userPrincipal.getUsername(),
|
Date now = new Date();
|
||||||
userPrincipal.getRoles(),
|
|
||||||
userPrincipal.getAuthorities()
|
// 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) {
|
public Claims parseToken(String token) {
|
||||||
try {
|
try {
|
||||||
Key key = getSigningKey();
|
Key key = key();
|
||||||
|
|
||||||
return Jwts.parserBuilder()
|
return Jwts.parserBuilder()
|
||||||
.setSigningKey(key)
|
.setSigningKey(key)
|
||||||
@ -120,7 +107,7 @@ public class JwtUtil {
|
|||||||
*/
|
*/
|
||||||
public boolean validateToken(String token) {
|
public boolean validateToken(String token) {
|
||||||
try {
|
try {
|
||||||
Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);
|
Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
return false;
|
return false;
|
||||||
@ -132,11 +119,11 @@ public class JwtUtil {
|
|||||||
*
|
*
|
||||||
* @return 返回key
|
* @return 返回key
|
||||||
*/
|
*/
|
||||||
private Key getSigningKey() {
|
private Key key() {
|
||||||
String secret = jwtConfig.getKey();
|
String secret = this.key;
|
||||||
if (secret == null || secret.isEmpty()) {
|
if (secret == null || secret.isEmpty()) {
|
||||||
throw new IllegalStateException("JWT 签名密钥未配置");
|
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配置
|
||||||
jasypt.encryptor.password=abc
|
jasypt.encryptor.password=abc
|
||||||
# JWT配置
|
# JWT配置
|
||||||
jwt.config.key=kw
|
jwt.config.key=daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb
|
||||||
jwt.config.ttl=600000
|
jwt.config.ttl=600000
|
||||||
jwt.config.remember=604800000
|
jwt.config.remember=604800000
|
||||||
@ -45,41 +45,37 @@ COMMIT;
|
|||||||
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
INSERT INTO `wk_user` (
|
INSERT INTO `wk_user` (id, username, nickname, password, email, birthday, sex, phone, status,
|
||||||
id, username, nickname, password, email, birthday, sex, phone, status,
|
create_time, update_time, last_login_time)
|
||||||
create_time, update_time, last_login_time
|
VALUES (1072806377661009920,
|
||||||
) VALUES (
|
'admin',
|
||||||
1072806377661009920,
|
'管理员',
|
||||||
'admin',
|
'$2a$10$iH/XRNn1Y5Gcyl1iG5zCD..Qq1L2YnL6xU0XmsdxmUs5SEsU/7jVe',
|
||||||
'管理员',
|
'admin@xkcoding.com',
|
||||||
'ff342e862e7c3285cdc07e56d6b8973b',
|
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
||||||
'admin@xkcoding.com',
|
1,
|
||||||
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
'17300000000',
|
||||||
1,
|
1,
|
||||||
'17300000000',
|
'2018-12-12 14:52:27', -- create_time
|
||||||
1,
|
'2018-12-12 14:52:27', -- update_time
|
||||||
'2018-12-12 14:52:27', -- create_time
|
'2018-12-12 14:52:27' -- last_login_time
|
||||||
'2018-12-12 14:52:27', -- update_time
|
);
|
||||||
'2018-12-12 14:52:27' -- last_login_time
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO `wk_user` (
|
INSERT INTO `wk_user` (id, username, nickname, password, email, birthday, sex, phone, status,
|
||||||
id, username, nickname, password, email, birthday, sex, phone, status,
|
create_time, update_time, last_login_time)
|
||||||
create_time, update_time, last_login_time
|
VALUES (1072806378780889088,
|
||||||
) VALUES (
|
'user',
|
||||||
1072806378780889088,
|
'普通用户',
|
||||||
'user',
|
'$2a$10$iH/XRNn1Y5Gcyl1iG5zCD..Qq1L2YnL6xU0XmsdxmUs5SEsU/7jVe',
|
||||||
'普通用户',
|
'user@xkcoding.com',
|
||||||
'6c6bf02c8d5d3d128f34b1700cb1e32c',
|
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
||||||
'user@xkcoding.com',
|
1,
|
||||||
'1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28
|
'17300001111',
|
||||||
1,
|
1,
|
||||||
'17300001111',
|
'2018-12-12 14:52:27', -- create_time
|
||||||
1,
|
'2018-12-12 14:52:27', -- update_time
|
||||||
'2018-12-12 14:52:27', -- create_time
|
'2018-12-12 14:52:27' -- last_login_time
|
||||||
'2018-12-12 14:52:27', -- update_time
|
);
|
||||||
'2018-12-12 14:52:27' -- last_login_time
|
|
||||||
);
|
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ CREATE TABLE `wk_user`
|
|||||||
`id` bigint(64) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
`id` bigint(64) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
||||||
`username` VARCHAR(32) NOT NULL UNIQUE COMMENT '用户名',
|
`username` VARCHAR(32) NOT NULL UNIQUE COMMENT '用户名',
|
||||||
`nickname` 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 '邮箱',
|
`email` VARCHAR(32) NOT NULL UNIQUE COMMENT '邮箱',
|
||||||
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
|
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
|
||||||
`sex` INT(2) DEFAULT NULL COMMENT '性别,男-1,女-2',
|
`sex` INT(2) DEFAULT NULL COMMENT '性别,男-1,女-2',
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
package asia.yulinling.workflow.utils;
|
package asia.yulinling.workflow.utils;
|
||||||
|
|
||||||
import asia.yulinling.workflow.config.JwtConfig;
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
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;
|
import java.util.List;
|
||||||
|
|
||||||
@ -24,34 +27,35 @@ class JwtUtilTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
// 构造 JwtConfig 模拟配置
|
jwtUtil = new JwtUtil();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGenerateTokenAndParseToken() {
|
void testGenerateTokenAndParseToken() {
|
||||||
Long userId = 123L;
|
UserDetails user = User.withUsername("test")
|
||||||
String username = "test_user";
|
.password("test")
|
||||||
List<String> roles = List.of("USER", "ADMIN");
|
.roles("USER")
|
||||||
List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
.build();
|
||||||
|
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||||
String token = jwtUtil.generateToken(false, userId, username, roles, authorities);
|
String token = jwtUtil.generateToken(authentication, false);
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
|
|
||||||
Claims claims = jwtUtil.parseToken(token);
|
Claims claims = jwtUtil.parseToken(token);
|
||||||
assertEquals(userId.toString(), claims.getId());
|
assertEquals("test", claims.getSubject());
|
||||||
assertEquals(username, claims.getSubject());
|
|
||||||
assertEquals(roles, claims.get("roles", List.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testValidateToken() {
|
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));
|
assertTrue(jwtUtil.validateToken(token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +79,6 @@ class JwtUtilTest {
|
|||||||
|
|
||||||
Claims claims = jwtUtil.parseToken(token);
|
Claims claims = jwtUtil.parseToken(token);
|
||||||
assertEquals("admin", claims.getSubject());
|
assertEquals("admin", claims.getSubject());
|
||||||
assertEquals("100", claims.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock UserPrincipal 用于测试
|
// Mock UserPrincipal 用于测试
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user