欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > 单体服务系统认证

单体服务系统认证

2025/8/17 7:46:43 来源:https://blog.csdn.net/m0_37978198/article/details/139902699  浏览:    关键词:单体服务系统认证

上一节讲了如何使用JWT生成令牌,下面说说单体服务认证基本流程。

认证流程

流程图:
在这里插入图片描述
流程描述:

  1. 用户输入登录信息,客户端(Web/APP等)发起登录请求;
  2. 服务端校验该用户是否有效,用户有效则返回令牌信息,用户无效则返回用户不存在;
  3. 客户端收到令牌信息后,将其存储到客户端(如:cookie中);
  4. 客户端端请求服务器资源数据,在有头信息中携带令牌信息;
  5. 服务器拦截器校验该令牌是否有效,有效的话则返回请求的资源数据;
  6. 若令牌认证失败401,则携带刷新令牌,请求刷新令牌的接口;
  7. 服务端校验该刷新令牌是否有效,有效的话则返回新的令牌信息给客户端;
  8. 无效的话,则返回刷新令牌过期402给客户端,客户端跳转到用户登录页;
  9. 用户重新登录。

如何实现

  1. 集成上一节令牌SDK
<dependency><groupId>com.angel.ocean</groupId><artifactId>ocean-auth-core</artifactId><version>1.0.0</version></dependency>
  1. 定义拦截器 AuthFilter,去校验令牌是否有效,认证失败则返回401
package com.angel.ocean.filter;import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.angel.ocean.common.ApiResult;
import com.angel.ocean.constant.NoAuthUrl;
import com.angel.ocean.constant.ResultCode;
import com.angel.ocean.constant.redis.RedisCacheKey;
import com.angel.ocean.token.TokenUtil;
import com.angel.ocean.util.SecurityUtil;
import com.angel.ocean.util.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.FilterConfig;
import java.io.IOException;/**1. 认证Filter*/
@Slf4j
public class AuthFilter implements Filter {@Resourceprivate RedissonClient redissonClient;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());this.redissonClient = context.getBean(RedissonClient.class);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 放开不需要认证的APIString apiUrl = ServletUtil.getRequest().getRequestURI();if(NoAuthUrl.contains(apiUrl)) {filterChain.doFilter(servletRequest, servletResponse);return;}// Token有效性校验String token = SecurityUtil.getToken();if(StrUtil.isEmpty(token)) {// 未携带令牌,认证失败返回401responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));return;}if((TokenUtil.isExpired(token))) {// 携带令牌但是该令牌已过期,认证失败返回401responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));return;}Long userId = SecurityUtil.getUserId();RBucket<String> accessTokenCache = redissonClient.getBucket(RedisCacheKey.accessTokenKey(userId));if(!accessTokenCache.isExists()) {// 携带令牌,用户已退出登录导致令牌失效,认证失败返回401responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));return;}String oldToken = accessTokenCache.get();if(!oldToken.equals(token)) {// 该用户已经有了新令牌,老令牌过期了,使用老令牌认证,认证失败返回401responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));return;}filterChain.doFilter(servletRequest, servletResponse);}public void responseWrite(ServletResponse response, Object data) {try {response.setCharacterEncoding("utf-8");response.setContentType("application/json; charset=utf-8");response.getWriter().write(JSON.toJSONString(data));} catch (IOException e) {log.error("responseWrite() error:", e);}}
}
  1. 定义与认证和用户有关的相关接口
  • 登录接口
  • 获取用户信息的接口
  • 刷新令牌的接口
  • 用户退出接口
package com.angel.ocean.controller;import com.angel.ocean.common.ApiResult;
import com.angel.ocean.domain.dto.LoginDTO;
import com.angel.ocean.service.AuthService;
import com.angel.ocean.token.model.TokenInfo;
import com.angel.ocean.token.model.UserInfo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;/*** 鉴权认证相关*/
@RestController
@RequestMapping("user")
public class AuthController {@Resourceprivate AuthService authService;/*** 用户登录* @param dto* @return*/@PostMapping("login")public ApiResult<TokenInfo> login(@RequestBody LoginDTO dto) {return authService.login(dto);}/*** 获取用户信息* @return*/@GetMapping("info")public ApiResult<UserInfo> userInfo() {return authService.userInfo();}/*** 刷新token* @param refreshToken* @return*/@PostMapping("token/refresh")public ApiResult<TokenInfo> refreshToken(@RequestBody String refreshToken) {return authService.refreshToken(refreshToken);}/*** 用户退出* @return*/@PostMapping("logout")public ApiResult logout() {return authService.logout();}
}

业务实现

package com.angel.ocean.service.impl;import cn.hutool.core.util.StrUtil;
import com.angel.ocean.common.ApiResult;
import com.angel.ocean.constant.ResultCode;
import com.angel.ocean.constant.redis.RedisCacheKey;
import com.angel.ocean.domain.dto.LoginDTO;
import com.angel.ocean.domain.entity.SysUser;
import com.angel.ocean.mapper.SysUserMapper;
import com.angel.ocean.token.TokenUtil;
import com.angel.ocean.token.model.TokenInfo;
import com.angel.ocean.token.model.UserInfo;
import com.angel.ocean.util.SecurityUtil;
import com.angel.ocean.service.AuthService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;/**1. 鉴权认证相关*/
@Service
public class AuthServiceImpl implements AuthService {@Resourceprivate RedissonClient redissonClient;@Resourceprivate SysUserMapper sysUserMapper;@Overridepublic ApiResult<TokenInfo> login(LoginDTO dto) {// 参数非空校验if(StrUtil.isEmpty(dto.getUsername()) || StrUtil.isEmpty(dto.getPassword())) {return ApiResult.error(ResultCode.PARAM_ERROR);}QueryWrapper<SysUser> query = new QueryWrapper<>();query.eq("username", dto.getUsername());query.eq("password", dto.getPassword());SysUser sysUser = sysUserMapper.selectOne(query);if(null == sysUser) {return ApiResult.error(ResultCode.USER_NOT_EXIST);}UserInfo userInfo = new UserInfo();userInfo.setName(sysUser.getUsername());userInfo.setUid(sysUser.getId());TokenInfo tokenInfo = TokenUtil.generateToken(userInfo);setTokenCache(sysUser.getId(), tokenInfo);return ApiResult.success(tokenInfo);}@Overridepublic ApiResult<UserInfo> userInfo() {UserInfo userInfo = SecurityUtil.getLoginUser();return ApiResult.success(userInfo);}@Overridepublic ApiResult<TokenInfo> refreshToken(String refreshToken) {// 刷新令牌有效性校验if(TokenUtil.isExpired(refreshToken)) {return ApiResult.error(ResultCode.AUTHORIZED_EXPIRED);}TokenInfo tokenInfo = TokenUtil.refreshToken(refreshToken);Long userId = TokenUtil.getUserInfoByToken(refreshToken).getUid();setTokenCache(userId, tokenInfo);return ApiResult.success(tokenInfo);}@Overridepublic ApiResult logout() {Long userId = SecurityUtil.getUserId();if(null != userId) {removeTokenCache(userId);}return ApiResult.success();}/*** 设置令牌缓存* @param userId* @param tokenInfo*/private void setTokenCache(Long userId, TokenInfo tokenInfo) {RBucket<String> accessTokenCache = redissonClient.getBucket(RedisCacheKey.accessTokenKey(userId));accessTokenCache.set(tokenInfo.getAccessToken());accessTokenCache.expireAt(new Date(tokenInfo.getAccessTokenExpireIn() * 1000));RBucket<String> refreshTokenCache = redissonClient.getBucket(RedisCacheKey.refreshTokenKey(userId));refreshTokenCache.set(tokenInfo.getRefreshToken());refreshTokenCache.expireAt(new Date(tokenInfo.getRefreshTokenExpireIn() * 1000));}/*** 删除令牌缓存* @param userId*/private void removeTokenCache(Long userId) {RBucket<String> accessTokenCache = redissonClient.getBucket(RedisCacheKey.accessTokenKey(userId));accessTokenCache.delete();RBucket<String> refreshTokenCache = redissonClient.getBucket(RedisCacheKey.refreshTokenKey(userId));refreshTokenCache.delete();}
}
  1. 用户信息的工具类SecurityUtil,给其他业务使用
package com.angel.ocean.util;import com.angel.ocean.token.TokenUtil;
import com.angel.ocean.token.model.UserInfo;public class SecurityUtil {private SecurityUtil() {}public static String getUsername() {UserInfo userInfo = getLoginUser();String username = userInfo.getName();return ServletUtil.urlDecode(username);}public static Long getUserId() {UserInfo userInfo = getLoginUser();return userInfo.getUid();}public static UserInfo getLoginUser() {UserInfo userInfo = TokenUtil.getUserInfoByToken(getToken());return userInfo;}public static String getToken() {String token = ServletUtil.getRequest().getHeader("Authorization");return token;}
}
package com.angel.ocean.util;import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;public class ServletUtil {private ServletUtil() {}public static String getParameter(String name) {return getRequest().getParameter(name);}public static String getParameter(String name, String defaultValue) {return Convert.toStr(getRequest().getParameter(name), defaultValue);}public static Integer getParameterToInt(String name) {return Convert.toInt(getRequest().getParameter(name));}public static Integer getParameterToInt(String name, Integer defaultValue) {return Convert.toInt(getRequest().getParameter(name), defaultValue);}public static HttpServletRequest getRequest() {try {return getRequestAttributes().getRequest();} catch (Exception var1) {return null;}}public static HttpServletResponse getResponse() {try {return getRequestAttributes().getResponse();} catch (Exception var1) {return null;}}public static HttpSession getSession() {return getRequest().getSession();}public static ServletRequestAttributes getRequestAttributes() {try {RequestAttributes attributes = RequestContextHolder.getRequestAttributes();return (ServletRequestAttributes)attributes;} catch (Exception var1) {return null;}}public static Map<String, String> getHeaders(HttpServletRequest request) {Map<String, String> map = new LinkedHashMap();Enumeration<String> enumeration = request.getHeaderNames();if (enumeration != null) {while(enumeration.hasMoreElements()) {String key = (String)enumeration.nextElement();String value = request.getHeader(key);map.put(key, value);}}return map;}public static String renderString(HttpServletResponse response, String string) {try {response.setStatus(200);response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().print(string);} catch (IOException var3) {var3.printStackTrace();}return null;}public static boolean isAjaxRequest(HttpServletRequest request) {String accept = request.getHeader("accept");if (accept != null && accept.indexOf("application/json") != -1) {return true;} else {String xRequestedWith = request.getHeader("X-Requested-With");if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {return true;} else {String uri = request.getRequestURI();if (StrUtil.containsAnyIgnoreCase(uri, new String[]{".json", ".xml"})) {return true;} else {String ajax = request.getParameter("__ajax");return StrUtil.containsAnyIgnoreCase(ajax, new String[]{"json", "xml"});}}}}public static String urlDecode(String str) {try {return URLDecoder.decode(str, "UTF-8");} catch (UnsupportedEncodingException var2) {return "";}}
}

运行演示

  1. 登录接口

注意:由于是演示使用,用户名字是明文(不安全),在项目开发过程中,一定要先在客户端加密,然后服务区解密。
在这里插入图片描述

  1. 刷新令牌接口

在这里插入图片描述

  1. 获取用户信息接口,携带有效令牌

在这里插入图片描述

  1. 获取用户信息, 携带无效令牌

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词