通过appId和appSecret生成accessToken访问api后端接口(接口授权)

  1. 功能点

(1)申请获取appId和appSecret

(2)通过appId和appSecret获取accessToken

(3)mysql和redis进行key-value键值对存储

(4)对需要的接口url进行请求地址拦截

效果图:

(1)正确的accessToken且没有过期(获取后端数据)

(2)没有填入的accessToken(提示为空)

(3)accessToken正确但是已经过了TTL时间(过期),redis进行数据删除。

(4)默认是7200s

具体实现:

(1) 数据库

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 50738 (5.7.38-log)
 Source Host           : localhost:3306
 Source Schema         : coupon

 Target Server Type    : MySQL
 Target Server Version : 50738 (5.7.38-log)
 File Encoding         : 65001

 Date: 20/03/2023 10:55:04
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for m_app
-- ----------------------------
DROP TABLE IF EXISTS `m_app`;
CREATE TABLE `m_app`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `app_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所申请应用名称',
  `app_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'appId',
  `app_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '秘钥',
  `is_flag` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否可用代表状态',
  `access_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '令牌',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of m_app
-- ----------------------------
INSERT INTO `m_app` VALUES (20, '测试', '4xMTxI9p', 'd1124a2acbc0074900e436505ccfa049f9abaa2f', '0', '95818324c9e34ff89d07f99772dee10c');
INSERT INTO `m_app` VALUES (21, '积分优惠券测试', 'W0wrjQ6C', '1bf37c433b627f9d2451d250916c866ee341de86', '0', '24e2fa591d9447c9b2745d84cda3c8da');
INSERT INTO `m_app` VALUES (22, 'test', 'WS09OO3s', 'cfe6b77fb68f213dedeb59f51b1a4808bfdec394', '0', 'a48e4e98314d4fa390ab31d49dd7757e');
INSERT INTO `m_app` VALUES (23, '123', 'tw9PDmTA', 'd35dd6ad355d6d85e8e9bb1682ea99952ca14d85', '0', '0c4734a413b54c86b8864e3f7aa87d60');
INSERT INTO `m_app` VALUES (24, '12345', 'OchZx9yE', '18e5892a2b92c016078eac4d41b25c765588073477c25b42e2c6b75859960e59', '0', '11c624f4e5394f948542673c393a820b');
INSERT INTO `m_app` VALUES (25, '1', 'qiKL6XKK', '9676fd0a49381cbdb3e86ad6547e58328594e349', '0', '7f15452ea17e4b9b9bddb3750952f787');
INSERT INTO `m_app` VALUES (26, '2121', 'hrGqnHC7', '4b684d90573d4bbeb4e2c504b219b3429eacc4d4', '0', '5682c190b8b54fd4904c8bf007e8708e');
INSERT INTO `m_app` VALUES (27, 'ghd', 'O5ELmevg', '0b45dead09e8195053eaeb8b7451a953572bfed5', '0', '3e80b55f73a54a3895be87fe3d334eb3');

SET FOREIGN_KEY_CHECKS = 1;

(2)实体类:

package com.coupon_test.coupon.model;

import com.baomidou.mybatisplus.annotation.TableName;

@TableName("m_app")
public class AppEntity {

    private long id;
    private String appId;
    private String appName;
    private String appSecret;
    private String accessToken;
    private int isFlag;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public String getAppSecret() {
        return appSecret;
    }

    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public int getIsFlag() {
        return isFlag;
    }

    public void setIsFlag(int isFlag) {
        this.isFlag = isFlag;
    }

    @Override
    public String toString() {
        return "AppEntity [id=" + id + ", appId=" + appId + ", appName=" + appName + ", appSecret=" + appSecret
                + ", accessToken=" + accessToken + ", isFlag=" + isFlag + "]";
    }

}




(3)mapper层:

package com.coupon_test.coupon.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.coupon_test.coupon.model.AppEntity;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;


public interface AppMapper extends BaseMapper<AppEntity> {

    @Select("SELECT ID AS ID ,APP_NAME AS appName, app_id as appId, app_secret as appSecret ,is_flag as isFlag , access_token as accessToken from m_app "
            + "where app_id=#{appId} and app_secret=#{appSecret}  ")
    AppEntity findApp(AppEntity appEntity);

    @Select("SELECT ID AS ID ,APP_NAME AS appName, app_id as appId, app_secret as appSecret ,is_flag as isFlag  access_token as accessToken from m_app "
            + "where app_id=#{appId} and app_secret=#{appSecret}  ")
    AppEntity findAppId(@Param("appId") String appId);

    @Update(" update m_app set access_token =#{accessToken} where app_id=#{appId} ")
    int updateAccessToken(@Param("accessToken") String accessToken, @Param("appId") String appId);


    
}

(4)工具类,main方法可以进行测试。

package com.coupon_test.coupon.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.UUID;

/**
 * @Title: AppUtils
 * @Description: 随机产生唯一的app_key和app_secret
 * @date 2023-02-15
 */
public class AppUtils {
    //生成 app_secret 密钥
    private final static String SERVER_NAME = "积分优惠券系统";
    private final static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
            "W", "X", "Y", "Z"};

    /**
     * @Description: <p>
     * 短8位UUID思想其实借鉴微博短域名的生成方式,但是其重复概率过高,而且每次生成4个,需要随即选取一个。
     * 本算法利用62个可打印字符,通过随机生成32位UUID,由于UUID都为十六进制,所以将UUID分成8组,每4个为一组,然后通过模62操作,结果作为索引取出字符,
     * 这样重复率大大降低。
     * 经测试,在生成一千万个数据也没有出现重复,完全满足大部分需求。
     * </p>
     * @date 2023-02-15
     */
    public static String getAppId() {
        StringBuffer shortBuffer = new StringBuffer();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        for (int i = 0; i < 8; i++) {
            String str = uuid.substring(i * 4, i * 4 + 4);
            int x = Integer.parseInt(str, 16);
            shortBuffer.append(chars[x % 0x3E]);
        }
        return shortBuffer.toString();

    }

    /**
     * <p>
     * 通过appId和内置关键词生成APP Secret
     * </P>
     *
     * @date 2023-02-15
     */
    public static String getAppSecret(String appId) {
        try {
            String[] array = new String[]{appId, SERVER_NAME};
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < array.length; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    public static void main(String[] args) {
        String appId = getAppId();
        System.out.println(appId);
        System.out.println(getAppSecret(appId));



    }
}

(5)请求拦截

package com.coupon_test.coupon.config;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.coupon_test.coupon.service.BaseApiService;
import com.coupon_test.coupon.service.BaseRedisService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONObject;

//验证AccessToken 是否正确
@Component
public class AccessTokenInterceptor extends BaseApiService implements HandlerInterceptor {
    @Autowired
    private BaseRedisService baseRedisService;

    /**
     * 进入controller层之前拦截请求
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @return
     * @throws Exception
     */

    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)
            throws Exception {
        System.out.println("---------------------开始进入请求地址拦截----------------------------");
        String accessToken = httpServletRequest.getParameter("accessToken");
        // 判断accessToken是否空
        if (StringUtils.isEmpty(accessToken)) {
            // 参数Token accessToken
            resultError(" this is parameter accessToken null ", httpServletResponse);
            return false;
        }
        String appId = (String) baseRedisService.getString(accessToken);
        if (StringUtils.isEmpty(appId)) {
            // accessToken 已经失效!
            resultError(" this is  accessToken Invalid ", httpServletResponse);
            return false;
        }
        // 正常执行业务逻辑...
        return true;
    }

    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o,
                           ModelAndView modelAndView) throws Exception {
        System.out.println("--------------处理请求完成后视图渲染之前的处理操作---------------");
    }

    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
        System.out.println("---------------视图渲染之后的操作-------------------------");
    }

    // 返回错误提示
    public void resultError(String errorMsg, HttpServletResponse httpServletResponse) throws IOException {
        PrintWriter printWriter = httpServletResponse.getWriter();
        printWriter.write(new JSONObject().toJSONString(setResultError(errorMsg)));
    }

}

(6)配置类

package com.coupon_test.coupon.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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 WebAppConfig {
    @Autowired
    private AccessTokenInterceptor accessTokenInterceptor;

    @Bean
    public WebMvcConfigurer WebMvcConfigurer() {
        return new WebMvcConfigurer() {
            public void addInterceptors(InterceptorRegistry registry) {
                //  /openApi   下的所有接⼝
                registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/*");
                registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/*/*");
                registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/*/*/*");
                registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/*/*/*/*");
                registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/*/*/*/*/*");
            }

            ;
        };
    }
}
package com.coupon_test.coupon.service;
import com.coupon_test.coupon.common.constants.Constants;
import com.coupon_test.coupon.model.system.ResponseBase;
import org.springframework.stereotype.Component;


@Component
public class BaseApiService {

    public ResponseBase setResultError(Integer code, String msg) {
        return setResult(code, msg, null);
    }

    // 返回错误,可以传msg
    public ResponseBase setResultError(String msg) {
        return setResult(Constants.HTTP_RES_CODE_500, msg, null);
    }

    // 返回成功,可以传data值
    public ResponseBase setResultSuccessData(Object data) {
        return setResult(Constants.HTTP_RES_CODE_200, Constants.HTTP_RES_CODE_200_VALUE, data);
    }

    public ResponseBase setResultSuccessData(Integer code, Object data) {
        return setResult(code, Constants.HTTP_RES_CODE_200_VALUE, data);
    }

    // 返回成功,沒有data值
    public ResponseBase setResultSuccess() {
        return setResult(Constants.HTTP_RES_CODE_200, Constants.HTTP_RES_CODE_200_VALUE, null);
    }

    // 返回成功,沒有data值
    public ResponseBase setResultSuccess(String msg) {
        return setResult(Constants.HTTP_RES_CODE_200, msg, null);
    }

    // 通用封装
    public ResponseBase setResult(Integer code, String msg, Object data) {
        return new ResponseBase(code, msg, data);
    }

}
package com.coupon_test.coupon.service;


import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class BaseRedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * TODO    添加/更新
     * @param key  键
     * @param data  值
     * @param timeout 时间(秒)
     * @return void
     */
    public void setString(String key, Object data, Long timeout) {
        if (data instanceof String) {
            String value = (String) data;
            stringRedisTemplate.opsForValue().set(key, value);
        }
        if (timeout != null) {
            //重新设置过期时间,刷新时间
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * TODO    读取
     * @param key 键
     * @return java.lang.Object
     */
    public Object getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * TODO    删除
     * @param key 键
     * @return void
     */
    public void delKey(String key) {
        stringRedisTemplate.delete(key);
    }
}

(7)controller层

package com.coupon_test.coupon.controller;

import com.coupon_test.coupon.dao.AppMapper;
import com.coupon_test.coupon.model.AppEntity;
import com.coupon_test.coupon.model.system.ResponseBase;
import com.coupon_test.coupon.service.BaseApiService;
import com.coupon_test.coupon.service.BaseRedisService;
import com.coupon_test.coupon.utils.AppUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.fastjson.JSONObject;

import java.util.*;


// 创建获取getAccessToken
@RestController
@RequestMapping(value = "/auth")
public class AuthController extends BaseApiService {
    @Autowired
    private BaseRedisService baseRedisService;

    private long timeToken = 60 * 60 * 2;
    @Autowired
    private AppMapper appMapper;

    // 使用appId+appSecret 生成AccessToke
    @RequestMapping(value = "/getAccessToken", method = RequestMethod.GET)
    public ResponseBase getAccessToken(AppEntity appEntity) {
        AppEntity appResult = appMapper.findApp(appEntity);
        if (appResult == null) {
            return setResultError("没有对应机构的认证信息");
        }
        int isFlag = appResult.getIsFlag();
        if (isFlag == 1) {
            return setResultError("您现在没有权限生成对应的AccessToken");
        }
        // ### 获取新的accessToken 之前删除之前老的accessToken
        // 从redis中删除之前的accessToken
        String accessToken = appResult.getAccessToken();
        baseRedisService.delKey(accessToken);
        // 生成的新的accessToken
        String newAccessToken = newAccessToken(appResult.getAppId());
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("accessToken", newAccessToken);
        return setResultSuccessData(jsonObject);
    }


    // 使用appId+appSecret 生成AccessToke
    @RequestMapping(value = "/insertApp", method = RequestMethod.POST)
    public ResponseBase save(AppEntity appEntity) {
        String appId = AppUtils.getAppId();
        String appSecret = AppUtils.getAppSecret(appId);
        AppEntity appEntity1 = new AppEntity();
        appEntity1.setIsFlag(0);
        appEntity1.setAppName(appEntity.getAppName());
        appEntity1.setAppId(appId);
        appEntity1.setAppSecret(appSecret);
        appEntity1.setAccessToken("");
        int insert = appMapper.insert(appEntity1);
        if (insert == 1) {
            return setResultSuccessData("appId是" + appId + "-----" + "appSecret是" + appSecret);
        } else {
            return setResultError("添加失败");
        }
    }

    private String newAccessToken(String appId) {
        // 使用appid+appsecret 生成对应的AccessToken 保存两个小时
//        String accessToken = TokenUtils.getAccessToken();
        String accessToken = UUID.randomUUID().toString().replace("-", "");
        // 保证在同一个事物redis 事物中
        // 生成最新的token key为accessToken value 为 appid
        baseRedisService.setString(accessToken, appId, timeToken);
        // 表中保存当前accessToken
        appMapper.updateAccessToken(accessToken, appId);
        return accessToken;
    }


}

(8)依赖

   <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>
   <!--字符串操作-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.2.1</version>
        </dependency>

先使用controller获取appId及appSecret, 接着申请accessToken并带至请求中,获取返回数据。

(二)、将请求的http协议改为https

效果图:postman测试

  1. 首先必须的依赖是

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 使用命令获取SSL文件

keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
  1. application.yml文件配置

server:
  port: 8081
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-type: PKCS12
    key-alias: tomcat
    key-store-password: caojun

注意是key-store-password

猜你喜欢

转载自blog.csdn.net/weixin_46085718/article/details/129663261