第四章

注意:需要更换当天的前端代码, 前端代码有改动

1. 登录滑块验证

1.1 接口说明

接口url:/buyer/common/slider/{verificationEnums}

请求方式:GET 获取校验接口,得到滑块验证相关的图片

请求方式:POST 进行校验 (预校验,如果后续还有操作,可以从缓存中获取校验结果)

请求参数:

参数名称参数类型说明
verificationEnumsint认证类型(路径参数)
uuidStringHeader参数 用于验证身份
xPosint滑块移动的x轴坐标 POST请求用到

返回数据:

GET:

{"success":true,"message":"success","code":2000000000,"result":{"slidingImage":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEqklEQVR42u2ZiVIbMQyGebUWSLL3mZscEKB9DN7c9a9dbRTjXJR1Mx1r5p8QkoH99pdk2Xt358OHDx8+/pP4+PhQ18rl3/t20DjKSUlcqCytGiWlSvTvovCzTl0gPgtGaafBMFajIFWPg0g9PIb650TdP4zUj58DFYSpms1XbsDxx9OsUkU+VnlWd+L3AE71DUjislMcFaRjF8YXHQZZBzwcJRosI3BAm8DzxVotlhv1tNr0Dwwny2JCkIWGLPVrpd+X7XtAZ2lNSpOKBPBzDjPwaJjcBjCnMVI4ReoilfVrjhtgQBf5RDs+JgH80vqVwKH+X/8cONFwuQbL8poEl+E2O57n2t2s2Ke4/v6lF9S4jJROqOYBDjXOJ+rxISAN9Q2Yz1Zqudiozea5f+BMw6COTWCCLiqCpnRvoa8BblzeAw8Hcet6TLAP9yMCXszX6mm5VdvtS7/AEdJZQyRpSdAAksBlWRM0O37txTAwXAWsBAYsNNBdG8Crp2f1/LzrFxh1BZejOCdo7tBVOW1UjQn6b4CR1hK4cTqyAvfuMIBj3agAHCfN+msCQ5zS166TNmAzpQGMGkZK917DlwLz+nxN05LA55rWbPrkpmlxSgOWUxrpy8CUzmXdTV5Yvi51mZe9SK+/GFR4CAE8bgKcheC2U4dtwAzNwACVOgdNK0BcdMAYVOAsxMBwFoLbzpoWpp+mU1fdiCmFNRgygSW0TQzLkjM4pzScBTDX8Ga9U7vdmxtgdphr1KZuQ9GmtlRC83ZhAc1aHQfmwcMpMJrWqTTlz2TzkrAS0iZzp8UTmHPga7Zk/D05ZtoctukYMLuMLt078FdvUjdnZ/VBmpuSKc9bSugY8Hr1cpvA1L15O9mCy0Ynb4QNuIHOCBrA0HSyJOCXl9fbA5bQpsx03zcyOzC7fLPA0mUpCcywsktfArzSe+GbBR5XM1UDtj0kwKEBHRS0ytvzMBwPQTbXAR0EuksPA1rrMWXdZEpP6vkBMISfpQ5OS4yl7BTwbrfr/0zrmh0QvjcdL0gSHK+mDjLAWtd5B4xDBmwNewWWg8cl4PhspmuNJaFNly9xGMBhqLv0KKRDBgC/vr66AeYTj1PzsYRlYIbm1JZiYNSzOagweBQl5LIz4Aa2ImB54tFtEdv9MMNBJjBkS+djKX0wkMQpucwp7QxYbh5swHARkpDSYZtkHZvA3dLVAjtpWvLEwwZM0C0wO2dCn4O9BBhpnaa5Wq+3/QPjgBzPfDils7beOpcFsLn0SCCZzgxsOix3WhA1Lg0MARgp/f7+7gYY20M6uWyfQnTguplAx2BZ1wBLtxk4STK3wHjcEUZZexyzh0ZtoaH0BQxQBkYN/xPgg0em3whs7qYkMOQUmGAtKU2PWXRKy/XVBnkO2DwrY2gAQ2hcaFpOaxid+qB+cXF4zFLWvQGjWQEYS9NNAfPjUxuwBPwqMKYtbA97B26g47NdGsAMLYHlunsK+OghwSfg3/3vloIQwGmnGHWlLyTFeXTbtMocdVxrgPGBxtVEA04/Cb/fa9ZAonl1GpPYcdzo+Xyp3t5+udse3pLufPjw4cOHDx8+fPjw4cOHj6/HH8bQTPlemts3AAAAAElFTkSuQmCC","backImage":"data:image/png;base64,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","randomX":0,"randomY":37,"originalHeight":150,"originalWidth":300,"sliderHeight":60,"sliderWidth":60}}
1

POST:

{"success":true,"message":"success","code":2000000000,"result":null}
1

1.2 表结构

见资料

package com.mszlu.shop.model.buyer.pojo;

import lombok.Data;

@Data
public class VerificationSource {

    private Long id;

    private String name;

    private String resource;
	//资源 滑块
    private String type;

    private Boolean deleteFlag;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

用到的枚举资源:

package com.mszlu.shop.model.buyer.eums;


/**
 * 验证码资源枚举
 */
public enum VerificationSourceEnum {

    SLIDER("滑块"),
    RESOURCE("验证码源");

    private final String description;

    VerificationSourceEnum(String des) {
        this.description = des;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

1.3 前端分析

components/verify/index.vue

接口在:components/verify/verify.js

1.4 Controller

package com.mszlu.shop.buyer.controller.common;

import com.mszlu.shop.buyer.service.common.VerificationService;
import com.mszlu.shop.common.vo.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/common/slider")
@Api(tags = "滑块验证")
public class SliderImageController {

    @Autowired
    private VerificationService verificationService;

    @GetMapping("/{verificationEnums}")
    @ApiOperation(value = "获取校验接口")
    public Result getSliderImage(@RequestHeader String uuid,
                                 @PathVariable("verificationEnums") Integer verificationCode) {
        return verificationService.createVerification(verificationCode, uuid);
    }

    @PostMapping("/{verificationEnums}")
    @ApiOperation(value = "验证码预校验")
    public Result getSliderImage(@RequestHeader String uuid,
                                 @PathVariable("verificationEnums") Integer verificationCode,
                                 Integer xPos) {
        return verificationService.preCheck(verificationCode, uuid,xPos);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.mszlu.shop.model.buyer.eums;

import java.util.HashMap;
import java.util.Map;

/**
 * VerificationEnums
 */
public enum VerificationEnums {

    LOGIN(1,"登录"),
    REGISTER(2,"注册"),
    FIND_USER(3,"找回用户"),
    UPDATE_PASSWORD(4,"找回密码"),
    WALLET_PASSWORD(5,"支付钱包密码");

    private int code;
    private String message;

    private static final Map<Integer, VerificationEnums> CODE_MAP = new HashMap<>(3);

    static{
        for(VerificationEnums verificationEnums: values()){
            CODE_MAP.put(verificationEnums.getCode(), verificationEnums);
        }
    }

    VerificationEnums(int code, String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public static VerificationEnums codeOf(int code){
        return CODE_MAP.get(code);
    }
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

1.5 Service

package com.mszlu.shop.buyer.service.common;

import com.mszlu.shop.buyer.service.VerificationSourceService;
import com.mszlu.shop.common.eunms.BusinessCodeEnum;
import com.mszlu.shop.common.utils.SerializableStream;
import com.mszlu.shop.common.utils.SliderImageCut;
import com.mszlu.shop.common.utils.SliderImageUtil;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.eums.VerificationEnums;
import com.mszlu.shop.model.buyer.vo.commons.VerificationSourceVo;
import com.mszlu.shop.model.buyer.vo.commons.slider.VerificationVO;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service
public class VerificationService {

    @DubboReference(version = "1.0.0")
    private VerificationSourceService verificationSourceService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 获取滑块验证的图片
     * @param verificationCode
     * @param uuid
     * @return
     */
    public Result<SliderImageCut> createVerification(Integer verificationCode, String uuid) {
        /**
         * 目的:为了获取滑块验证的图片,同时 需要有一个滑块,然后图片上扣出来一个和滑块一样的图片出来
         *      扣出来的这个图片,移动到对应的位置上之后,重新组合为一个完整图片
         * 1. uuid 先判断请求是否合法
         * 2. verificationCode 验证的类型  login (1),首先判断参数合法
         * 3. 获取数据库verification_source 表中的所有数据,分为两块 一个是 资源数据,一个是 滑块数据
         * 4. 从资源数据中,随机获取一个资源,随机获取一个滑块
         * 5. 从 资源图片上 扣一个和滑块一模一样的图片 出来
         * 6. 记录相关的信息 x轴信息
         * 7. 相关的信息 存入 redis中,便于后续验证  key   prefix + verificationCode + uuid value x轴移动坐标
         */
        if (uuid == null) {
            return Result.fail(BusinessCodeEnum.ILLEGAL_REQUEST.getCode(),"非法请求,请重新刷新页面操作");
        }
        VerificationEnums verificationEnums = VerificationEnums.codeOf(verificationCode);
        if (verificationEnums == null){
            return Result.fail(BusinessCodeEnum.ILLEGAL_REQUEST.getCode(),"非法请求,请重新刷新页面操作");
        }
        VerificationVO verificationVO =  verificationSourceService.findVerificationSource();
        List<VerificationSourceVo> verificationResources = verificationVO.getVerificationResources();
        List<VerificationSourceVo> verificationSlider = verificationVO.getVerificationSlider();

        Random random = new Random();
        //随机选择需要切的图下标
        int resourceNum = random.nextInt(verificationResources.size());
        //随机选择剪切模版下标
        int sliderNum = random.nextInt(verificationSlider.size());

        VerificationSourceVo resource = verificationResources.get(resourceNum);
        VerificationSourceVo slider = verificationSlider.get(sliderNum);
        String resourceUrl = resource.getResource();
        String sliderUrl = slider.getResource();
        try {
            //图片相应的处理操作
            SliderImageCut sliderImageCut = SliderImageUtil.pictureTemplatesCut(getInputStream(sliderUrl), getInputStream(resourceUrl));
            //如果不设置为0  滑块直接就匹配
            //生成验证参数 120可以验证 无需手动清除,120秒有效时间自动清除
            redisTemplate.opsForValue().set(verificationRedisKey(uuid, verificationEnums), String.valueOf(sliderImageCut.getRandomX()), 120, TimeUnit.SECONDS);
            sliderImageCut.setRandomX(0);
            return Result.success(sliderImageCut);
        }catch (Exception e){
            e.printStackTrace();
        }

        return Result.fail(-999,"创建校验错误");
    }

    private String verificationRedisKey(String uuid, VerificationEnums verificationEnums) {
        return "VERIFICATION_IMAGE_"+verificationEnums.name()+uuid;
    }

    /**
     * 根据网络地址,获取源文件
     * 这里简单说一下,这里是将不可序列化的inputstream序列化对象,存入redis缓存
     * @param originalResource
     * @return
     */
    private SerializableStream getInputStream(String originalResource) throws Exception {

        if (StringUtils.isNotEmpty(originalResource)) {
            URL url = new URL(originalResource);
            InputStream inputStream = url.openStream();
            SerializableStream serializableStream = new SerializableStream(inputStream);
            return serializableStream;
        }
        return null;
    }



    /**
     * 滑动验证结果
     * @param verificationCode
     * @param uuid
     * @param xPos
     * @return
     */
    public Result preCheck(Integer verificationCode, String uuid, Integer xPos) {
        /**
         * 去redis 验证 xPos和redis存的一不一样
         */
        if (uuid == null) {
            return Result.fail(BusinessCodeEnum.ILLEGAL_REQUEST.getCode(),"非法请求,请重新刷新页面操作");
        }
        VerificationEnums verificationEnums = VerificationEnums.codeOf(verificationCode);
        if (verificationEnums == null){
            return Result.fail(BusinessCodeEnum.ILLEGAL_REQUEST.getCode(),"非法请求,请重新刷新页面操作");
        }
        String verificationRedisKey = verificationRedisKey(uuid, verificationEnums);
        String x = redisTemplate.opsForValue().get(verificationRedisKey);
        if (StringUtils.isBlank(x)){
            return Result.fail(-999,"不匹配");
        }
        //验证结果
        boolean result = Math.abs(Integer.parseInt(x) - xPos) < 3;
        if (result){
            //验证成功,则记录验证结果 验证有效时间,120秒
            //滑块验证成功之后 随后的一段时间 如果有操作,认为是验证通过的 不需要重复进行滑块验证
            redisTemplate.opsForValue().set("VERIFICATION_IMAGE_RESULT_"+verificationEnums.name()+uuid, "true", 120,TimeUnit.SECONDS);
            return Result.success();
        }
        return Result.fail(-999,"不匹配");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.mszlu.shop.model.buyer.vo.commons.slider;

import com.mszlu.shop.model.buyer.vo.commons.VerificationSourceVo;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class VerificationVO implements Serializable {

    /**
     * 资源
     */
    List<VerificationSourceVo> verificationResources;

    /**
     * 滑块资源
     */
    List<VerificationSourceVo> verificationSlider;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mszlu.shop.model.buyer.vo.commons;

import lombok.Data;

import java.io.Serializable;

@Data
public class VerificationSourceVo implements Serializable {

    private Long id;

    private String name;

    private String resource;

    private String type;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

1.6 Dubbo服务

package com.mszlu.shop.buyer.service;

import com.mszlu.shop.model.buyer.vo.commons.slider.VerificationVO;

public interface VerificationSourceService {

    VerificationVO findVerificationSource();
}

1
2
3
4
5
6
7
8
9
package com.mszlu.shop.buyer.service.impl.common;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.shop.buyer.mapper.VerificationSourceMapper;
import com.mszlu.shop.buyer.service.VerificationSourceService;
import com.mszlu.shop.model.buyer.eums.VerificationSourceEnum;
import com.mszlu.shop.model.buyer.vo.commons.VerificationSourceVo;
import com.mszlu.shop.model.buyer.vo.commons.slider.VerificationVO;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@DubboService(version = "1.0.0",interfaceClass = VerificationSourceService.class)
public class VerificationSourceServiceImpl implements VerificationSourceService {

    @Resource
    private VerificationSourceMapper verificationSourceMapper;

    public VerificationVO findVerificationSource() {
        LambdaQueryWrapper<VerificationSource> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VerificationSource::getDeleteFlag,false);
        List<VerificationSource> dbList = verificationSourceMapper.selectList(queryWrapper);
        List<VerificationSource> resourceList = new ArrayList<>();
        List<VerificationSource> sliderList = new ArrayList<>();
        for (VerificationSource item : dbList) {
            if (item.getType().equals(VerificationSourceEnum.RESOURCE.name())) {
                resourceList.add(item);
            } else if (item.getType().equals(VerificationSourceEnum.SLIDER.name())) {
                sliderList.add(item);
            }
        }
        VerificationVO verificationVO = new VerificationVO();
        verificationVO.setVerificationResources(copyList(resourceList));
        verificationVO.setVerificationSlider(copyList(sliderList));
        return verificationVO;
    }

    public VerificationSourceVo copy(VerificationSource verificationSource){
        if (verificationSource == null){
            return null;
        }
        VerificationSourceVo verificationSourceVo = new VerificationSourceVo();
        BeanUtils.copyProperties(verificationSource,verificationSourceVo);
        return verificationSourceVo;
    }

    public List<VerificationSourceVo> copyList(List<VerificationSource> verificationSourceList){
        List<VerificationSourceVo> verificationSourceVoList = new ArrayList<>();
        for (VerificationSource verificationSource : verificationSourceList) {
            verificationSourceVoList.add(copy(verificationSource));
        }
        return verificationSourceVoList;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.mszlu.shop.buyer.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface VerificationSourceMapper extends BaseMapper<VerificationSource> {
}

1
2
3
4
5
6
7

1.7 测试

2. 登录

以淘宝为例子,taobao.com和fliggy.com 都是阿里旗下的业务,登录的时候 使用的都是同一套登录系统。

这就是单点登录。sso

在taobao.com登录之后,那么在使用fliggy.com业务的时候,不需要重新登录就可以直接使用。

使用了token令牌的登录认证方式。

单点登录一般使用jwt技术实现,jwt是一个加密技术,用于生成token。

接下来在实现登录功能的时候,需要新建一个sso模块。

在进行登录认证的时候,buyer-api使用security进行认证,调用sso服务,进行登录认证。

2.1 新建sso模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mall-parent</artifactId>
        <groupId>com.mszlu.shop</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mall-sso</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mszlu.shop</groupId>
            <artifactId>mall-dao</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.mszlu.shop</groupId>
            <artifactId>mall-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.mszlu.shop</groupId>
            <artifactId>mall-model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
        </dependency>
        <!-- Dubbo Registry Nacos -->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </dependency>
    </dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

配置文件:

application.yml:

###启动端口号
server:
  port: 9989
  ###接口访问路径
  servlet:
    context-path: /sso
spring:
  application:
    name: mall-sso
    ##本机开发环境 多环境配置
  profiles:
    active: local


1
2
3
4
5
6
7
8
9
10
11
12
13
14

application-local.yml:

buyer:
  url: http://localhost:10000/
spring:
  jackson:
    time-zone: GMT+8
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ms_mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
  redis:
    port: 6379
    host: localhost
##启动时检查提供者是否存在,true报错,false忽略
dubbo:
  consumer:
    check: false
  scan:
    base-packages: com.mszlu.shop.sso.api
  registry:
    group: dubbo_sso_service
    address: nacos://${nacos.server-address}:${nacos.port}/?username=${nacos.username}&password=${nacos.password}
  protocol:
    port: 20881
    name: dubbo
nacos:
  server-address: 127.0.0.1
  port: 8848
  username: nacos
  password: nacos

### 读写分离配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

启动类:

package com.mszlu.shop.sso;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SSOApp {

    public static void main(String[] args) {
        SpringApplication.run(SSOApp.class,args);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

2.2 接口说明

接口url:/sso/members/userLogin

请求方式:POST

请求参数:

参数名称参数类型说明
usernamestring用户名
uuidStringHeader参数 用于验证身份
passwordstring密码
clientTypestringHeader参数 客户端类型 0 PC: web端

返回数据:

{
 "success":true,
 "message":"success",
 "code":2000000000,
 "result":{
     "accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcImlkXCI6XCIxXCIsXCJpc1N1cGVyXCI6ZmFsc2UsXCJsb25nVGVybVwiOmZhbHNlLFwibmlja05hbWVcIjpcIm1zemx1XCIsXCJyb2xlXCI6XCJNRU1CRVJcIixcInVzZXJuYW1lXCI6XCJtc3psdVwifSIsInN1YiI6Im1zemx1IiwiZXhwIjoxNjI3MDQ1OTQ1fQ.5O9gTF6WmaDIziWc22dYHKVhcQ7tqRodb_Udtt1ZveY",
"refreshToken":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcImlkXCI6XCIxXCIsXCJpc1N1cGVyXCI6ZmFsc2UsXCJsb25nVGVybVwiOmZhbHNlLFwibmlja05hbWVcIjpcIm1zemx1XCIsXCJyb2xlXCI6XCJNRU1CRVJcIixcInVzZXJuYW1lXCI6XCJtc3psdVwifSIsInN1YiI6Im1zemx1IiwiZXhwIjoxNjI3NzM3MTQ1fQ.oqRjtBj4izPDu4hyVp3oXJ3YJqcTVq7URWvkbP942KU"
 }
}
1
2
3
4
5
6
7
8
9

返回的数据模型:

package com.mszlu.shop.common.security;

import lombok.Data;

/**
 * Token 实体类
 */
@Data
public class Token {
    /**
     * 访问token
     */
    private String accessToken;

    /**
     * 刷新token
     */
    private String refreshToken;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

2.3 表结构

见资料 ms_member

package com.mszlu.shop.model.buyer.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;

/**
 * 会员
 */
@Data
@NoArgsConstructor
public class Member {

    private Long id;

    private String username;

    private String password;

    private String nickName;

    //1 男 0 女
    private Integer sex;
    //
    private Date birthday;
    //会员地址ID
    private String regionId;
    //会员地址
    private String region;

    private String mobile;
    //积分
    private Long point;

    private String face;
    //会议状态
    private Boolean disabled;

    private Boolean haveStore;

    private String storeId;

    private Integer clientEnum;

    private Long lastLoginDate;
    //会员等级
    private String gradeId;
    //经验
    private Long experience;

    private Long registerTime;


    public Member(String username, String password, String mobile) {
        this.username = username;
        this.password = password;
        this.mobile = mobile;
        this.nickName = mobile;
        this.disabled = true;
        this.haveStore = false;
        this.sex = 0;
        this.point = 0L;
        this.lastLoginDate = System.currentTimeMillis();
    }

    public Member(String username, String password, String mobile, String nickName, String face) {
        this.username = username;
        this.password = password;
        this.mobile = mobile;
        this.nickName = nickName;
        this.disabled = true;
        this.haveStore = false;
        this.face = face;
        this.sex = 0;
        this.point = 0L;
        this.lastLoginDate = System.currentTimeMillis();
    }

    public Member(String username, String password, String face, String nickName, Integer sex) {
        this.username = username;
        this.password = password;
        this.mobile = "";
        this.nickName = nickName;
        this.disabled = true;
        this.haveStore = false;
        this.face = face;
        this.sex = sex;
        this.point = 0L;
        this.lastLoginDate = System.currentTimeMillis();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

2.4 Controller

package com.mszlu.shop.sso.controller;

import com.mszlu.shop.common.security.Token;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.eums.VerificationEnums;
import com.mszlu.shop.sso.service.MemberService;
import com.mszlu.shop.sso.service.VerificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/members")
public class MembersController {

    @Autowired
    private MemberService memberService;
    @Autowired
    private VerificationService verificationService;


    @PostMapping("/userLogin")
    public Result<Token> userLogin(@RequestParam String username,
                                   @RequestParam String password,
                                   @RequestHeader String uuid) {
        if (verificationService.check(uuid, VerificationEnums.LOGIN)) {
            return this.memberService.usernameLogin(username, password);
        } else {
            return Result.fail(-999,"请重新验证");
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

2.5 Service

角色定义:

package com.mszlu.shop.common.security;

/**
 * token角色类型
 */
public enum UserEnums {
    /**
     * 角色
     */
    MEMBER("会员"),
    STORE("商家"),
    MANAGER("管理员"),
    SYSTEM("系统");
    private final String role;

    UserEnums(String role) {
        this.role = role;
    }

    public String getRole() {
        return role;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

token中存放的信息:

package com.mszlu.shop.common.security;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class AuthUser implements Serializable {

    /**
     * 用户名
     */
    private String username;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * id
     */
    private String id;

    /**
     * 长期有效(用于手机app登录场景或者信任场景等)
     */
    private Boolean longTerm = false;

    /**
     * @see UserEnums
     * 角色
     */
    private UserEnums role;

    /**
     * 如果角色是商家,则存在此店铺id字段
     * storeId
     */
    private String storeId;

    /**
     * 如果角色是商家,则存在此店铺名称字段
     * storeName
     */
    private String storeName;

    /**
     * 是否是超级管理员
     */
    private Boolean isSuper = false;

    public AuthUser(String username, String id, String nickName, UserEnums role) {
        this.username = username;
        this.id = id;
        this.role = role;
        this.nickName = nickName;
    }

    public AuthUser(String username, String id, UserEnums manager, String nickName, Boolean isSuper) {
        this.username = username;
        this.id = id;
        this.role = manager;
        this.isSuper = isSuper;
        this.nickName = nickName;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.mszlu.shop.sso.service;

import com.mszlu.shop.common.eunms.BusinessCodeEnum;
import com.mszlu.shop.common.utils.SerializableStream;
import com.mszlu.shop.common.utils.SliderImageCut;
import com.mszlu.shop.common.utils.SliderImageUtil;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.eums.VerificationEnums;
import com.mszlu.shop.model.buyer.vo.commons.VerificationSourceVo;
import com.mszlu.shop.model.buyer.vo.commons.slider.VerificationVO;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
//滑块验证后,存了一个验证结果,检查有无过期
@Service
public class VerificationService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean check(String uuid, VerificationEnums verificationEnums) {
        Boolean hasKey = redisTemplate.hasKey("VERIFICATION_IMAGE_RESULT_" + verificationEnums.name() + uuid);
        return hasKey != null && hasKey;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.mszlu.shop.sso.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.shop.sso.mapper.MemberMapper;
import com.mszlu.shop.common.cache.CachePrefix;
import com.mszlu.shop.common.context.ThreadContextHolder;
import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.common.security.Token;
import com.mszlu.shop.common.security.UserEnums;
import com.mszlu.shop.common.utils.token.TokenUtils;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.eums.ClientType;
import com.mszlu.shop.model.buyer.pojo.Member;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
public class MemberService {

    @Resource
    private MemberMapper memberMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    public Result<Token> usernameLogin(String username, String password) {
        LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Member::getUsername,username).eq(Member::getDisabled,false);
        Member member = memberMapper.selectOne(queryWrapper);
        if (member == null){
            return Result.fail(-999,"用户不存在");
        }
        if (!new BCryptPasswordEncoder().matches(password,member.getPassword())){
            return Result.fail(-999,"用户名或密码错误");
        }
        Token token = genToken(member);
        return Result.success(token);
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("e10adc3949ba59abbe56e057f20f883e"));
    }
    private Token genToken(Member member) {
        //获取客户端类型
        String clientType = ThreadContextHolder.getHttpRequest().getHeader("clientType");
        Integer clientTypeInt = 0;
        try {
            if (StringUtils.isBlank(clientType)){clientType = "0";}
            clientTypeInt = Integer.parseInt(clientType);
            ClientType type = ClientType.codeOf(clientTypeInt);
            if (type != null){
                member.setClientEnum(clientTypeInt);
            }
        }catch (Exception e){
            e.printStackTrace();
            member.setClientEnum(ClientType.UNKNOWN.getCode());
        }
        member.setLastLoginDate(System.currentTimeMillis());
        this.memberMapper.updateById(member);
        AuthUser authUser = new AuthUser(member.getUsername(), String.valueOf(member.getId()),member.getNickName(), UserEnums.MEMBER);

        Token token = new Token();

        String jwtToken = TokenUtils.createToken(member.getUsername(), authUser, 7 * 24 * 60L);
        token.setAccessToken(jwtToken);
        redisTemplate.opsForValue().set(CachePrefix.ACCESS_TOKEN.name() + UserEnums.MEMBER.name() + jwtToken, "1",7, TimeUnit.DAYS);
        //设置刷新token,当accessToken过期的时候,可以通过refreshToken来 重新获取accessToken 而不用访问数据库
        String refreshToken = TokenUtils.createToken(member.getUsername(), authUser, 15 * 24 * 60L);
        redisTemplate.opsForValue().set(CachePrefix.REFRESH_TOKEN.name() + UserEnums.MEMBER.name() + jwtToken, "1",15, TimeUnit.DAYS);
        token.setRefreshToken(refreshToken);
        return token;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

2.6 涉及到的工具类

生成token的:

需要导包:在mall-common模块中

 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <!--   token加密 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.mszlu.shop.common.utils.token;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Jwts;


import java.util.Date;

public class TokenUtils {

    public static String createToken(String username,Object claim,Long expirationTime){
        //JWT 生成
        return Jwts.builder()
                //jwt 私有声明
                .claim(SecurityKey.USER_CONTEXT, JSON.toJSONString(claim))
                //JWT的主体
                .setSubject(username)
                //失效时间 当前时间+过期分钟
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 1000))
                //签名算法和密钥
                .signWith(SecretKeyUtil.generalKey())
                .compact();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.mszlu.shop.common.utils.token;

import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.SecretKey;
import java.io.UnsupportedEncodingException;

/**
 * SignWithUtil
 */
public class SecretKeyUtil {

    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decodeBase64("bXN6bHVfMTIzYXNkYWRnYXVoZCFAIyQlNkFERkdHS0pIR1lURkRZVVM=");//自定义
        javax.crypto.SecretKey key = Keys.hmacShaKeyFor(encodedKey);
        return key;
    }

    public static SecretKey generalKeyByDecoders() {
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode("bXN6bHVfMTIzYXNkYWRnYXVoZCFAIyQlNkFERkdHS0pIR1lURkRZVVM="));

    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(Base64.encodeBase64String("mszlu_123asdadgauhd!@##$%6ADFGGKJHGYTFDYUS".getBytes()));
        System.out.println(new String(SecretKeyUtil.generalKeyByDecoders().getEncoded()));
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.mszlu.shop.common.utils.token;

public class SecurityKey {

    public static final String USER_CONTEXT = "userContext";

    public static final String ACCESS_TOKEN = "accessToken";
}

1
2
3
4
5
6
7
8
9

2.7 缓存前缀

package com.mszlu.shop.common.cache;

/**
 * 缓存前缀
 */
public enum CachePrefix {


    /**
     * nonce
     */
    NONCE,

    /**
     * 在线人数
     */
    ONLINE_NUM,


    /**
     * 会员分布数据
     */
    MEMBER_DISTRIBUTION,

    /**
     * 在线会员统计
     */
    ONLINE_MEMBER,

    /**
     * token 信息
     */
    ACCESS_TOKEN,
    /**
     * token 信息
     */
    REFRESH_TOKEN,
    /**
     * 联合登录响应
     */
    CONNECT_RESULT,
    /**
     * 微信联合登陆 session key
     */
    SESSION_KEY,
    /**
     * 权限
     */
    PERMISSION_LIST,
    /**
     * 部门id列表
     */
    DEPARTMENT_IDS,

    /**
     * 用户错误登录限制
     */
    LOGIN_TIME_LIMIT,
    /**
     * 系统设置
     */
    SETTING,

    /**
     * 验证码滑块源
     */
    VERIFICATION,

    /**
     * 验证码滑块源
     */
    VERIFICATION_IMAGE,

    /**
     * 快递平台
     */
    EXPRESS,

    /**
     * 图片验证码
     */
    CAPTCHA,

    /**
     * 商品
     */
    GOODS,

    /**
     * 商品SKU
     */
    GOODS_SKU,

    /**
     * 运费模板脚本
     */
    SHIP_SCRIPT,

    /**
     * 商品sku
     */
    SKU,

    /**
     * sku库存
     */
    SKU_STOCK,

    /**
     * 促销商品sku库存
     */
    PROMOTION_GOODS_STOCK,

    /**
     * 商品库存
     */
    GOODS_STOCK,

    /**
     * 商品分类
     */
    CATEGORY,
    /**
     * 浏览次数
     */
    VISIT_COUNT,
    /**
     * 存储方案
     */
    UPLOADER,
    /**
     * 地区
     */
    REGION,

    /**
     * 短信网关
     */
    SPlATFORM,
    /**
     * 短信验证码前缀
     */
    _CODE_PREFIX,
    /**
     * smtp
     */
    SMTP,
    /**
     * 系统设置
     */
    SETTINGS,
    /**
     * 电子面单
     */
    WAYBILL,
    /**
     * 短信验证码
     */
    SMS_CODE,
    /**
     * 邮箱验证码
     */
    EMAIL_CODE,
    /**
     * 管理员角色权限对照表
     */
    ADMIN_URL_ROLE,

    /**
     * 店铺管理员角色权限对照表
     */
    SHOP_URL_ROLE,

    /**
     * 手机验证标识
     */
    MOBILE_VALIDATE,

    /**
     * 邮箱验证标识
     */
    EMAIL_VALIDATE,

    /**
     * 店铺运费模版列表
     */
    SHIP_TEMPLATE,

    /**
     * 店铺中某个运费模版
     */
    SHIP_TEMPLATE_ONE,

    //================促销=================
    /**
     * 促销活动
     */
    PROMOTION,
    /**
     * 促销活动
     */
    PROMOTION_GOODS,

    /*** 单品立减 */
    STORE_ID_MINUS,

    /*** 第二件半价 */
    STORE_ID_HALF_PRICE,

    /*** 满优惠 */
    STORE_ID_FULL_DISCOUNT,

    /**
     * 秒杀活动活动缓存key前缀
     */
    STORE_ID_SECKILL,

    /**
     * 团购活动缓存key前缀
     */
    STORE_ID_GROUP_BUY,

    /**
     * 积分商品缓存key前缀
     */
    STORE_ID_EXCHANGE,


    //================交易=================

    /**
     * 购物车原始数据
     */
    CART_ORIGIN_DATA_PREFIX,

    /**
     * 立即购买原始数据
     */
    BUY_NOW_ORIGIN_DATA_PREFIX,

    /**
     * 交易原始数据
     */
    TRADE_ORIGIN_DATA_PREFIX,

    /**
     * 立即购买sku
     */
    CART_SKU_PREFIX,

    /**
     * 购物车视图
     */
    CART_MEMBER_ID_PREFIX,

    /**
     * 购物车,用户选择的促销信息
     */
    CART_PROMOTION_PREFIX,


    /**
     * 交易_交易价格的前缀
     */
    PRICE_SESSION_ID_PREFIX,

    /**
     * 交易_交易单
     */
    TRADE_SESSION_ID_PREFIX,


    /**
     * 结算参数
     */
    CHECKOUT_PARAM_ID_PREFIX,

    /**
     * 交易单号前缀
     */
    TRADE_SN_CACHE_PREFIX,

    /**
     * 订单编号前缀
     */
    ORDER_SN_CACHE_PREFIX,
    /**
     * 订单编号标记
     */
    ORDER_SN_SIGN_CACHE_PREFIX,
    /**
     * 订单编号前缀
     */
    PAY_LOG_SN_CACHE_PREFIX,

    /**
     * 合同编号
     */
    CONTRACT_SN_CACHE_PREFIX,


    /**
     * 零钱
     */
    SMALL_CHANGE_CACHE_PREFIX,

    /**
     * 售后服务单号前缀
     */
    AFTER_SALE_SERVICE_PREFIX,

    /**
     * 交易
     */
    TRADE,

    /**
     * 站点导航栏
     */
    SITE_NAVIGATION,

    /**
     * 支付参数
     */
    PAYMENT_CONFIG,

    /**
     * 流程控制
     */
    FLOW,

    /**
     * 热门搜索
     */
    HOT_WORD,

    /**
     * 会员积分
     */
    MEMBER_POINT,

    /**
     * 会员积分
     */
    POINT_ORDER,


    /**
     * 微博登录
     */
    WEIBO_STATE,
    /**
     * 微博登录
     */
    QQ_STATE,
    /**
     * 微博登录
     */
    GITHUB_STATE,
    /**
     * 验证码key
     */
    VERIFICATION_KEY,
    /**
     * 验证码验证结果
     */
    VERIFICATION_RESULT,
    /**
     * 微信CGItoken
     */
    WECHAT_CGI_ACCESS_TOKEN,
    /**
     * 微信JSApitoken
     */
    WECHAT_JS_API_TOKEN,
    /**
     * 微信会话信息
     */
    WECHAT_SESSION_PARAMS,
    /**
     * 第三方用户权限
     */
    ALIPAY_CONFIG,
    /**
     * 微信支付配置
     */
    WECHAT_PAY_CONFIG,
    /**
     * 微信支付平台证书配置
     */
    WECHAT_PLAT_FORM_CERT,
    /**
     * 第三方用户权限
     */
    CONNECT_AUTH,
    /**
     * 平台PageView 统计
     */
    PV,
    /**
     * 平台UserView 统计
     */
    UV,
    /**
     * 平台 商品PV 统计
     */
    GOODS_PV,
    /**
     * 平台 商品UV 统计
     */
    GOODS_UV,
    /**
     * 店铺PageView 统计
     */
    STORE_PV,
    /**
     * 店铺UserView 统计
     */
    STORE_UV,
    /**
     * 店铺 商品PV 统计
     */
    STORE_GOODS_PV,
    /**
     * 店铺 商品UV 统计
     */
    STORE_GOODS_UV,
    /**
     * 分销员
     */
    DISTRIBUTION,

    /**
     * 找回手机
     */
    FIND_MOBILE,
    /**
     * 文章分类
     */
    ARTICLE_CATEGORY,
    /**
     * 文章
     */
    ARTICLE_CACHE
    ;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448

2.8 用户上下文

package com.mszlu.shop.common.context;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 用户上下文
 **/
public class ThreadContextHolder {

    private static final ThreadLocal<HttpServletRequest> REQUEST_THREAD_LOCAL_HOLDER = new ThreadLocal<>();
    private static final ThreadLocal<HttpServletResponse> RESPONSE_THREAD_LOCAL_HOLDER = new ThreadLocal<>();

    public static void remove() {
        REQUEST_THREAD_LOCAL_HOLDER.remove();
        RESPONSE_THREAD_LOCAL_HOLDER.remove();
    }

    public static HttpServletResponse getHttpResponse() {

        return RESPONSE_THREAD_LOCAL_HOLDER.get();
    }

    public static void setHttpResponse(HttpServletResponse response) {
        RESPONSE_THREAD_LOCAL_HOLDER.set(response);
    }

    public static HttpServletRequest getHttpRequest() {
        return REQUEST_THREAD_LOCAL_HOLDER.get();
    }

    public static void setHttpRequest(HttpServletRequest request) {

        REQUEST_THREAD_LOCAL_HOLDER.set(request);
    }


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

实现拦截器:

package com.mszlu.shop.sso.handler;

import com.mszlu.shop.common.context.ThreadContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 写入request/response
 */
@Slf4j
@Component
public class RequestInterceptorAdapter implements AsyncHandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) {
        ThreadContextHolder.setHttpResponse(response);
        ThreadContextHolder.setHttpRequest(request);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {

        ThreadContextHolder.setHttpResponse(response);
        ThreadContextHolder.setHttpRequest(request);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        ThreadContextHolder.remove();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.mszlu.shop.sso.config;

import com.mszlu.shop.sso.handler.RequestInterceptorAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    @Value("${buyer.url}")
    private String buyerUrl;

    @Autowired
    private RequestInterceptorAdapter requestInterceptorAdapter;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins(buyerUrl);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestInterceptorAdapter);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

2.9 测试

2.10 前端说明

前端新添加了sso的url地址,接口会访问sso的地址

3. 买家API 登录认证

3.1 导包

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
1
2
3
4

3.2 security配置

package com.mszlu.shop.buyer.config;

import com.mszlu.shop.buyer.handler.security.BuyerAuthenticationFilter;
import com.mszlu.shop.buyer.handler.security.CustomAccessDeniedHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.web.cors.CorsConfigurationSource;

/**
 * spring Security 核心配置类 Buyer安全配置中心
 */

@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class BuyerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 忽略验权配置
     */
    @Autowired
    private IgnoredUrlsProperties ignoredUrlsProperties;

    /**
     * spring security -》 权限不足处理
     */
    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
                .authorizeRequests();
        //配置的url 不需要授权
        for (String url : ignoredUrlsProperties.getUrls()) {
            registry.antMatchers(url).permitAll();
        }
        registry
                .and()
                //禁止网页iframe
                .headers().frameOptions().disable()
                .and()
                .logout()
                .permitAll()
                .and()
                .authorizeRequests()
                //任何请求
                .anyRequest()
                //需要身份认证
                .authenticated()
                .and()
                //允许跨域
                .cors().and()
                //关闭跨站请求防护
                .csrf().disable()
                //前后端分离采用JWT 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //自定义权限拒绝处理类
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                .and()
                //添加JWT认证过滤器
                .addFilter(new BuyerAuthenticationFilter(authenticationManager(), redisTemplate));
    }


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

3.2 忽略配置

package com.mszlu.shop.buyer.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

@Configuration
@ConfigurationProperties(prefix = "ignored")
@Data
public class IgnoredUrlsProperties {

    private List<String> urls = new ArrayList<>();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ignored:
  urls:
    - /pages/**
    - /pageData/**
    - /article/**
    - /goods/**
    - /members/**
    - /category/**
    - /common/**
1
2
3
4
5
6
7
8
9

3.3 认证过滤器

package com.mszlu.shop.common.utils.token;

public class SecurityKey {

    public static final String USER_CONTEXT = "userContext";

    public static final String ACCESS_TOKEN = "accessToken";
}

1
2
3
4
5
6
7
8
9
package com.mszlu.shop.buyer.handler.security;

import com.alibaba.fastjson.JSON;
import com.mszlu.shop.common.cache.CachePrefix;
import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.common.security.UserEnums;
import com.mszlu.shop.common.utils.ResponseUtil;
import com.mszlu.shop.common.utils.token.SecretKeyUtil;
import com.mszlu.shop.common.utils.token.SecurityKey;
import com.mszlu.shop.common.vo.Result;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * 认证结果过滤器
 */
@Slf4j
public class BuyerAuthenticationFilter extends BasicAuthenticationFilter {


    private StringRedisTemplate redisTemplate;

    /**
     * 自定义构造器
     *
     * @param authenticationManager
     */
    public BuyerAuthenticationFilter(AuthenticationManager authenticationManager
            ,StringRedisTemplate redisTemplate) {
        super(authenticationManager);
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        //从header中获取jwt
        String jwt = request.getHeader(SecurityKey.ACCESS_TOKEN);
        try {
            //如果没有token 则return
            if (StringUtils.isBlank(jwt)) {
                chain.doFilter(request, response);
                return;
            }
            //获取用户信息,存入context
            UsernamePasswordAuthenticationToken authentication = getAuthentication(jwt, response);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            log.error("BuyerAuthenticationFilter-> member authentication exception:", e);
        }
        chain.doFilter(request, response);
    }

    /**
     * 解析用户
     *
     * @param jwt
     * @param response
     * @return
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String jwt, HttpServletResponse response) {

        try {
            Claims claims
                    = Jwts.parser()
                    .setSigningKey(SecretKeyUtil.generalKey())
                    .parseClaimsJws(jwt).getBody();
            //获取存储在claims中的用户信息
            String json = claims.get(SecurityKey.USER_CONTEXT).toString();
            AuthUser authUser = JSON.parseObject(json, AuthUser.class);

            //校验redis中是否有权限
            Boolean hasKey = redisTemplate.hasKey(CachePrefix.ACCESS_TOKEN.name() + UserEnums.MEMBER.name() + jwt);
            if (hasKey != null && hasKey) {
                //构造返回信息
                List<GrantedAuthority> auths = new ArrayList<>();
                auths.add(new SimpleGrantedAuthority("ROLE_" + authUser.getRole().name()));
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authUser.getUsername(), null, auths);
                authentication.setDetails(authUser);
                return authentication;
            }
            ResponseUtil.output(response, 401, Result.noLogin());
            return null;
        } catch (ExpiredJwtException e) {
            log.debug("user analysis exception:", e);
        } catch (Exception e) {
            log.error("user analysis exception:", e);
        }
        return null;
    }

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

3.4 认证失败的返回

package com.mszlu.shop.buyer.handler.security;

import com.mszlu.shop.common.utils.ResponseUtil;
import com.mszlu.shop.common.vo.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

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

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        ResponseUtil.output(httpServletResponse, Result.noPermission());
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.mszlu.shop.common.utils;

import com.alibaba.fastjson.JSON;
import com.mszlu.shop.common.vo.Result;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * response 输出响应工具
 */
@Slf4j
public class ResponseUtil {

    static final String ENCODING = "UTF-8";
    static final String CONTENT_TYPE = "application/json;charset=UTF-8";

    /**
     * 输出前端内容以及状态指定
     *
     * @param response
     * @param status
     * @param content
     */
    public static void output(HttpServletResponse response, Integer status, String content) {
        ServletOutputStream servletOutputStream = null;
        try {
            response.setCharacterEncoding(ENCODING);
            response.setContentType(CONTENT_TYPE);
            response.setStatus(status);
            servletOutputStream = response.getOutputStream();
            servletOutputStream.write(content.getBytes());
        } catch (Exception e) {
            log.error("response output error: ", e);
        } finally {
            if (servletOutputStream != null) {
                try {
                    servletOutputStream.flush();
                    servletOutputStream.close();
                } catch (IOException e) {
                    log.error("response output IO close error:", e);
                }
            }
        }
    }


    /**
     * response 输出JSON
     *
     * @param response
     * @param status    response 状态
     * @param result
     */
    public static void output(HttpServletResponse response, Integer status, Result result) {
        response.setStatus(status);
        output(response, result);
    }


    /**
     * response 输出JSON
     *
     * @param response
     * @param result
     */
    public static void output(HttpServletResponse response, Result result) {
        ServletOutputStream servletOutputStream = null;
        try {
            response.setCharacterEncoding(ENCODING);
            response.setContentType(CONTENT_TYPE);
            servletOutputStream = response.getOutputStream();
            servletOutputStream.write(JSON.toJSONString(result).getBytes());
        } catch (Exception e) {
            log.error("response output error:", e);
        } finally {
            if (servletOutputStream != null) {
                try {
                    servletOutputStream.flush();
                    servletOutputStream.close();
                } catch (IOException e) {
                    log.error("response output IO close error:", e);
                }
            }
        }
    }


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public static <T> Result<T> noPermission(){
        return new Result<>(false,BusinessCodeEnum.HTTP_NO_PERMISSION.getMsg(),BusinessCodeEnum.HTTP_NO_PERMISSION.getCode(),null);
    }

    public static <T> Result<T> noLogin() {
        return new Result<>(false,BusinessCodeEnum.HTTP_NO_LOGIN.getMsg(),BusinessCodeEnum.HTTP_NO_LOGIN.getCode(),null);
    }
1
2
3
4
5
6
7
 HTTP_NO_LOGIN(401,"登录已失效,请重新登录"),
 HTTP_NO_PERMISSION(403,"抱歉,你没有访问权限"),
1
2

3. 登录会员信息

3.1 接口说明

接口url:/buyer/members

请求方式:POST

请求参数:

参数名称参数类型说明
accessTokenstringheader token

返回数据:

{"success":true,"message":"success","code":2000000000,"result":{"id":1,"username":"mszlu","nickName":"mszlu","sex":0,"birthday":"2000-07-04T00:00:00.000+08:00","regionId":"1","region":"123","mobile":"123213","point":0,"face":"https://static.mszlu.com/mall/head/head1.jpg","haveStore":true,"storeId":"1","gradeId":"1","experience":123}}
1

3.2 Controller

package com.mszlu.shop.buyer.controller.members;


import com.mszlu.shop.buyer.service.members.MemberBuyerService;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.vo.member.MemberVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/members")
public class MemberBuyerController {

    @Autowired
    private MemberBuyerService memberBuyerService;

    @GetMapping
    public Result<MemberVo> getUserInfo() {

        return memberBuyerService.getUserInfo();
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.mszlu.shop.model.buyer.vo.member;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
 * 会员
 */
@Data
@NoArgsConstructor
public class MemberVo implements Serializable {

    private String id;

    private String username;

    private String nickName;

    //1 男 0 女
    private Integer sex;
    //
    private Date birthday;
    //会员地址ID
    private String regionId;
    //会员地址
    private String region;

    private String mobile;
    //积分
    private Long point;

    private String face;

    private Boolean haveStore;

    private String storeId;

    //会员等级
    private String gradeId;
    //经验
    private Long experience;



}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

3.3 Service

package com.mszlu.shop.buyer.service.members;

import com.mszlu.shop.buyer.handler.security.UserContext;
import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.vo.member.MemberVo;
import com.mszlu.shop.sso.api.SSOApi;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

@Service
public class MemberBuyerService {

    @DubboReference(version = "1.0.0")
    private SSOApi ssoApi;

    public Result<MemberVo> getUserInfo() {
        AuthUser currentUser = UserContext.getCurrentUser();
        if (currentUser != null){
            String id = currentUser.getId();
            MemberVo memberById = ssoApi.findMemberById(Long.parseLong(id));
            return Result.success(memberById);
        }
        return Result.fail(-999,"登录已过期");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

获取用户上下文:

package com.mszlu.shop.buyer.handler.security;

import com.mszlu.shop.common.security.AuthUser;

/**
 * 用户上下文
 *
 */
public class UserContext {

    private static AuthenticationHandler authenticationHandler;

    public static void setHolder(AuthenticationHandler authenticationHandler) {
        UserContext.authenticationHandler = authenticationHandler;
    }


    public static AuthUser getCurrentUser() {
        return authenticationHandler.getAuthUser();
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

启动的时候 放入:

package com.mszlu.shop.buyer.handler.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

/**
 * 给予用户上下文,初始化参数
 */
@Component
public class UserContextInit implements ApplicationRunner {

    /**
     * 用户信息holder,认证信息的获取者
     */
    @Autowired
    private AuthenticationHandler authenticationHandler;

    /**
     * 在项目加载时指定认证信息获取者
     * 默认是由spring 安全上下文中获取
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) {
        UserContext.setHolder(authenticationHandler);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

从security上下文获取:

package com.mszlu.shop.buyer.handler.security;

import com.mszlu.shop.common.security.AuthUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

/**
 * 获取用户信息 处理
 */
@Component
public class AuthenticationHandler {

    /**
     * 获取当前用户信息
     *
     * @return
     */
    public AuthUser getAuthUser() {
        //获取spring security 权限信息,如果token有权限,在这里就会得到内容
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return null;
        }
        Object object = authentication.getDetails();
        if (object instanceof AuthUser) {
            return (AuthUser) authentication.getDetails();
        }
        return null;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

3.4 SSO的Dubbo服务

package com.mszlu.shop.sso.api;

import com.mszlu.shop.model.buyer.vo.member.MemberVo;
import com.mszlu.shop.sso.service.MemberService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;

@DubboService(version = "1.0.0")
public class SSOApiImpl implements SSOApi {

    @Autowired
    private MemberService memberService;

    @Override
    public MemberVo findMemberById(Long id) {
        return memberService.findMemberById(id);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  public MemberVo findMemberById(Long id) {
        LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Member::getId,id).eq(Member::getDisabled,false);
        Member member = memberMapper.selectOne(queryWrapper);

        return copy(member);
    }

    private MemberVo copy(Member member) {
        if (member == null){
            return null;
        }
        MemberVo memberVo = new MemberVo();
        BeanUtils.copyProperties(member,memberVo);
        memberVo.setId(String.valueOf(member.getId()));
        return memberVo;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

3.5 测试