diff --git a/pom.xml b/pom.xml index aa06b7b..5c852f2 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-data-redis + com.auth0 java-jwt diff --git a/src/main/java/com/example/config/SecurityConfiguration.java b/src/main/java/com/example/config/SecurityConfiguration.java index c32e2c2..8ac01c2 100644 --- a/src/main/java/com/example/config/SecurityConfiguration.java +++ b/src/main/java/com/example/config/SecurityConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.io.IOException; +import java.io.PrintWriter; @Configuration public class SecurityConfiguration { @@ -54,10 +55,6 @@ public class SecurityConfiguration { //用户登录了但没有权限访问一些资源 .accessDeniedHandler(this::onAccessDeny) ) - - - - .csrf(AbstractHttpConfigurer::disable) .sessionManagement(conf -> conf .sessionCreationPolicy(SessionCreationPolicy.STATELESS) @@ -106,6 +103,13 @@ public class SecurityConfiguration { public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - + response.setContentType("application/json;charset=utf-8"); + PrintWriter writer = response.getWriter(); + String authorization = request.getHeader("Authorization"); + if (utils.invalidateJwt(authorization)){ + writer.write(RestBean.success().asJsonString()); + }else { + writer.write(RestBean.failure(400,"退出登录失败").asJsonString()); + } } } \ No newline at end of file diff --git a/src/main/java/com/example/utils/Const.java b/src/main/java/com/example/utils/Const.java new file mode 100644 index 0000000..09193a8 --- /dev/null +++ b/src/main/java/com/example/utils/Const.java @@ -0,0 +1,5 @@ +package com.example.utils; + +public class Const { + public static final String JWT_BLACK_LIST = "jwt:blacklist:"; +} diff --git a/src/main/java/com/example/utils/JwtUtils.java b/src/main/java/com/example/utils/JwtUtils.java index 2597b63..5c998fc 100644 --- a/src/main/java/com/example/utils/JwtUtils.java +++ b/src/main/java/com/example/utils/JwtUtils.java @@ -6,8 +6,10 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; @@ -16,6 +18,8 @@ import org.springframework.stereotype.Component; import java.util.Calendar; import java.util.Date; import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; @Component public class JwtUtils { @@ -25,16 +29,48 @@ public class JwtUtils { @Value("${spring.security.jwt.expire}") int expire; + @Resource + StringRedisTemplate template; + //添加让jwt令牌失效的方法 + public boolean invalidateJwt(String headerToken){ + String token =this.convertToken(headerToken); + if (token == null ) return false; + Algorithm algorithm = Algorithm.HMAC256(key); + JWTVerifier jwtVerifier = JWT.require(algorithm).build(); + try { + DecodedJWT jwt = jwtVerifier.verify(token); + String id = jwt.getId(); + return deleteToken(id ,jwt.getExpiresAt()); + }catch (JWTVerificationException e){ + return false; + } + } + + private boolean deleteToken(String uuid , Date time){ + if (this.isInvalidToken(uuid)) + return false; + Date now = new Date(); + long expire = Math.max(time.getTime() - now.getTime() , 0 ); + template.opsForValue().set(Const.JWT_BLACK_LIST + uuid , "" ,expire , TimeUnit.MILLISECONDS); + return true; + } + + private boolean isInvalidToken(String uuid){ + return Boolean.TRUE.equals(template.hasKey(Const.JWT_BLACK_LIST + uuid)); + } + + //解析jwt public DecodedJWT resolverJwt(String headerToken){ String token =this.convertToken(headerToken); if (token == null ) return null; Algorithm algorithm = Algorithm.HMAC256(key); JWTVerifier jwtVerifier = JWT.require(algorithm).build(); - //验证jwt是否被用户篡改过,是的话抛出异常 try { DecodedJWT verify = jwtVerifier.verify(token); - Date expiresAt = verify.getExpiresAt(); //判断token是否过期 + if (this.isInvalidToken(verify.getId())) + return null; + Date expiresAt = verify.getExpiresAt(); return new Date().after(expiresAt) ? null : verify; } catch (JWTVerificationException e) { @@ -70,6 +106,8 @@ public class JwtUtils { Algorithm algorithm = Algorithm.HMAC256(key); Date expire = this.expireTime(); return JWT.create() + //让每个令牌生成一个随机的uuid + .withJWTId(UUID.randomUUID().toString()) .withClaim("id",id) .withClaim("name",username) .withClaim("authorities",details.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())