refactor: 添加 websocket 连接认证拦截器实现点对点指定用户发送消息;移除 easy-captcha 替换为 hutool-captcha验证码实现代码简化;重构认证接口控制层代码。

This commit is contained in:
haoxr
2023-09-12 18:25:16 +08:00
parent 87fcf022ba
commit 9453600715
37 changed files with 290 additions and 358 deletions

View File

@@ -1,9 +1,9 @@
package com.youlai.system.aspect;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.exception.BusinessException;
import com.youlai.system.exception.BusinessException;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.RequestUtils;
import com.youlai.system.util.RequestUtils;
import com.youlai.system.common.annotation.PreventDuplicateSubmit;
import com.youlai.system.security.JwtTokenManager;
import jakarta.servlet.http.HttpServletRequest;

View File

@@ -4,7 +4,7 @@ package com.youlai.system.common.constant;
* Security 常量
*
* @author haoxr
* @since 3.0.0
* @since 2.0.0
*/
public interface SecurityConstants {

View File

@@ -1,62 +0,0 @@
package com.youlai.system.config;
import com.youlai.system.common.enums.CaptchaTypeEnum;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.awt.*;
/**
* EasyCaptcha 配置类
*
* @author haoxr
* @since 2023/03/24
*/
@ConfigurationProperties(prefix = "easy-captcha")
@Configuration
@Data
public class CaptchaConfig {
/**
* 验证码类型
*/
private CaptchaTypeEnum type = CaptchaTypeEnum.ARITHMETIC;
/**
* 验证码缓存过期时间(单位:秒)
*/
private long ttl = 120l;
/**
* 验证码内容长度
*/
private int length = 4;
/**
* 验证码宽度
*/
private int width = 120;
/**
* 验证码高度
*/
private int height = 36;
/**
* 验证码字体
*/
private String fontName = "Verdana";
/**
* 字体风格
*/
private Integer fontStyle = Font.PLAIN;
/**
* 字体大小
*/
private int fontSize = 20;
}

View File

@@ -33,8 +33,6 @@ public class WebMvcConfig implements WebMvcConfigurer {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 后台Long值传递给前端精度丢失问题JS最大精度整数是Math.pow(2,53)

View File

@@ -1,28 +1,32 @@
package com.youlai.system.config;
import com.youlai.system.interceptor.AuthChannelInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket 配置
* <p>
* STOMP 不是一个传输协议,而是一个简单的文本消息协议,定义消息格式和交换规则
* WebSocket 配置
*
* @author haoxr
* @since 2.3.0
* @since 2.4.0
*/
@Configuration
@ConditionalOnProperty(name = "system.config.websocket-enabled")// system.config.websocket-enabled = true 才会自动装配
@EnableWebSocketMessageBroker // 启用WebSocket消息代理功能和配置STOMP协议实现实时双向通信和消息传递
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final AuthChannelInterceptor authChannelInterceptor;
/**
* 配置和注册WebSocket端点(endpoints)
* 注册一个端点,客户端通过这个端点进行连接
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
@@ -35,9 +39,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* 配置消息代理(Message Broker)
* <p>
* 设置消息传输的规则和前缀
* 配置消息代理
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
@@ -51,4 +53,14 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
registry.setUserDestinationPrefix("/user");
}
/**
* 配置客户端入站通道拦截器
*
* @param registration 通道注册器
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(authChannelInterceptor);
}
}

View File

@@ -1,41 +1,26 @@
package com.youlai.system.controller;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.common.result.Result;
import com.youlai.system.common.util.RequestUtils;
import com.youlai.system.security.captcha.EasyCaptchaService;
import com.youlai.system.model.dto.CaptchaResult;
import com.youlai.system.model.dto.LoginResult;
import com.youlai.system.security.JwtTokenManager;
import io.jsonwebtoken.Claims;
import com.youlai.system.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
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.web.bind.annotation.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Tag(name = "01.认证中心")
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenManager jwtTokenManager;
private final EasyCaptchaService easyCaptchaService;
private final RedisTemplate redisTemplate;
private final AuthService authService;
@Operation(summary = "登录")
@PostMapping("/login")
@@ -43,46 +28,21 @@ public class AuthController {
@Parameter(description = "用户名", example = "admin") @RequestParam String username,
@Parameter(description = "密码", example = "123456") @RequestParam String password
) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username.toLowerCase().trim(),
password
);
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 生成token
String accessToken = jwtTokenManager.createToken(authentication);
LoginResult loginResult = LoginResult.builder()
.tokenType("Bearer")
.accessToken(accessToken)
.build();
LoginResult loginResult = authService.login(username, password);
return Result.success(loginResult);
}
@Operation(summary = "注销", security = {@SecurityRequirement(name = SecurityConstants.TOKEN_KEY)})
@DeleteMapping("/logout")
public Result logout(HttpServletRequest request) {
String token = RequestUtils.resolveToken(request);
if (StrUtil.isNotBlank(token)) {
Claims claims = jwtTokenManager.getTokenClaims(token);
String jti = claims.get("jti", String.class);
Date expiration = claims.getExpiration();
if (expiration != null) {
// 有过期时间在token有效时间内存入黑名单超出时间移除黑名单节省内存占用
long ttl = (expiration.getTime() - System.currentTimeMillis());
redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null, ttl, TimeUnit.MILLISECONDS);
} else {
// 无过期时间,永久加入黑名单
redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null);
}
}
SecurityContextHolder.clearContext();
return Result.success("注销成功");
public Result logout() {
authService.logout();
return Result.success();
}
@Operation(summary = "获取验证码")
@GetMapping("/captcha")
public Result getCaptcha() {
CaptchaResult captcha = easyCaptchaService.getCaptcha();
CaptchaResult captcha = authService.getCaptcha();
return Result.success(captcha);
}

View File

@@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.system.common.constant.ExcelConstants;
import com.youlai.system.common.result.PageResult;
import com.youlai.system.common.result.Result;
import com.youlai.system.common.util.ExcelUtils;
import com.youlai.system.util.ExcelUtils;
import com.youlai.system.common.annotation.PreventDuplicateSubmit;
import com.youlai.system.listener.easyexcel.UserImportListener;
import com.youlai.system.model.vo.UserImportVO;
@@ -128,8 +128,8 @@ public class SysUserController {
@Operation(summary = "获取当前登录用户信息", security = {@SecurityRequirement(name = "Authorization")})
@GetMapping("/me")
public Result<UserInfoVO> getUserLoginInfo() {
UserInfoVO userInfoVO = userService.getUserLoginInfo();
public Result<UserInfoVO> getCurrentUserInfo() {
UserInfoVO userInfoVO = userService.getCurrentUserInfo();
return Result.success(userInfoVO);
}

View File

@@ -1,17 +1,16 @@
package com.youlai.system.controller.demo;
import com.youlai.system.common.result.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
/**
* WebSocket 测试控制器
*
@@ -24,28 +23,35 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
public class WebsocketController {
private final SimpMessagingTemplate messagingTemplate;
/**
* 广播发送消息
*
* @param message 消息内容
*/
@MessageMapping("/sendToAll")
@SendTo("/topic/all")
@SendTo("/topic/notice")
public String sendToAll(String message) {
// 处理消息
return "Hello, " + message + "!";
return "System Notice: " + message;
}
/**
* 点对点发送消息
* <p>
* 模拟 张三 给 李四 发送消息场景
*
* @param principal 当前用户
* @param username 接收消息的用户
* @param message 消息内容
*/
// 处理发送到"/app/sendToUser/{username}"的消息
@MessageMapping("/sendToUser/{username}")
// 将消息处理器的返回值发送到指定用户
@SendTo("/queue/user")
public String sendToUser(@DestinationVariable("username") String username, String message) {
// 处理消息
return "Hello, " + username + ", your message is: " + message;
//@SendToUser(value = "/queue/greeting")
public void sendToUser(Principal principal, @DestinationVariable String username, String message) {
log.info("sender:{};receiver:{}", username, principal.getName());
messagingTemplate.convertAndSendToUser(username, "/queue/greeting", "Hello," + message);
/// return "Hello, " + message;
}
}

View File

@@ -39,7 +39,7 @@ public interface UserConverter {
@Mappings({
@Mapping(target = "userId", source = "id")
})
UserInfoVO entity2UserInfoVo(SysUser entity);
UserInfoVO toUserInfoVo(SysUser entity);
SysUser importVo2Entity(UserImportVO vo);

View File

@@ -1,4 +1,4 @@
package com.youlai.system.common.exception;
package com.youlai.system.exception;
import com.youlai.system.common.result.IResultCode;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package com.youlai.system.common.exception;
package com.youlai.system.exception;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;

View File

@@ -3,8 +3,8 @@ package com.youlai.system.filter;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.RequestUtils;
import com.youlai.system.common.util.ResponseUtils;
import com.youlai.system.util.RequestUtils;
import com.youlai.system.util.ResponseUtils;
import com.youlai.system.security.JwtTokenManager;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;

View File

@@ -5,7 +5,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils;
import com.youlai.system.util.ResponseUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;

View File

@@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.youlai.system.common.annotation.DataPermission;
import com.youlai.system.common.base.IBaseEnum;
import com.youlai.system.common.enums.DataScopeEnum;
import com.youlai.system.common.util.SecurityUtils;
import com.youlai.system.util.SecurityUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;

View File

@@ -0,0 +1,63 @@
package com.youlai.system.interceptor;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.security.JwtTokenManager;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;
import java.security.Principal;
/**
* Websocket 连接认证拦截器
*
* @author haoxr
* @since 2.4.0
*/
@Component
@RequiredArgsConstructor
public class AuthChannelInterceptor implements ChannelInterceptor {
private final JwtTokenManager jwtTokenManager;
/**
* 连接前监听
*
* @param message
* @param channel
* @return
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
assert accessor != null;
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// get token from header
String token = accessor.getFirstNativeHeader("Authorization");
// if token is not null
if (StrUtil.isNotBlank(token)) {
token = token.substring(SecurityConstants.TOKEN_PREFIX.length());
Claims claims = jwtTokenManager.parseAndValidateToken(token);
String username = claims.get("username", String.class);
// if the username is not null, assign it to the Principal.
if (StrUtil.isNotBlank(username)) {
Principal principal = () -> username;
accessor.setUser(principal);
return message;
}
}
}
return ChannelInterceptor.super.preSend(message, channel);
}
}

View File

@@ -18,6 +18,9 @@ public class UserInfoVO {
@Schema(description="用户ID")
private Long userId;
@Schema(description="用户名")
private String username;
@Schema(description="用户昵称")
private String nickname;

View File

@@ -3,7 +3,7 @@ package com.youlai.system.security;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.security.userdetails.SysUserDetails;
import com.youlai.system.security.model.SysUserDetails;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.DecodingException;

View File

@@ -1,60 +0,0 @@
package com.youlai.system.security.captcha;
import com.wf.captcha.*;
import com.wf.captcha.base.Captcha;
import com.youlai.system.config.CaptchaConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.awt.*;
/**
* 验证码生成器
*
* @author haoxr
* @since 2023/03/24
*/
@Component
@RequiredArgsConstructor
public class EasyCaptchaProducer {
private final CaptchaConfig captchaConfig;
public Captcha getCaptcha() {
Captcha captcha;
int width = captchaConfig.getWidth();
int height = captchaConfig.getHeight();
int length = captchaConfig.getLength();
String fontName = captchaConfig.getFontName();
switch (captchaConfig.getType()) {
case ARITHMETIC:
captcha = new ArithmeticCaptcha(width, height);
//固定设置为两位,图片为算数运算表达式
captcha.setLen(2);
break;
case CHINESE:
captcha = new ChineseCaptcha(width, height);
captcha.setLen(length);
break;
case CHINESE_GIF:
captcha = new ChineseGifCaptcha(width, height);
captcha.setLen(length);
break;
case GIF:
captcha = new GifCaptcha(width, height);//最后一位是位数
captcha.setLen(length);
break;
case SPEC:
captcha = new SpecCaptcha(width, height);
captcha.setLen(length);
break;
default:
throw new RuntimeException("验证码配置信息错误!正确配置查看 CaptchaTypeEnum ");
}
captcha.setFont(new Font(fontName, captchaConfig.getFontStyle(), captchaConfig.getFontSize()));
return captcha;
}
}

View File

@@ -1,52 +0,0 @@
package com.youlai.system.security.captcha;
import cn.hutool.core.util.IdUtil;
import com.wf.captcha.base.Captcha;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.config.CaptchaConfig;
import com.youlai.system.model.dto.CaptchaResult;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* EasyCaptcha 业务类
*
* @author haoxr
* @since 2023/03/24
*/
@Component
@RequiredArgsConstructor
public class EasyCaptchaService {
private final EasyCaptchaProducer easyCaptchaProducer;
private final RedisTemplate redisTemplate;
private final CaptchaConfig captchaConfig;
/**
* 获取验证码
*
* @return
*/
public CaptchaResult getCaptcha() {
// 获取验证码
Captcha captcha = easyCaptchaProducer.getCaptcha();
String captchaText = captcha.text(); // 验证码文本
String captchaBase64 = captcha.toBase64(); // 验证码图片Base64字符串
// 验证码文本缓存至Redis用于登录校验
String verifyCodeKey = IdUtil.fastSimpleUUID();
redisTemplate.opsForValue().set(SecurityConstants.VERIFY_CODE_CACHE_PREFIX + verifyCodeKey, captchaText,
captchaConfig.getTtl(), TimeUnit.SECONDS);
return CaptchaResult.builder()
.verifyCodeKey(verifyCodeKey)
.verifyCodeBase64(captchaBase64)
.build();
}
}

View File

@@ -1,7 +1,7 @@
package com.youlai.system.security.exception;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils;
import com.youlai.system.util.ResponseUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

View File

@@ -1,7 +1,7 @@
package com.youlai.system.security.exception;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils;
import com.youlai.system.util.ResponseUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

View File

@@ -1,4 +1,4 @@
package com.youlai.system.security.userdetails;
package com.youlai.system.security.model;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;

View File

@@ -3,12 +3,11 @@ package com.youlai.system.security.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.common.util.SecurityUtils;
import com.youlai.system.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.PatternMatchUtils;
import java.util.Set;

View File

@@ -1,6 +1,7 @@
package com.youlai.system.security.userdetails;
package com.youlai.system.security.service;
import com.youlai.system.model.dto.UserAuthInfo;
import com.youlai.system.security.model.SysUserDetails;
import com.youlai.system.service.SysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;

View File

@@ -0,0 +1,34 @@
package com.youlai.system.service;
import com.youlai.system.model.dto.CaptchaResult;
import com.youlai.system.model.dto.LoginResult;
/**
* 认证服务接口
*
* @author haoxr
* @since 2.4.0
*/
public interface AuthService {
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 登录结果
*/
LoginResult login(String username, String password);
/**
* 登出
*/
void logout();
/**
* 获取验证码
*
* @return 验证码
*/
CaptchaResult getCaptcha();
}

View File

@@ -98,5 +98,5 @@ public interface SysUserService extends IService<SysUser> {
*
* @return
*/
UserInfoVO getUserLoginInfo();
UserInfoVO getCurrentUserInfo();
}

View File

@@ -0,0 +1,103 @@
package com.youlai.system.service.impl;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.GifCaptcha;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.service.AuthService;
import com.youlai.system.util.RequestUtils;
import com.youlai.system.model.dto.CaptchaResult;
import com.youlai.system.model.dto.LoginResult;
import com.youlai.system.security.JwtTokenManager;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
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.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @author haoxr
* @since 2.4.0
*/
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final JwtTokenManager jwtTokenManager;
private final RedisTemplate redisTemplate;
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 登录结果
*/
@Override
public LoginResult login(String username, String password) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username.toLowerCase().trim(), password);
Authentication authentication = authenticationManager.authenticate(authenticationToken);
String accessToken = jwtTokenManager.createToken(authentication);
return LoginResult.builder()
.tokenType("Bearer")
.accessToken(accessToken)
.build();
}
/**
* 登出
*/
@Override
public void logout() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = RequestUtils.resolveToken(request);
if (StrUtil.isNotBlank(token)) {
Claims claims = jwtTokenManager.getTokenClaims(token);
String jti = claims.get("jti", String.class);
Date expiration = claims.getExpiration();
if (expiration != null) {
long ttl = expiration.getTime() - System.currentTimeMillis();
redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null, ttl, TimeUnit.MILLISECONDS);
} else {
redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null);
}
}
SecurityContextHolder.clearContext();
}
/**
* 获取验证码
*
* @return 验证码
*/
@Override
public CaptchaResult getCaptcha() {
// 获取验证码
GifCaptcha captcha = CaptchaUtil.createGifCaptcha(120, 36, 4); // 宽、高、位数
String captchaCode = captcha.getCode(); // 验证码
String captchaBase64 = captcha.getImageBase64Data(); // 验证码图片Base64
// 验证码文本缓存至Redis用于登录校验
String verifyCodeKey = IdUtil.fastSimpleUUID();
redisTemplate.opsForValue().set(SecurityConstants.VERIFY_CODE_CACHE_PREFIX + verifyCodeKey, captchaCode,
120, TimeUnit.SECONDS);
return CaptchaResult.builder()
.verifyCodeKey(verifyCodeKey)
.verifyCodeBase64(captchaBase64)
.build();
}
}

View File

@@ -20,7 +20,7 @@ import com.youlai.system.model.vo.RolePageVO;
import com.youlai.system.service.SysRoleMenuService;
import com.youlai.system.service.SysRoleService;
import com.youlai.system.service.SysUserRoleService;
import com.youlai.system.common.util.SecurityUtils;
import com.youlai.system.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

View File

@@ -11,7 +11,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.converter.UserConverter;
import com.youlai.system.common.util.SecurityUtils;
import com.youlai.system.util.SecurityUtils;
import com.youlai.system.mapper.SysUserMapper;
import com.youlai.system.model.dto.UserAuthInfo;
import com.youlai.system.model.bo.UserBO;
@@ -233,25 +233,29 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
* @return
*/
@Override
public UserInfoVO getUserLoginInfo() {
// 登录用户entity
public UserInfoVO getCurrentUserInfo() {
String username = SecurityUtils.getUser().getUsername(); // 登录用户名
// 获取登录用户基础信息
SysUser user = this.getOne(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUsername, SecurityUtils.getUser().getUsername())
.eq(SysUser::getUsername, username)
.select(
SysUser::getId,
SysUser::getUsername,
SysUser::getNickname,
SysUser::getAvatar
)
);
// entity->VO
UserInfoVO userInfoVO = userConverter.entity2UserInfoVo(user);
UserInfoVO userInfoVO = userConverter.toUserInfoVo(user);
// 用户角色集合
Set<String> roles = SecurityUtils.getRoles();
userInfoVO.setRoles(roles);
// 用户权限集合
Set<String> perms = (Set<String>) redisTemplate.opsForValue().get(SecurityConstants.USER_PERMS_CACHE_PREFIX+ user.getId());
Set<String> perms = (Set<String>) redisTemplate.opsForValue().get(SecurityConstants.USER_PERMS_CACHE_PREFIX + user.getId());
userInfoVO.setPerms(perms);
return userInfoVO;

View File

@@ -1,4 +1,4 @@
package com.youlai.system.common.util;
package com.youlai.system.util;
import com.alibaba.excel.EasyExcel;
import com.youlai.system.listener.easyexcel.MyAnalysisEventListener;

View File

@@ -1,4 +1,4 @@
package com.youlai.system.common.util;
package com.youlai.system.util;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SecurityConstants;

View File

@@ -1,4 +1,4 @@
package com.youlai.system.common.util;
package com.youlai.system.util;
import cn.hutool.json.JSONUtil;
import com.youlai.system.common.result.Result;
@@ -14,7 +14,7 @@ import java.io.IOException;
* 响应工具类
*
* @author haoxr
* @since 2022/10/18
* @since 2.0.0
*/
public class ResponseUtils {

View File

@@ -1,10 +1,10 @@
package com.youlai.system.common.util;
package com.youlai.system.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.security.userdetails.SysUserDetails;
import com.youlai.system.security.model.SysUserDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;