no message
This commit is contained in:
15
README.md
Normal file
15
README.md
Normal 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
10
pom.xml
@@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>4.0.0-M1</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.arrokoth</groupId>
|
||||
<artifactId>authorization-server-standalone</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>1.2.0.RELEASE</version>
|
||||
<name>authorization-server-standalone</name>
|
||||
<description>authorization-server-standalone</description>
|
||||
<url/>
|
||||
@@ -34,8 +34,8 @@
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<spring.boot.version>3.5.3</spring.boot.version>
|
||||
|
||||
<arrokoth.version>1.1.0.RELEASE</arrokoth.version>
|
||||
<arrokoth.bom.version>1.1.0.RELEASE</arrokoth.bom.version>
|
||||
<arrokoth.version>1.2.0.RELEASE</arrokoth.version>
|
||||
<arrokoth.bom.version>1.2.0.RELEASE</arrokoth.bom.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<dependency>
|
||||
<groupId>com.arrokoth.framework</groupId>
|
||||
<artifactId>basic-authorization-server</artifactId>
|
||||
<version>1.0.1-RELEASE</version>
|
||||
<version>1.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -50,7 +50,7 @@ public class SecurityWebAutoConfigurer {
|
||||
|
||||
.logout(logout -> logout
|
||||
.logoutUrl(SecurityWebProperties.AXIOS_LOGOUT_PROCESSING_URL)
|
||||
.logoutSuccessUrl(securityWebProperties.getLogoutSuccessUrl())
|
||||
// .logoutSuccessUrl(securityWebProperties.getLogoutSuccessUrl())
|
||||
.invalidateHttpSession(true) // 注销时销毁 session
|
||||
.deleteCookies("JSESSIONID", "Authorization")
|
||||
.permitAll()
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||
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;
|
||||
|
||||
@@ -28,92 +29,113 @@ public class JwtRequestFilter extends OncePerRequestFilter {
|
||||
private final RedisTokenService redisTokenService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
|
||||
|
||||
logRequestDetails(request);
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
// 1. 提取 JWT Token
|
||||
String jwt = extractJwtFromRequest(request);
|
||||
if (jwt == null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String username = extractUsernameFromToken(jwt);
|
||||
if (username == null) {
|
||||
chain.doFilter(request, response);
|
||||
// 2. 解析用户名
|
||||
String username = JwtUtils.extractUsername(jwt);
|
||||
if (username == null || username.isBlank()) {
|
||||
log.warn("JWT token does not contain a valid username: {}", maskToken(jwt));
|
||||
sendUnauthorizedResponse(response, "Invalid token");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTokenBlacklisted(jwt, response)) {
|
||||
// 3. 检查 Token 是否被拉黑(退出登录状态)
|
||||
if (redisTokenService.isBlacklisted(jwt)) {
|
||||
log.warn("Token is blacklisted: {}", maskToken(jwt));
|
||||
sendUnauthorizedResponse(response, "Token is blacklisted");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 若用户未认证,则进行认证
|
||||
if (isUserNotAuthenticated(username)) {
|
||||
authenticateUser(jwt, username);
|
||||
authenticateUser(jwt, username, request);
|
||||
}
|
||||
|
||||
// 5. 继续过滤链
|
||||
chain.doFilter(request, response);
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Error occurred during JWT filter processing", ex);
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||
log.warn("JWT filter processing failed for request: {}", request.getRequestURI(), ex);
|
||||
sendUnauthorizedResponse(response, "Unauthorized: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从请求中提取 JWT Token
|
||||
*/
|
||||
private String extractJwtFromRequest(HttpServletRequest request) {
|
||||
|
||||
String jwt = null;
|
||||
|
||||
// 1. 先从 Authorization Header 中提取
|
||||
String authorizationHeader = request.getHeader("Authorization");
|
||||
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
|
||||
jwt = authorizationHeader.substring(7);
|
||||
// 优先从 Authorization: Bearer <token>
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7).trim();
|
||||
}
|
||||
|
||||
// 2. 如果 Header 中没有,则尝试从 URL 参数中获取(如 X-Token)
|
||||
if (jwt == null || jwt.isEmpty()) {
|
||||
jwt = request.getParameter("X-Token");
|
||||
// 其次尝试从 X-Token 参数获取(URL 或表单)
|
||||
String tokenParam = request.getParameter("X-Token");
|
||||
if (tokenParam != null && !tokenParam.isBlank()) {
|
||||
return tokenParam.trim();
|
||||
}
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
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;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前上下文是否已认证为指定用户
|
||||
*/
|
||||
private boolean isUserNotAuthenticated(String username) {
|
||||
return SecurityContextHolder.getContext().getAuthentication() == null ||
|
||||
!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() ||
|
||||
!SecurityContextHolder.getContext().getAuthentication().getName().equals(username);
|
||||
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication == null
|
||||
|| !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())) {
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
} else {
|
||||
if (!JwtUtils.validateToken(jwt, userDetails.getUsername())) {
|
||||
log.warn("JWT token validation failed for user: {}", username);
|
||||
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();
|
||||
|
||||
// 打印基本信息
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Request URL: {}", requestURL);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Request URL: {}", requestURL);
|
||||
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()) {
|
||||
String paramName = paramNames.nextElement();
|
||||
String[] paramValues = request.getParameterValues(paramName);
|
||||
for (String value : paramValues) {
|
||||
log.debug("Request Param: {} = {}", paramName, value);
|
||||
if (log.isDebugEnabled()) {
|
||||
for (String value : paramValues) {
|
||||
log.debug("Request Param: {} = {}", paramName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,15 @@ public class UserDetailsServiceStore {
|
||||
.roles("admin", "normal")
|
||||
.authorities("app", "web")
|
||||
.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ mybatis:
|
||||
mapper-locations: classpath*:com.arrokoth/**/mapper/xml/*.xml
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
mybatis-plus:
|
||||
type-aliases-package: com.arrokoth.**.domain
|
||||
mapper-locations: classpath*:com.arrokoth/**/mapper/xml/*.xml
|
||||
|
||||
Reference in New Issue
Block a user