文章目录
- 1、单点登录解决方案
-
- 2、user服务-登录接口
- 2.1、UserController
- 2.2、UserInfoServiceImpl
- 2.3、载荷
- 2.4、响应
- 2.5、Redis Desktop Manager
- 3、user服务-登录成功获取用户信息回显
- 3.1、UserController
- 3.2、UserInfoServiceImpl
- 3.3、响应
- 4、登录状态验证&状态透传分析
- 4.1、gateway-过滤器统一校验登录状态
-
- 4.2、登录状态透传拦截器
- 4.2.1、SpzxServiceAuthInterceptor
- 4.2.2、H5SpzxWebMvcConfigurer
- 4.2.3、@EnableSpzxServiceAuth
- 4.2.4、UserInfoServiceImpl
- 5、登录校验状态透传总结
1、单点登录解决方案
1.1、后端保存登录状态
1.2、token模式
2、user服务-登录接口
2.1、UserController
@Operation( summary = "用户登录接口")
@PostMapping("/login")
public Result login(@RequestBody UserInfo userInfo) {
String token = userInfoService.login(userInfo);
return Result.ok(token);
}
2.2、UserInfoServiceImpl
@Override
public String login(UserInfo userInfo) {
String username = userInfo.getUsername();
String password = userInfo.getPassword();
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new SpzxException(ResultCodeEnum.LOGIN_PARAMS_ERROR, null);
}
UserInfo dbUserInfo = this.getOne(Wrappers.lambdaQuery(UserInfo.class).eq(UserInfo::getUsername, username));
if (dbUserInfo == null) {
throw new SpzxException(ResultCodeEnum.LOGIN_USERNAME_ERROR, null);
}
if (dbUserInfo.getStatus() != 1) {
throw new SpzxException(ResultCodeEnum.LOGIN_USER_STATUS_ERROR, null);
}
String encodePwd = DigestUtils.md5DigestAsHex((
DigestUtils.md5DigestAsHex(password.getBytes()) + dbUserInfo.getSalt()).getBytes());
if (!encodePwd.equals(dbUserInfo.getPassword())) {
throw new SpzxException(ResultCodeEnum.LOGIN_PASSWORD_ERROR, null);
}
String token = IdUtil.getSnowflake(1, 1).nextIdStr();
dbUserInfo.setPassword(null);
dbUserInfo.setSalt(null);
redisTemplate.opsForValue().set("spzx:user:login:" + token, dbUserInfo, 7, TimeUnit.DAYS);
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
return token;
}
2.3、载荷
{
"username": "18947628476",
"password": "123456"
}
2.4、响应
{"code":200,"message":"SUCCESS","data":"1801188891328385024"}
2.5、Redis Desktop Manager
{
"@class": "com.atguigu.spzx.model.entity.h5.UserInfo",
"id": 1774759294596579329,
"createTime": [
"java.util.Date",
"2024-04-01 19:22:44"
],
"updateTime": [
"java.util.Date",
1718243055000
],
"deleted": false,
"username": "18947628476",
"password": null,
"nickName": "小弟",
"avatar": "https://cdn.apifox.com/app/project-icon/builtin/16.jpg",
"sex": null,
"phone": null,
"memo": null,
"openId": null,
"unionId": null,
"lastLoginIp": null,
"lastLoginTime": null,
"status": 1,
"salt": null
}
3、user服务-登录成功获取用户信息回显
3.1、UserController
@Operation( summary = "查询用户信息回显接口")
@GetMapping("/auth/getCurrentUserInfo")
public Result getCurrentUserInfo(@RequestHeader(value = "token",required = false)String token) {
UserInfoVo userInfoVo = userInfoService.getCurrentUserInfo(token);
return Result.ok(userInfoVo);
}
3.2、UserInfoServiceImpl
@Override
public UserInfoVo getCurrentUserInfo(String token) {
UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get("spzx:user:login:" + token);
if (userInfo == null) {
throw new SpzxException(ResultCodeEnum.LOGIN_STATUS_ERROR, null);
}
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setNickName(userInfo.getNickName());
userInfoVo.setAvatar(userInfo.getAvatar());
return userInfoVo;
}
3.3、响应
{
"code": 200,
"message": "SUCCESS",
"data": {
"nickName": "小弟",
"avatar": "https://cdn.apifox.com/app/project-icon/builtin/16.jpg"
}
}
4、登录状态验证&状态透传分析
4.1、gateway-过滤器统一校验登录状态
4.1.1、GlobalAuthFilter
package com.atguigu.spzx.gateway.filter;
import com.alibaba.nacos.shaded.com.google.gson.JsonObject;
import com.atguigu.spzx.model.result.ResultCodeEnum;
import jakarta.annotation.Resource;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class GlobalAuthFilter implements GlobalFilter, Ordered {
@Resource
private RedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
AntPathMatcher matcher = new AntPathMatcher();
if (!matcher.match("/**/auth/**", path)) {
return chain.filter(exchange);
}
String token = request.getHeaders().getFirst("token");
if (StringUtils.isEmpty(token) || !redisTemplate.hasKey("spzx:user:login:" + token)) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("code", ResultCodeEnum.LOGIN_STATUS_ERROR.getCode());
jsonObject.addProperty("message", ResultCodeEnum.LOGIN_STATUS_ERROR.getMessage());
String json = jsonObject.toString();
DataBuffer buffer = response.bufferFactory().wrap(json.getBytes());
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
4.2、登录状态透传拦截器
4.2.1、SpzxServiceAuthInterceptor
package com.atguigu.spzx.common.handler.interceptor;
import com.atguigu.spzx.model.entity.h5.UserInfo;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class SpzxServiceAuthInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate redisTemplate;
public static ThreadLocal<UserInfo> THREAD_LOCAL = new ThreadLocal();
@Override
public boolean preHandle(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get("spzx:user:login:" + token);
THREAD_LOCAL.set(userInfo);
return true;
}
@Override
public void afterCompletion(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
THREAD_LOCAL.remove();
}
}
4.2.2、H5SpzxWebMvcConfigurer
package com.atguigu.spzx.common.handler.interceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class H5SpzxWebMvcConfigurer implements WebMvcConfigurer {
@Resource
private SpzxServiceAuthInterceptor spzxServiceAuthInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(spzxServiceAuthInterceptor).addPathPatterns("/**/auth/**");
}
}
4.2.3、@EnableSpzxServiceAuth
package com.atguigu.spzx.common.handler.interceptor;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(value = {SpzxServiceAuthInterceptor.class, H5SpzxWebMvcConfigurer.class})
public @interface EnableSpzxServiceAuth {
}
4.2.4、UserInfoServiceImpl
@Override
public UserInfoVo getCurrentUserInfo(String token) {
UserInfo userInfo = SpzxServiceAuthInterceptor.THREAD_LOCAL.get();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setNickName(userInfo.getNickName());
userInfoVo.setAvatar(userInfo.getAvatar());
return userInfoVo;
}
5、登录校验状态透传总结