学员安全登出

This commit is contained in:
none 2023-03-21 14:57:00 +08:00
parent ee248b483d
commit ad02bcad59
11 changed files with 114 additions and 49 deletions

View File

@ -13,6 +13,8 @@ public class PlayEduFCtx {
private static final String KEY_USER_ID = "user_id"; private static final String KEY_USER_ID = "user_id";
private static final String KEY_USER = "user"; private static final String KEY_USER = "user";
private static final String KEY_JWT_JTI = "jwt_jti";
private static final String KEY_TOKEN = "token";
public PlayEduFCtx() { public PlayEduFCtx() {
} }
@ -49,4 +51,20 @@ public class PlayEduFCtx {
public static User getUser() { public static User getUser() {
return (User) get(KEY_USER); return (User) get(KEY_USER);
} }
public static void setJWtJti(String jti) {
put(KEY_JWT_JTI, jti);
}
public static String getJwtJti() {
return (String) get(KEY_JWT_JTI);
}
public static void setToken(String token) {
put(KEY_TOKEN, token);
}
public static String getToken() {
return (String) get(KEY_TOKEN);
}
} }

View File

@ -1,32 +0,0 @@
package xyz.playedu.api.caches;
import org.springframework.stereotype.Component;
import xyz.playedu.api.exception.LimitException;
import xyz.playedu.api.util.RedisUtil;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/10 14:13
*/
@Component
public class UserLoginCache {
private final static String keyTemplate = "user-login:%s";
private final static int expire = 10;//10s
public void check(String email) throws LimitException {
if (RedisUtil.exists(key(email))) {
throw new LimitException();
}
}
public void put(String email) {
RedisUtil.set(key(email), "1", expire);
}
private String key(String email) {
return String.format(keyTemplate, email);
}
}

View File

@ -7,10 +7,12 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.caches.UserLoginCache; import xyz.playedu.api.PlayEduFCtx;
import xyz.playedu.api.constant.SystemConstant; import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.User; import xyz.playedu.api.domain.User;
import xyz.playedu.api.event.UserLoginEvent; import xyz.playedu.api.event.UserLoginEvent;
import xyz.playedu.api.event.UserLogoutEvent;
import xyz.playedu.api.exception.JwtLogoutException;
import xyz.playedu.api.exception.LimitException; import xyz.playedu.api.exception.LimitException;
import xyz.playedu.api.request.frontend.LoginPasswordRequest; import xyz.playedu.api.request.frontend.LoginPasswordRequest;
import xyz.playedu.api.service.JWTService; import xyz.playedu.api.service.JWTService;
@ -40,16 +42,10 @@ public class LoginController {
@Autowired @Autowired
private ApplicationContext ctx; private ApplicationContext ctx;
@Autowired
private UserLoginCache userLoginCache;
@PostMapping("/password") @PostMapping("/password")
public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req) throws LimitException { public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req) throws LimitException {
String email = req.getEmail(); String email = req.getEmail();
// 限流-限制学员10s内登录成功一次
userLoginCache.check(email);
User user = userService.find(email); User user = userService.find(email);
if (user == null) { if (user == null) {
return JsonResponse.error("邮箱或密码错误"); return JsonResponse.error("邮箱或密码错误");
@ -72,4 +68,11 @@ public class LoginController {
return JsonResponse.data(data); return JsonResponse.data(data);
} }
@PostMapping("/logout")
public JsonResponse logout() throws JwtLogoutException {
jwtService.userLogout(PlayEduFCtx.getToken());
ctx.publishEvent(new UserLogoutEvent(this, PlayEduFCtx.getUserId(), PlayEduFCtx.getJwtJti()));
return JsonResponse.success();
}
} }

View File

@ -0,0 +1,26 @@
package xyz.playedu.api.event;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.ApplicationEvent;
import java.util.Date;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/21 14:31
*/
@Setter
@Getter
public class UserLogoutEvent extends ApplicationEvent {
private Integer userId;
private String jti;
private Date createdAt;
public UserLogoutEvent(Object source, Integer userId, String jti) {
super(source);
this.userId = userId;
this.jti = jti;
this.createdAt = new Date();
}
}

View File

@ -5,7 +5,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import xyz.playedu.api.caches.UserLoginCache;
import xyz.playedu.api.constant.SystemConstant; import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.event.UserLoginEvent; import xyz.playedu.api.event.UserLoginEvent;
import xyz.playedu.api.exception.JwtLogoutException; import xyz.playedu.api.exception.JwtLogoutException;
@ -28,9 +27,6 @@ public class UserLoginListener {
@Autowired @Autowired
private JWTService jwtService; private JWTService jwtService;
@Autowired
private UserLoginCache userLoginCache;
@EventListener @EventListener
@Async @Async
public void updateLoginInfo(UserLoginEvent event) throws JwtLogoutException { public void updateLoginInfo(UserLoginEvent event) throws JwtLogoutException {
@ -47,10 +43,4 @@ public class UserLoginListener {
event.getUserAgent().getOs().toString() event.getUserAgent().getOs().toString()
); );
} }
@EventListener
public void writeCache(UserLoginEvent event) {
userLoginCache.put(event.getEmail());
}
} }

View File

@ -0,0 +1,27 @@
package xyz.playedu.api.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserLogoutEvent;
import xyz.playedu.api.service.UserLoginRecordService;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/21 14:51
*/
@Component
@Slf4j
public class UserLogoutListener {
@Autowired
private UserLoginRecordService userLoginRecordService;
@EventListener
@Async
public void updateLoginRecord(UserLogoutEvent event) {
userLoginRecordService.logout(event.getUserId(), event.getJti());
}
}

View File

@ -61,6 +61,8 @@ public class FrontMiddleware implements HandlerInterceptor {
PlayEduFCtx.setUserId(user.getId()); PlayEduFCtx.setUserId(user.getId());
PlayEduFCtx.setUser(user); PlayEduFCtx.setUser(user);
PlayEduFCtx.setJWtJti(token);
PlayEduFCtx.setJWtJti(payload.getJti());
return HandlerInterceptor.super.preHandle(request, response, handler); return HandlerInterceptor.super.preHandle(request, response, handler);
} catch (Exception e) { } catch (Exception e) {

View File

@ -11,5 +11,9 @@ public interface JWTService {
void logout(String token, String prv) throws JwtLogoutException; void logout(String token, String prv) throws JwtLogoutException;
void userLogout(String token) throws JwtLogoutException;
void adminUserLogout(String token) throws JwtLogoutException;
JWTPayload parse(String token, String prv) throws JwtLogoutException; JWTPayload parse(String token, String prv) throws JwtLogoutException;
} }

View File

@ -12,4 +12,6 @@ public interface UserLoginRecordService extends IService<UserLoginRecord> {
UserLoginRecord store(Integer userId, String jti, Long expired, String ip, String ipArea, String browser, String browserVersion, String os); UserLoginRecord store(Integer userId, String jti, Long expired, String ip, String ipArea, String browser, String browserVersion, String os);
void saveIpArea(Integer id, String area); void saveIpArea(Integer id, String area);
void logout(Integer userid, String jti);
} }

View File

@ -7,6 +7,8 @@ import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import xyz.playedu.api.constant.FrontendConstant;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.exception.JwtLogoutException; import xyz.playedu.api.exception.JwtLogoutException;
import xyz.playedu.api.service.JWTService; import xyz.playedu.api.service.JWTService;
import xyz.playedu.api.types.JWTPayload; import xyz.playedu.api.types.JWTPayload;
@ -82,6 +84,16 @@ public class JwtServiceImpl implements JWTService {
RedisUtil.set(cacheKey, 1, expire); RedisUtil.set(cacheKey, 1, expire);
} }
@Override
public void userLogout(String token) throws JwtLogoutException {
logout(token, SystemConstant.JWT_PRV_USER);
}
@Override
public void adminUserLogout(String token) throws JwtLogoutException {
logout(token, SystemConstant.JWT_PRV_ADMIN_USER);
}
private Claims parseToken(String token, String prv) throws JwtLogoutException { private Claims parseToken(String token, String prv) throws JwtLogoutException {
Claims claims = (Claims) Jwts.parserBuilder().setSigningKey(getSecretKey()).require("prv", prv).build().parse(token).getBody(); Claims claims = (Claims) Jwts.parserBuilder().setSigningKey(getSecretKey()).require("prv", prv).build().parse(token).getBody();
if (isInBlack(claims.getId())) { if (isInBlack(claims.getId())) {

View File

@ -36,6 +36,19 @@ public class UserLoginRecordServiceImpl extends ServiceImpl<UserLoginRecordMappe
record.setIpArea(area); record.setIpArea(area);
updateById(record); updateById(record);
} }
@Override
public void logout(Integer userid, String jti) {
UserLoginRecord record = getOne(query().getWrapper().eq("user_id", userid).eq("jti", jti).eq("is_logout", 0));
if (record == null) {
return;
}
UserLoginRecord newRecord = new UserLoginRecord();
newRecord.setId(record.getId());
newRecord.setIsLogout(1);
updateById(newRecord);
}
} }