no message

This commit is contained in:
wangjianhong
2025-08-06 15:54:19 +08:00
parent bca87e7b0d
commit 417856b7be
6 changed files with 108 additions and 61 deletions

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
## Getting started
##
```
mvn versions:display-dependency-updates
mvn versions:update-properties
mvn versions:set -DnewVersion=1.2.0.RELEASE
```

10
pom.xml
View File

@@ -5,12 +5,12 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version> <version>4.0.0-M1</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.arrokoth</groupId> <groupId>com.arrokoth</groupId>
<artifactId>authorization-server-standalone</artifactId> <artifactId>authorization-server-standalone</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>1.2.0.RELEASE</version>
<name>authorization-server-standalone</name> <name>authorization-server-standalone</name>
<description>authorization-server-standalone</description> <description>authorization-server-standalone</description>
<url/> <url/>
@@ -34,8 +34,8 @@
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<spring.boot.version>3.5.3</spring.boot.version> <spring.boot.version>3.5.3</spring.boot.version>
<arrokoth.version>1.1.0.RELEASE</arrokoth.version> <arrokoth.version>1.2.0.RELEASE</arrokoth.version>
<arrokoth.bom.version>1.1.0.RELEASE</arrokoth.bom.version> <arrokoth.bom.version>1.2.0.RELEASE</arrokoth.bom.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -50,7 +50,7 @@
<dependency> <dependency>
<groupId>com.arrokoth.framework</groupId> <groupId>com.arrokoth.framework</groupId>
<artifactId>basic-authorization-server</artifactId> <artifactId>basic-authorization-server</artifactId>
<version>1.0.1-RELEASE</version> <version>1.2.0.RELEASE</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -50,7 +50,7 @@ public class SecurityWebAutoConfigurer {
.logout(logout -> logout .logout(logout -> logout
.logoutUrl(SecurityWebProperties.AXIOS_LOGOUT_PROCESSING_URL) .logoutUrl(SecurityWebProperties.AXIOS_LOGOUT_PROCESSING_URL)
.logoutSuccessUrl(securityWebProperties.getLogoutSuccessUrl()) // .logoutSuccessUrl(securityWebProperties.getLogoutSuccessUrl())
.invalidateHttpSession(true) // 注销时销毁 session .invalidateHttpSession(true) // 注销时销毁 session
.deleteCookies("JSESSIONID", "Authorization") .deleteCookies("JSESSIONID", "Authorization")
.permitAll() .permitAll()

View File

@@ -12,6 +12,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
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.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@@ -28,92 +29,113 @@ public class JwtRequestFilter extends OncePerRequestFilter {
private final RedisTokenService redisTokenService; private final RedisTokenService redisTokenService;
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
logRequestDetails(request);
try { try {
// 1. 提取 JWT Token
String jwt = extractJwtFromRequest(request); String jwt = extractJwtFromRequest(request);
if (jwt == null) { if (jwt == null) {
chain.doFilter(request, response); chain.doFilter(request, response);
return; return;
} }
String username = extractUsernameFromToken(jwt); // 2. 解析用户名
if (username == null) { String username = JwtUtils.extractUsername(jwt);
chain.doFilter(request, response); if (username == null || username.isBlank()) {
log.warn("JWT token does not contain a valid username: {}", maskToken(jwt));
sendUnauthorizedResponse(response, "Invalid token");
return; return;
} }
if (isTokenBlacklisted(jwt, response)) { // 3. 检查 Token 是否被拉黑(退出登录状态)
if (redisTokenService.isBlacklisted(jwt)) {
log.warn("Token is blacklisted: {}", maskToken(jwt));
sendUnauthorizedResponse(response, "Token is blacklisted");
return; return;
} }
// 4. 若用户未认证,则进行认证
if (isUserNotAuthenticated(username)) { if (isUserNotAuthenticated(username)) {
authenticateUser(jwt, username); authenticateUser(jwt, username, request);
} }
// 5. 继续过滤链
chain.doFilter(request, response); chain.doFilter(request, response);
} catch (Exception ex) { } catch (Exception ex) {
logger.warn("Error occurred during JWT filter processing", ex); log.warn("JWT filter processing failed for request: {}", request.getRequestURI(), ex);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); sendUnauthorizedResponse(response, "Unauthorized: " + ex.getMessage());
} }
} }
/**
* 从请求中提取 JWT Token
*/
private String extractJwtFromRequest(HttpServletRequest request) { private String extractJwtFromRequest(HttpServletRequest request) {
// 优先从 Authorization: Bearer <token>
String jwt = null; String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
// 1. 先从 Authorization Header 中提取 return bearerToken.substring(7).trim();
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
} }
// 2. 如果 Header 中没有,则尝试从 URL 参数获取(如 X-Token // 其次尝试从 X-Token 参数获取(URL 或表单
if (jwt == null || jwt.isEmpty()) { String tokenParam = request.getParameter("X-Token");
jwt = request.getParameter("X-Token"); if (tokenParam != null && !tokenParam.isBlank()) {
return tokenParam.trim();
} }
return jwt; return null;
}
private String extractUsernameFromToken(String jwt) {
try {
return JwtUtils.extractUsername(jwt);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
private boolean isTokenBlacklisted(String jwt, HttpServletResponse response) throws IOException {
if (redisTokenService.isBlacklisted(jwt)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token is blacklisted");
return true;
}
return false;
} }
/**
* 判断当前上下文是否已认证为指定用户
*/
private boolean isUserNotAuthenticated(String username) { private boolean isUserNotAuthenticated(String username) {
return SecurityContextHolder.getContext().getAuthentication() == null || var authentication = SecurityContextHolder.getContext().getAuthentication();
!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || return authentication == null
!SecurityContextHolder.getContext().getAuthentication().getName().equals(username); || !authentication.isAuthenticated()
|| !username.equals(authentication.getName());
} }
private void authenticateUser(String jwt, String username) { /**
UserDetails userDetails = userDetailsService.loadUserByUsername(username); * 对用户进行身份认证并设置 SecurityContext
*/
private void authenticateUser(String jwt, String username, HttpServletRequest request) {
UserDetails userDetails;
try {
userDetails = userDetailsService.loadUserByUsername(username);
} catch (Exception e) {
log.warn("User not found during JWT authentication: {}", username);
throw new RuntimeException("Invalid token or user does not exist");
}
if (JwtUtils.validateToken(jwt, userDetails.getUsername())) { if (!JwtUtils.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( log.warn("JWT token validation failed for user: {}", username);
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new RuntimeException("Token is expired or invalid"); throw new RuntimeException("Token is expired or invalid");
} }
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("Authenticated user: {} via JWT", username);
} }
/**
* 发送 401 响应
*/
private void sendUnauthorizedResponse(HttpServletResponse response, String message) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, message);
}
/**
* 日志脱敏:隐藏长 Token 的中间部分
*/
private String maskToken(String token) {
if (token == null || token.length() <= 10) return token;
return token.substring(0, 5) + "..." + token.substring(token.length() - 5);
}
/** /**
* 封装的打印请求详情方法 * 封装的打印请求详情方法
@@ -123,10 +145,10 @@ public class JwtRequestFilter extends OncePerRequestFilter {
String queryString = request.getQueryString(); String queryString = request.getQueryString();
// 打印基本信息 // 打印基本信息
if (log.isInfoEnabled()) { if (log.isDebugEnabled()) {
log.info("Request URL: {}", requestURL); log.debug("Request URL: {}", requestURL);
if (queryString != null) { if (queryString != null) {
log.info("Query Parameters: {}", queryString); log.debug("Query Parameters: {}", queryString);
} }
} }
@@ -135,8 +157,10 @@ public class JwtRequestFilter extends OncePerRequestFilter {
while (paramNames.hasMoreElements()) { while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement(); String paramName = paramNames.nextElement();
String[] paramValues = request.getParameterValues(paramName); String[] paramValues = request.getParameterValues(paramName);
for (String value : paramValues) { if (log.isDebugEnabled()) {
log.debug("Request Param: {} = {}", paramName, value); for (String value : paramValues) {
log.debug("Request Param: {} = {}", paramName, value);
}
} }
} }
} }

View File

@@ -19,7 +19,15 @@ public class UserDetailsServiceStore {
.roles("admin", "normal") .roles("admin", "normal")
.authorities("app", "web") .authorities("app", "web")
.build(); .build();
return new InMemoryUserDetailsManager(user);
UserDetails user2 = User.withUsername("guest")
.password(passwordEncoder.encode("yyds@8848"))
.roles("normal")
.authorities("app")
.build();
return new InMemoryUserDetailsManager(user,user2);
} }
} }

View File

@@ -69,7 +69,7 @@ mybatis:
mapper-locations: classpath*:com.arrokoth/**/mapper/xml/*.xml mapper-locations: classpath*:com.arrokoth/**/mapper/xml/*.xml
configuration: configuration:
map-underscore-to-camel-case: true map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus: mybatis-plus:
type-aliases-package: com.arrokoth.**.domain type-aliases-package: com.arrokoth.**.domain
mapper-locations: classpath*:com.arrokoth/**/mapper/xml/*.xml mapper-locations: classpath*:com.arrokoth/**/mapper/xml/*.xml