Initial commit

This commit is contained in:
2025-09-25 09:45:47 +08:00
commit 9e8662efc0
41 changed files with 4070 additions and 0 deletions

33
cn-auth/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

68
cn-auth/pom.xml Normal file
View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn.product</groupId>
<artifactId>CN_Product</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>cn-auth</artifactId>
<version>1.0.0</version>
<name>cn-auth</name>
<description>cn-auth</description>
<dependencies>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>njcn-common</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>mybatis-plus</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>spingboot2.3.12</artifactId>
<version>2.3.12</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version> <!-- 使用最新稳定版 -->
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,91 @@
package com.njcn.product.security;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.HttpResultUtil;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;
@RestController
@Slf4j
@RequiredArgsConstructor
public class AuthController extends BaseController {
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;
@PostMapping("/cn_authenticate")
@ApiOperation("登录认证")
public HttpResult<AuthResponse> createAuthenticationToken(@RequestBody @Validated AuthRequest authRequest) {
String methodDescribe = getMethodDescribe("createAuthenticationToken");
//log.info("Authentication request - username: {}, password: {}",authRequest.getUsername(),authRequest.getPassword());
try {
// 执行认证,内部会调用 UserDetailsService 加载用户信息
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(),authRequest.getPassword()));
// 将认证信息存入 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
// 直接从 Authentication 对象中获取已加载的 UserDetails避免重复查询
MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal();
// 获取用户部门(假设 CustomUserDetails 包含部门信息)
String department = userDetails.getDeptId();
final String jwt = jwtUtil.generateToken(userDetails);
AuthResponse authResponse = new AuthResponse();
authResponse.setToken(jwt);
authResponse.setDeptId(department);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, authResponse, methodDescribe);
} catch (Exception e) {
e.printStackTrace();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe);
}
}
}
// 认证请求类
class AuthRequest {
@NotBlank(message = "用户名不可为空")
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,20 @@
package com.njcn.product.security;
import lombok.Data;
/**
* @Author: cdf
* @CreateTime: 2025-06-26
* @Description:
*/
@Data
public class AuthResponse {
private String token;
private String deptId;
private String roleId;
private String userIndex;
}

View File

@@ -0,0 +1,81 @@
package com.njcn.product.security;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;
@Component
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtUtil.extractUsername(jwt);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
sendErrorResponse(response,CommonResponseEnum.TOKEN_EXPIRE_JWT);
return;
} catch (Exception e) {
log.error(e.getMessage());
sendErrorResponse(response,CommonResponseEnum.PARSE_TOKEN_ERROR);
return;
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
private void sendErrorResponse(HttpServletResponse response, CommonResponseEnum error) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json;charset=UTF-8");
HttpResult<String> httpResult = new HttpResult<>();
httpResult.setCode(error.getCode());
httpResult.setMessage(error.getMessage());
response.getWriter().write(new JSONObject(httpResult, false).toString());
}
}

View File

@@ -0,0 +1,85 @@
package com.njcn.product.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private final String userId = "userId";
private final String userName = "userName";
private final String deptId = "deptId";
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 1000000000L; // 100000小时
// 生成JWT令牌
public String generateToken(MyUserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(userId,userDetails.getUserId());
claims.put(userName,userDetails.getUsername());
claims.put(deptId,userDetails.getDeptId());
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
}
// 验证令牌
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 提取用户名
public String extractUsername(String token) {
return extractClaim(token, it->it.get(userName).toString());
}
// 提取用户ID
public String extractUserId(String token) {
return extractClaim(token,it->it.get(userId).toString());
}
// 提取用户部门
public String extractDepartment(String token) {
return extractClaim(token, it->it.get(deptId).toString());
}
// 提取过期时间
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
}

View File

@@ -0,0 +1,64 @@
package com.njcn.product.security;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* @Author: cdf
* @CreateTime: 2025-06-26
* @Description:
*/
@Data
public class MyUserDetails implements UserDetails {
private String userId; // 用户唯一标识
private String username; // 用户名
private String password; // 密码
private String deptId; // 部门信息
private Collection<? extends GrantedAuthority> authorities; // 权限集合
private boolean accountNonExpired; // 账户是否未过期
private boolean accountNonLocked; // 账户是否未锁定
private boolean credentialsNonExpired; // 凭证是否未过期
private boolean enabled; // 账户是否启用
public MyUserDetails(String userId,String username, String password, String deptId,Collection<? extends GrantedAuthority> authorities) {
this.userId = userId;
this.username = username;
this.password = password;
this.deptId = deptId;
this.authorities = authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,51 @@
package com.njcn.product.security;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
@RequiredArgsConstructor
public class MyUserDetailsService implements UserDetailsService {
@Override
public MyUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if("system_event".equals(username)){
return new MyUserDetails("12345678910","system_event", "@#001njcnpqs","10001",
new ArrayList<>());
}
// 这里应该从数据库中获取用户信息
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode("@#001njcnpqs");
/*
LambdaQueryWrapper<PqsUser> userWrapper = new LambdaQueryWrapper<>();
userWrapper.eq(PqsUser::getLoginname,username);
PqsUser pqsUser = pqsUserMapper.selectOne(userWrapper);
if(Objects.isNull(pqsUser)){
throw new UsernameNotFoundException("User not found with username: " + username);
}
LambdaQueryWrapper<PqsUserSet> userSetWrapper = new LambdaQueryWrapper<>();
userSetWrapper.eq(PqsUserSet::getUserIndex,pqsUser.getUserIndex());
PqsUserSet pqsUserSet = pqsUserSetMapper.selectOne(userSetWrapper);
String deptId = pqsUserSet.getDeptsIndex();*/
return new MyUserDetails("12345678910","cdf", encodedPassword,"beac2cdb4acc4826d4519ea4177f3bfa",
new ArrayList<>());
}
}

View File

@@ -0,0 +1,58 @@
package com.njcn.product.security;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import javax.annotation.Resource;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Resource
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
//.antMatchers("/cn_authenticate","/ws/**","/accept/testEvent","/accept/eventMsg").permitAll() // 允许访问认证接口
.antMatchers("/**").permitAll() // 允许访问认证接口
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 使用无状态会话
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}