简单的Java的token鉴权架构
总体时序图
1、登录
服务端校验密码,成功后存储token到redis(失效时间为1天)并返回token给客户端
2、访问API
通过自定义拦截器,拦截所有请求,并对需要鉴权的API做token鉴权逻辑处理,存在改token则通过请求,不存在则需要做刷新token或生成token处理
鉴权流程
1、创建token
用户传入appid和appkey来生成token,服务端通过appid和appkey来到mysql中查找该用户的接口权限,如果满足访问该接口权限则生成token并存储到redis,失效时间为1天,并返回给客户端
2、查询token
用户通过appid和appkey来查询token,服务端通过appid和appkey来到mysql中查找该用户的接口权限,如果满足访问该接口权限则根据appid到redis中获取当前的token,并返回给客户端
3、注销token
用户通过appid和appkey来查询token,服务端通过appid和appkey来到mysql中查找该用户的接口权限,如果满足访问该接口权限则到redis中删除对应的token信息
代码参考
自定义用户鉴权注解
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author gk * @note 加上该注解的类http请求时需要进行token鉴权 * @date 2021/9/16 10:14 */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface AuthToken {}
创建、查询、注销接口
import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import java.util.HashMap;import java.util.Map;/** * @author gk * @note token的controller * @date 2021/6/26 11:14 */@RestController@RequestMapping(value = "/usr/token", produces = "application/json; charset=utf-8")public class TokenChargingController {private final TokenService tokenService;public TokenChargingController(TokenService tokenService) {this.tokenService = tokenService;}/** * 生成我们自己的token * @return */@RequestMapping(value = "/create_token", method = RequestMethod.GET)@ApiOperation(value = "生成token", notes = "生成token值")public Object createToken(HttpServletRequest request,@ApiParam(name = "appid", value = "appid", required = true) @RequestHeader(value = "appid") String appid,@ApiParam(name = "appkey", value = "appkey", required = true) @RequestHeader(value = "appkey") String appkey) {Map<String, Object> resultMap = new HashMap<>();CacheService cacheService = = null;try {cacheService = new JedisPoolUtils();// 初始化:URI、URL、addrString uri = request.getRequestURI();resultMap.put("AppId", appid);resultMap.put("AppKey", appkey);// 查询用户权限是否有效:appid、appkey、uriApiUserUriMapping apiUserUriMapping = this.tokenService.getApiUserUriMapping(appid, appkey, uri);if (apiUserUriMapping == null) {resultMap.put("StatusCode", CommonEnum.SIGNATURE_NOT_MATCH.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", CommonEnum.SIGNATURE_NOT_MATCH.getResultMsg());} else {// 创建tokenString token = this.tokenService.createToken(cacheService, appid, appkey);resultMap.put("StatusCode", CommonEnum.SUCCESS.getResultCode());resultMap.put("AccessToken", token);resultMap.put("TokenAvailableTime", cacheService.ttl(token, RedisDbConstant.DB_10) + "");resultMap.put("Msg", CommonEnum.SUCCESS.getResultMsg());}} catch (Exception e) {resultMap.put("StatusCode", CommonEnum.INTERNAL_SERVER_ERROR.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", CommonEnum.INTERNAL_SERVER_ERROR.getResultMsg());} finally {if (Objects.nonNull(cacheService))cacheService.destroy();}return resultMap;}/** * 查询token * @return */@RequestMapping(value = "/query_token", method = RequestMethod.GET)@ApiOperation(value = "获取token", notes = "获取token值")public Object queryToken(HttpServletRequest request,@ApiParam(name = "appid", value = "appid", required = true) @RequestHeader(value = "appid") String appid,@ApiParam(name = "appkey", value = "appkey", required = true) @RequestHeader(value = "appkey") String appkey) {Map<String, Object> resultMap = new HashMap<>();CacheService cacheService = null;try {cacheService = new JedisPoolUtils();// 初始化:URI、URL、addrString uri = request.getRequestURI();resultMap.put("AppId", appid);resultMap.put("AppKey", appkey);// 查询用户权限是否有效:appid、appkey、uriApiUserUriMapping apiUserUriMapping = this.tokenService.getApiUserUriMapping(appid, appkey, uri);if (apiUserUriMapping == null) {resultMap.put("StatusCode", CommonEnum.SIGNATURE_NOT_MATCH.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", CommonEnum.SIGNATURE_NOT_MATCH.getResultMsg());} else {// 查询tokenString token = this.tokenService.queryToken(cacheService, appid);resultMap.put("StatusCode", CommonEnum.SUCCESS.getResultCode());resultMap.put("AccessToken", token);resultMap.put("TokenAvailableTime", cacheService.ttl(token, RedisDbConstant.DB_10) + "");resultMap.put("Msg", CommonEnum.SUCCESS.getResultMsg());}} catch (Exception e) {resultMap.put("StatusCode", CommonEnum.INTERNAL_SERVER_ERROR.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", CommonEnum.INTERNAL_SERVER_ERROR.getResultMsg());} finally {if (Objects.nonNull(cacheService))cacheService.destroy();}return resultMap;}@RequestMapping(value = "/logout_user", method = RequestMethod.GET)@ApiOperation(value = "用户注销", notes = "用户注销")public Object logoutUser(HttpServletRequest request,@ApiParam(name = "appid", value = "appid", required = true) @RequestHeader(value = "appid") String appid,@ApiParam(name = "appkey", value = "appkey", required = true) @RequestHeader(value = "appkey") String appkey) {Map<String, Object> resultMap = new HashMap<>();CacheService cacheService = null;try {cacheService = new JedisPoolUtils();// 初始化:URI、URL、addrString uri = request.getRequestURI();resultMap.put("AppId", appid);resultMap.put("AppKey", appkey);// 查询用户权限是否有效:appid、appkey、uriApiUserUriMapping apiUserUriMapping = this.tokenService.getApiUserUriMapping(appid, appkey, uri);if (apiUserUriMapping == null) {resultMap.put("StatusCode", CommonEnum.SIGNATURE_NOT_MATCH.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", CommonEnum.SIGNATURE_NOT_MATCH.getResultMsg());} else {// 创建tokenboolean isSuccess = this.tokenService.logoutUser(cacheService, appid);resultMap.put("StatusCode", isSuccess " />CommonEnum.SUCCESS.getResultCode() : CommonEnum.INTERNAL_SERVER_ERROR.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", isSuccess ? CommonEnum.SUCCESS.getResultMsg() : CommonEnum.INTERNAL_SERVER_ERROR.getResultMsg());}} catch (Exception e) {resultMap.put("StatusCode", CommonEnum.INTERNAL_SERVER_ERROR.getResultCode());resultMap.put("AccessToken", "");resultMap.put("TokenAvailableTime", "");resultMap.put("Msg", CommonEnum.INTERNAL_SERVER_ERROR.getResultMsg());} finally {if (Objects.nonNull(cacheService))cacheService.destroy();}return resultMap;}}
接口服务及实现类
public interface TokenService {/** * 权限验证:查询用户的接口访问权限信息 * * @param appid用户ID * @param appkey 用户KEY * @param uri请求路径URI * @return ApiUserUriMapping */ApiUserUriMapping getApiUserUriMapping(String appid,String appkey,String uri);/** * 创建token * * @param appid * @param appkey * @return token */String createToken(CacheService cacheService, String appid, String appkey) throws Exception;/** * 查询token * * @param appid * @return token */String queryToken(CacheService cacheService, String appid) throws Exception;/** * 查询token * * @param appid * @return token */boolean logoutUser(CacheService cacheService, String appid) throws Exception;}
@Servicepublic class TokenServiceImpl implements TokenService {private final VBApiUserUriMappingMapper vbApiUserUriMappingMapper;@Autowiredpublic ApiUserUriMappingServiceImpl(VBApiUserUriMappingMapper vbApiUserUriMappingMapper) {this.vbApiUserUriMappingMapper = vbApiUserUriMappingMapper;}@Overridepublic ApiUserUriMapping getApiUserUriMapping(String appid, String appkey, String uri) {List<Map> lists = this.getVBApiUserUriMappingByMap(new HashMap<String, String>() {{put("appid", appid);put("appkey", appkey);put("uri", uri);}});return CollectionUtils.isEmpty(lists) ?null : JSONObject.parseObject(JSONObject.toJSONString(lists.get(0)), ApiUserUriMapping.class);}@Overridepublic String createToken(CacheService cacheService, String user, String key) throws Exception {String token = "";Random random = new Random();double v = random.nextDouble();//获取当前时间时间String currentDate = DateUtils.getCurrentDate("yyyy-MM-dd HH:mm:ss");String md = user + "+" + key + "#" + currentDate + "%" + v + "@";//md5加密String mdToken = Md5Util.MD5(md);//MD5二次加密token = Md5Util.MD5(mdToken);//删除老的tokenString oldToken = cacheService.get(TokenConstant.CURRENT_TOKEN_PREFIX + user, RedisDbConstant.DB_10);if (StringUtils.isNotBlank(oldToken))cacheService.del(oldToken, RedisDbConstant.DB_10);//更新rediscacheService.hset(token, UserFieldConstant.APP_ID, user, RedisDbConstant.DB_10);cacheService.hset(token, UserFieldConstant.APP_KEY, key, RedisDbConstant.DB_10);cacheService.hset(token, UserFieldConstant.TOKEN, token, RedisDbConstant.DB_10);cacheService.hset(token, UserFieldConstant.UPDATE_TIME, currentDate, RedisDbConstant.DB_10);//设置失效时间cacheService.expire(token, TokenConstant.ONE_DAY, RedisDbConstant.DB_10);//设置当前用户使用的tokencacheService.set(TokenConstant.ONE_DAY, TokenConstant.CURRENT_TOKEN_PREFIX + user, token, RedisDbConstant.DB_10);return token;}@Overridepublic String queryToken(CacheService cacheService, String appid) throws Exception {String token = "";String redisKey = TokenConstant.CURRENT_TOKEN_PREFIX + appid;if (cacheService.exists(redisKey, RedisDbConstant.DB_10))token = cacheService.get(redisKey, RedisDbConstant.DB_10);return token;}@Overridepublic boolean logoutUser(CacheService cacheService, String appid) throws Exception {boolean isLogout = false;String redisKey = TokenConstant.CURRENT_TOKEN_PREFIX + appid;if (cacheService.exists(redisKey, RedisDbConstant.DB_10))cacheService.del(redisKey, RedisDbConstant.DB_10);isLogout = true;return isLogout ;}private List<Map> getVBApiUserUriMappingByMap(Map map) {return this.vbApiUserUriMappingMapper.getAllByParam(ObjectUtils.isEmpty(map.get("id")) ? null : map.get("id").toString(),ObjectUtils.isEmpty(map.get("appid")) ? null : map.get("appid").toString(),ObjectUtils.isEmpty(map.get("appkey")) ? null : map.get("appkey").toString(),ObjectUtils.isEmpty(map.get("userType")) ? null : map.get("userType").toString(),ObjectUtils.isEmpty(map.get("userName")) ? null : map.get("userName").toString(),ObjectUtils.isEmpty(map.get("userUnit")) ? null : map.get("userUnit").toString(),ObjectUtils.isEmpty(map.get("phone")) ? null : map.get("phone").toString(),ObjectUtils.isEmpty(map.get("createTime")) ? null : map.get("createTime").toString(),ObjectUtils.isEmpty(map.get("updTime")) ? null : map.get("updTime").toString(),ObjectUtils.isEmpty(map.get("beginTime")) ? null : map.get("beginTime").toString(),ObjectUtils.isEmpty(map.get("endTime")) ? null : map.get("endTime").toString(),ObjectUtils.isEmpty(map.get("accessIpList")) ? null : map.get("accessIpList").toString(),ObjectUtils.isEmpty(map.get("uri")) ? null : map.get("uri").toString(),ObjectUtils.isEmpty(map.get("uriUserConfig")) ? null : map.get("uriUserConfig").toString(),ObjectUtils.isEmpty(map.get("formatCls")) ? null : map.get("formatCls").toString(),ObjectUtils.isEmpty(map.get("opType")) ? null : map.get("opType").toString());}}
自定义登录拦截器
/** * @author gk * @note 登陆过滤器 * @date 2021/09/16 11:14 */@Component@Slf4jpublic class LoginInterceptor implements HandlerInterceptor {@Autowiredprivate ApiUserUriService apiUserUriService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();// 如果打上了AuthToken注解则需要验证tokenif (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {CacheService cacheService = new JedisPoolUtils();try {//从header中拿token,并进行解析String token = request.getHeader(TokenConstant.TOKEN);if (StringUtils.isBlank(token)) {reSetResponse(response, CommonEnum.SIGNATURE_NOT_MATCH);return false;}if (!cacheService.exists(token, RedisDbConstant.DB_10)) {reSetResponse(response, CommonEnum.NO_USER);return false;}String uri = request.getRequestURI();String appid = cacheService.hget(token, UserFieldConstant.APP_ID, RedisDbConstant.DB_10);String appkey = cacheService.hget(token, UserFieldConstant.APP_KEY, RedisDbConstant.DB_10);if (StringUtils.isBlank(appid) || StringUtils.isBlank(appkey)) {reSetResponse(response, CommonEnum.NOT_PERMISSION);return false;}// 查询用户权限是否有效:appid、appkey、uriApiUserUriMapping apiUserUriMapping = this.apiUserUriService.getApiUserUriMapping(appid, appkey, uri);if (Objects.isNull(apiUserUriMapping)) {reSetResponse(response, CommonEnum.NOT_PERMISSION);return false;}//dataRange|appid|appkeyString dataRange = apiUserUriMapping.getUriUserConfig();Map<String, String> key = new HashMap<>();key.put("dataRange", dataRange);key.put("appid", appid);key.put("appkey", appkey);key.put("token", token);key.put("className", apiUserUriMapping.getFormatCls());request.setAttribute(TokenConstant.REQUEST_CURRENT_KEY, key);} catch (Exception e) {e.printStackTrace();reSetResponse(response, CommonEnum.INTERNAL_SERVER_ERROR);return false;} finally {cacheService.destroy();}return true;}if (StringUtils.isNotBlank(request.getRequestURI()) && request.getRequestURI().endsWith("/v2/api-docs")) {//swagger自带的api-docs// 查询用户权限是否有效:appid、appkey、uriString appid = request.getHeader("appid");String appkey = request.getHeader("appkey");if (StringUtils.isBlank(appid) || StringUtils.isBlank(appkey)) {reSetResponse(response, CommonEnum.SIGNATURE_NOT_MATCH);return false;}// 查询用户权限是否有效:appid、appkey、uriApiUserUriMapping mapping = this.apiUserUriService.getApiUserUriMapping(appid, appkey, request.getRequestURI());if (Objects.isNull(mapping)) {reSetResponse(response, CommonEnum.NOT_PERMISSION);return false;}}request.setAttribute(TokenConstant.REQUEST_CURRENT_KEY, null);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}/** * 登录拦截 重置响应数据 * * @param response */private void reSetResponse(HttpServletResponse response, CommonEnum commonEnum) {PrintWriter pw = null;try {response.reset();response.setContentType("application/json;charset=UTF-8");ServiceResult serviceResult = new ServiceResult(commonEnum.getResultCode(), commonEnum.getResultMsg());serviceResult.insertResult(null);pw = response.getWriter();pw.write(JSONObject.toJSONString(serviceResult));} catch (Exception e) {e.printStackTrace();} finally {if (pw != null) {pw.flush();pw.close();}}}}
配置类配置
import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;/** * @author gk * @note 配置类 * @date 2021/7/20 11:14 */@Configurationpublic class WebConfigurer implements WebMvcConfigurer {@Resourceprivate LoginInterceptor loginInterceptor;//注册登录拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//添加所需要拦截的接口,这里我们拦截所有,但是//.excludePathPatterns("/**/evcs/v1/query_token/**"); //不拦截登陆和退出请求}//页面跳转@Overridepublic void addViewControllers(ViewControllerRegistry registry) {}//配置静态资源@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {}}
调用方法
在接口方法上使用@AuthToken
该注解