文章目录
一、概念介绍
1、单点登陆(简称SSO)
| 单点登陆(SSO) |
|---|
| 单点登陆就是,当在某个模块中登陆后,转到本项目其它模块时,无需二次登陆 |
| 单点登陆,适用于分布式系统,也就是我们当前这个项目,每个模块单独部署到一台服务器 |
| 常见OSS方式 |
|---|
| session广播机制 |
|---|
| 通过为每一个模块复制当前登陆后的session对象实现 |
| 缺点:目前一个项目一般都有多个模块,每次登陆都要复制一次,及其浪费资源 |
| cookie+redis |
|---|
| 当我们在某一个模块登陆后,会把数据放到cookie和redis中 |
| 其中redis会生成唯一key值,对应value中会存储用户数据 |
| 最后把redis中的key值放到cookie里面 |
| 每次发送请求,都会带cookie对象,通过解析cookie中的key,获取redis中对应的value,得到用户登陆信息。最终也就实现了SSO |
| session默认30分钟过期,我们使用cookie+redis或者token两种方式,也可以通过一些手段,实现规定时间后过期功能 |
| token |
|---|
| 按照一定规则(编码和加密)生成字符串,字符串中包含用户登陆信息 |
| 将字符串通过cookie返回或者通过地址栏返回给后台 |
| 去其他模块的时候,就根据返回的字符串利用反规则解码出登陆信息实SSO |
2、JWT
| JWT(令牌加密) |
|---|
| 令牌,token形式的单点登陆可以通过JWT来实现 |
| 自包含令牌:就是按照一定规则生成的字符串,包含所需信息 |
| JWT:就是一种通用的规则,已经规定好了规则,使用jwt可以直接生成字符串,包含我们需要的信息 |
| JWT生成字符串规则(包含3部分) |
|---|
| 1、jwt头信息 |
| 2、有效荷载,包含主体信息(用户信息) |
| 3、签名哈希,防伪标志 |
二、整合JWT
1、引入JWT依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

2、编写工具类
package com.yzpnb.common_utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
* @author
*/
public class JwtUtils {
public static final long EXPIRE = 1000 * 60 * 60 * 24; //设置token过期时间
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //设置token密钥,我瞎写的,每个公司都有按自己规则生成的密钥
//生成token字符串
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder() //构建jwt字符串
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256") //设置jwt头信息
.setSubject("guli-user") //分类,名字随便起的,不同的分类可以设置不同的过期
.setIssuedAt(new Date()) //设置过期时间的计时起始值为当前时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //设置过期时间为当前时间+EXPIRE我们设定的过期时间
.claim("id", id) //token主体,这里放你需要的信息,我们实现登陆,就放用户登陆信息
.claim("nickname", nickname) //需要多少主体信息,就设置多少个claim属性
.signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名哈希,根据指定规则和我们的密钥设定签名
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效(方法一)
* @param jwtToken token字符串
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效(方式二)
* @param request 请求体
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token获取会员id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token"); //根据请求体获取token字符串
if(StringUtils.isEmpty(jwtToken)) return ""; //如果token为空,返回空串
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); //解析token字符串
Claims claims = claimsJws.getBody(); //获取主体内容
return (String)claims.get("id"); //获取主体值,这里只获取了id值
}
}

三、整合阿里云短信服务
1、阿里云短信服务开通



2、申请模板和签名





3、新建一个微服务引入依赖
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<!--阿里云依赖-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.1</version>
</dependency>

4、配置application.yml
server:
port: 8005 #微服务端口
spring:
application:
name: service-msm #服务名
profiles:
active: dev #环境设置 dev表示构建阶段,test表示测试阶段,prod表示发布阶段
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos
redis: #redis配置
host: 127.0.0.1 #你的redis地址
port: 6379 #端口号
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5 #最大阻塞等待时间(负数表示没限制)
min-idle: 0
5、编写controller
| 注意:阿里云短信验证码,不会帮我们默认生成验证码,只能帮我们将短信发送,所以验证码需要自己生成 |
|---|
package com.yzpnb.msmservice.controller;
import com.yzpnb.common_utils.Result;
import com.yzpnb.msmservice.service.MsmService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/msmservice/")
@CrossOrigin
public class MsmController {
@Autowired
private MsmService msmService;
@Autowired
private RedisTemplate<String,String> redisTemplate;//用来操作redis
@ApiOperation("根据手机号发送短信")
@GetMapping("shortMessage/{phoneNumber}")
public Result shortMessage(@ApiParam(name = "phoneNumber",value = "电话号")
@PathVariable String phoneNumber){
/**实现验证码5分钟内有效*/
//1、从redis中获取验证码,获取到直接返回,获取不到重新生成发送
String s = redisTemplate.opsForValue().get(phoneNumber);
if(!StringUtils.isEmpty(s)){
return Result.ok();
}
/**redis中没有内容,表示验证码过期,重新生成*/
//1、生成验证码
DecimalFormat fourdf=new DecimalFormat("0000");//生成4位模板
String code = fourdf.format(new Random().nextInt(10000));//生成4位随机数字符串
//2、将验证码发送
Map<String,Object> map=new HashMap<>();
map.put("code",code);//我习惯将验证码存储到Map集合中
Boolean flag = msmService.shortMessage(phoneNumber, map);//传入手机号和验证码
if (flag==true){
//发送成功,把验证码放在redis中,并设置有效时间
//key用手机号,value是验证码,5表示,TimeUnit.MINUTES表示分钟,配合上数字5表示5分钟
redisTemplate.opsForValue().set(phoneNumber,code,5, TimeUnit.MINUTES);
return Result.ok();
}else{
return Result.error().message("发送失败");
}
}
}

6、编写service
package com.yzpnb.msmservice.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.yzpnb.msmservice.service.MsmService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Map;
@Service
public class MsmServiceImpl implements MsmService {
/**
* 根据手机号发送短信
* @param phoneNumber
* @param map
* @return
*/
@Override
public Boolean shortMessage(String phoneNumber, Map<String, Object> map) {
if(StringUtils.isEmpty(phoneNumber)) return false;//如果手机号为空,返回false
/**
* 生成默认链接,第一个参数域节点,用默认即可
* 第二个参数是阿里云id
* 第三个参数是阿里云密钥
*/
DefaultProfile profile=DefaultProfile.getProfile("default",
"你的密钥id",
"你的密钥");
IAcsClient acsClient = new DefaultAcsClient(profile);
//设置参数,不要改
CommonRequest request = new CommonRequest();
//request.setProtocol(ProtocolType.HTTPS);
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");
request.setVersion("2017-05-25");
request.setAction("SendSms");
//设置发送内容(写你自己的)
request.putQueryParameter("PhoneNumbers", phoneNumber);//设置发送手机号
request.putQueryParameter("SignName", "我的农大线上学院网站");//你申请的签名的名称
request.putQueryParameter("TemplateCode", "你的模板CODE");//你的模板CODE
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(map));//验证码,需要用json格式,所以使用map,可以直接转换为json
//发送
boolean b=false;//默认为不成功
try {
CommonResponse commonResponse =acsClient.getCommonResponse(request);//发送短信
b=commonResponse.getHttpResponse().isSuccess();//返回是否发送成功
} catch (ClientException e) {
e.printStackTrace();
}
return b;
}
}

7、测试



四、登陆接口(后端)
1、环境搭建
1、数据库源码

# Host: 47.93.118.241:33306 (Version 5.7.21)
# Date: 2019-11-15 11:58:11
# Generator: MySQL-Front 6.1 (Build 1.26)
#
# Structure for table "ucenter_member"
#
CREATE TABLE `ucenter_member` (
`id` char(19) NOT NULL COMMENT '会员id',
`openid` varchar(128) DEFAULT NULL COMMENT '微信openid',
`mobile` varchar(11) DEFAULT '' COMMENT '手机号',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`sex` tinyint(2) unsigned DEFAULT NULL COMMENT '性别 1 女,2 男',
`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
`avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
`sign` varchar(100) DEFAULT NULL COMMENT '用户签名',
`is_disabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否禁用 1(true)已禁用, 0(false)未禁用',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';
#
# Data for table "ucenter_member"
#
INSERT INTO `ucenter_member` VALUES ('1',NULL,'13700000001','96e79218965eb72c92a549dd5a330112','小三123',1,5,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-01 12:11:33','2019-11-08 11:56:01'),('1080736474267144193',NULL,'13700000011','96e79218965eb72c92a549dd5a330112','用户XJtDfaYeKk',1,19,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-02 12:12:45','2019-01-02 12:12:56'),('1080736474355224577',NULL,'13700000002','96e79218965eb72c92a549dd5a330112','用户wUrNkzAPrc',1,27,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-02 12:13:56','2019-01-02 12:14:07'),('1086387099449442306',NULL,'13520191388','96e79218965eb72c92a549dd5a330112','用户XTMUeHDAoj',2,20,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099520745473',NULL,'13520191389','96e79218965eb72c92a549dd5a330112','用户vSdKeDlimn',1,21,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099608825858',NULL,'13520191381','96e79218965eb72c92a549dd5a330112','用户EoyWUVXQoP',1,18,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099701100545',NULL,'13520191382','96e79218965eb72c92a549dd5a330112','用户LcAYbxLNdN',2,24,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099776598018',NULL,'13520191383','96e79218965eb72c92a549dd5a330112','用户dZdjcgltnk',2,25,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1086387099852095490',NULL,'13520191384','96e79218965eb72c92a549dd5a330112','用户wNHGHlxUwX',2,23,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-01-19 06:17:23','2019-01-19 06:17:23'),('1106746895272849410','o1R-t5u2TfEVeVjO9CPGdHPNw-to',NULL,NULL,'檀梵\'',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/zZfLXcetf2Rpsibq6HbPUWKgWSJHtha9y1XBeaqluPUs6BYicW1FJaVqj7U3ozHd3iaodGKJOvY2PvqYTuCKwpyfQ/132',NULL,0,0,'2019-03-16 10:39:57','2019-03-16 10:39:57'),('1106822699956654081',NULL,NULL,NULL,NULL,NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-03-16 15:41:10','2019-03-16 15:41:10'),('1106823035660357634','o1R-t5i4gENwHYRb5lVFy98Z0bdk',NULL,NULL,'GaoSir',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJI53RcCuc1no02os6ZrattWGiazlPnicoZQ59zkS7phNdLEWUPDk8fzoxibAnXV1Sbx0trqXEsGhXPw/132',NULL,0,0,'2019-03-16 15:42:30','2019-03-16 15:42:30'),('1106823041599492098',NULL,NULL,NULL,NULL,NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-03-16 15:42:32','2019-03-16 15:42:32'),('1106823115788341250','o1R-t5l_3rnbZbn4jWwFdy6Gk6cg',NULL,NULL,'换个网名哇、',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/jJHyeM0EN2jhB70LntI3k8fEKe7W6CwykrKMgDJM4VZqCpcxibVibX397p0vmbKURGkLS4jxjGB0GpZfxCicgt07w/132',NULL,0,0,'2019-03-16 15:42:49','2019-03-16 15:42:49'),('1106826046730227714','o1R-t5gyxumyBqt0CWcnh0S6Ya1g',NULL,NULL,'我是Helen',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKDRfib8wy7A2ltERKh4VygxdjVC1x5OaOb1t9hot4JNt5agwaVLdJLcD9vJCNcxkvQnlvLYIPfrZw/132',NULL,0,0,'2019-03-16 15:54:28','2019-03-16 15:54:28'),('1106828185829490690','o1R-t5nNlou5lRwBVgGNJFm4rbc4',NULL,NULL,' 虎头',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKxCqRzuYWQmpwiaqQEjNxbC7WicebicXQusU306jgmfoOzUcFg1qaDq5BStiblwBjw5dUOblQ2gUicQOQ/132',NULL,0,0,'2019-03-16 16:02:58','2019-03-16 16:02:58'),('1106830599651442689','o1R-t5hZHQB1cbX7HZJsiM727_SA',NULL,NULL,'是吴啊',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJ9CsqApybcs7f3Dyib9IxIh0sBqJb7LicbjU4WticJFF0PVwFvHgtbFdBwfmk3H2t3NyqmEmVx17tRA/132',NULL,0,0,'2019-03-16 16:12:34','2019-03-16 16:12:34'),('1106830976199278593','o1R-t5meKOoyEJ3-IhWRCBKFcvzU',NULL,NULL,'我才是Helen',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epMicP9UT6mVjYWdno0OJZkOXiajG0sllJTbGJ9DYiceej2XvbDSGCK8LCF7jv1PuG2uoYlePWic9XO8A/132',NULL,0,0,'2019-03-16 16:14:03','2019-03-16 16:14:03'),('1106831936900415490','o1R-t5jXYSWakGtnUBnKbfVT5Iok',NULL,NULL,'文若姬',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/3HEmJwpSzguqqAyzmBwqT6aicIanswZibEOicQInQJI3ZY1qmu59icJC6N7SahKqWYv24GvX5KH2fibwt0mPWcTJ3fg/132',NULL,0,0,'2019-03-16 16:17:52','2019-03-16 16:17:52'),('1106832491064442882','o1R-t5sud081Qsa2Vb2xSKgGnf_g',NULL,NULL,'Peanut',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-03-16 16:20:04','2019-03-16 16:20:04'),('1106833021442510849','o1R-t5lsGc3I8P5bDpHj7m_AIRvQ',NULL,NULL,'食物链终结者',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/MQ7qUmCprK9am16M1Ia1Cs3RK0qiarRrl9y8gsssBjIZeS2GwKSrnq7ZYhmrzuzDwBxSMMAofrXeLic9IBlW4M3Q/132',NULL,0,0,'2019-03-16 16:22:11','2019-03-16 16:22:11'),('1191600824445046786',NULL,'15210078344','96e79218965eb72c92a549dd5a330112','IT妖姬',1,5,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-11-05 14:19:10','2019-11-08 18:04:43'),('1191616288114163713',NULL,'17866603606','96e79218965eb72c92a549dd5a330112','xiaowu',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-11-05 15:20:37','2019-11-05 15:20:37'),('1195187659054329857',NULL,'15010546384','96e79218965eb72c92a549dd5a330112','qy',NULL,NULL,'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132',NULL,0,0,'2019-11-15 11:51:58','2019-11-15 11:51:58');
2、创建子模块

3、application.yml
server:
port: 8006 #微服务端口号为8006
spring:
application:
name: service-ucenter #服务名
profiles:
active: dev #环境设置 dev表示构建阶段,test表示测试阶段,prod表示发布阶段
datasource: #数据源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/gulischool?serverTimeZone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
jackson: #我们的时区是东八区,应该加8个小时,时区显示格式也需要改成我们想要的
date-format: yyyy-MM-DD HH:mm:ss
time-zone: GMT+8
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos
feign:
client:
config:
default:
connect-timeout: 10000 #设置超时限制,必须超过10000ms才报错
read-timeout: 10000 #设置Feign服务熔断机制的最大超时限制
hystrix:
enabled: true #开启熔断机制
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 #设置hystri超时时间 默认1000ms(10s)
mybatis-plus:
mapper-locations: classpath:com/yzpnb/eduservice/mapper/xml/*.xml #配置mapper xml文件的路径
4、使用代码生成器,生成表对应MVC层次结构(记住将参数改成你自己的)
package gennerator;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
/**
* @author
* @since 2018/12/13
*/
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("D:\\IdeaProjects\\gulischool\\service\\service-ucenter" + "/src/main/java"); //输出目录,生成的代码最终输出的地方,请写你的项目的绝对路径
gc.setAuthor("testjava");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略,ID_WORKER表示默认Integer,ID_WORKER_STR表示字符型
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/gulischool?serverTimeZone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置(生成的包名)
PackageConfig pc = new PackageConfig();
pc.setParent("com.yzpnb");
pc.setModuleName("ucenter_service"); //模块名 com.yzpnb.eduservice
pc.setController("controller"); //com.yzpnb.eduservice.controller
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("ucenter_member");//你要为哪个表生成代码
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}

5、给实体类加自动填充,给controller层加跨域

6、启动类
package com.yzpnb.ucenter_service;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.yzpnb")
@MapperScan("com.yzpnb.ucenter_service.mapper")
public class UcenterApplication {
public static void main(String[] args) {
SpringApplication.run(UcenterApplication.class,args);
}
}
2、编写MD5加密工具类
| MD5 |
|---|
| 目前最常见的一种密码加密形式 |
| 此加密的最大特点就是,只能加密,无法解密 |
| 我们想要判断输入的密码是否等于数据库中存储的加密后的密码 |
| 必须使用你设定的相同的MD5规则加密后,与数据库中数据比较 |
| 为什么无法解密 |
|---|
| 很简单,加密的时候采用位运算,并在每个加密后的字符后面加上,加密前字符的全新加密格式 |
| 位运算后的数据,由于进1,补零,原来的值就不见了,无法通过反运算获取原值 |
| 所以就是把加密规则告诉你,你也无法反加密出原数据,因为你根本不知道,加密后消掉了几个1,补了多少个零 |
package com.yzpnb.common_utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5 {
public static String encrypt(String strSrc){
char hexChars[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
byte[] bytes=strSrc.getBytes();//将字符串转换成字节数组
try {
MessageDigest md =MessageDigest.getInstance("MD5");//通过反射获取MD5加密Digest对象
md.update(bytes); //将字节数组传入
bytes=md.digest(); //将字节数组改为加密后的字节数组
int j=bytes.length; //获取字节数组的长度
char[] chars=new char[j*2]; //新建一个字符数组,长度为字节数组长度的二倍
int k=0; //定义一个变量,用来指定字符数组的下标
for(int i=0;i<bytes.length;i++){ //循环字节数组
byte b=bytes[i]; //依次获取字节数组中的值
chars[k++]=hexChars[b>>> 4 & 0xf]; //将字节右位移4次 然后对十六进制f进行位与运算加密
chars[k++]=hexChars[b & 0xf]; //在每个字符的后面,跟上字节对十六进制f的位与运算加密值
}
return new String(chars); //最后将加密好的字符数组转换为字符串,返回
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+"+e);
}
}
}

3、编写controller接口
package com.yzpnb.ucenter_service.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yzpnb.common_utils.Result;
import com.yzpnb.ucenter_service.entity.UcenterMember;
import com.yzpnb.ucenter_service.service.UcenterMemberService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 会员表 前端控制器
* </p>
*
* @author testjava
* @since 2020-06-03
*/
@RestController
@RequestMapping("/ucenter_service/ucenter-member")
@CrossOrigin
public class UcenterMemberController {
@Autowired
private UcenterMemberService ucenterMemberService;
@ApiOperation("登录,判断登录的手机号和密码是否正确,返回JWT加密后token字符串")
@PostMapping("login")
public Result login(@RequestBody UcenterMember ucenterMember){
String token=ucenterMemberService.login(ucenterMember);//返回由JWT生成的token字符串
return Result.ok().data("token",token);
}
}

4、service接口
package com.yzpnb.ucenter_service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yzpnb.common_utils.JwtUtils;
import com.yzpnb.common_utils.MD5;
import com.yzpnb.service_base_handler.CustomExceptionHandler;
import com.yzpnb.ucenter_service.entity.UcenterMember;
import com.yzpnb.ucenter_service.mapper.UcenterMemberMapper;
import com.yzpnb.ucenter_service.service.UcenterMemberService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* <p>
* 会员表 服务实现类
* </p>
*
* @author testjava
* @since 2020-06-03
*/
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {
/**
* 登录,判断登录的手机号和密码是否正确,返回JWT加密后token字符串
* @param ucenterMember
* @return
*/
@Override
public String login(UcenterMember ucenterMember) {
/**判断手机号和密码是否为空,为空直接终止程序,返回自定义异常信息*/
if(StringUtils.isEmpty(ucenterMember.getMobile()) || StringUtils.isEmpty(ucenterMember.getPassword())){
throw new CustomExceptionHandler(20001,"手机号或密码为空");
}
/**判断手机号和密码是否正确,账号是否可用,有一项出错,直接返回自定义异常**/
//根据手机号查询数据,有值表示手机号正确,没值直接失败
QueryWrapper<UcenterMember> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("mobile",ucenterMember.getMobile());
UcenterMember one = baseMapper.selectOne(queryWrapper);
if(one !=null){//有值表示手机号正确
//通过MD5加密用户输入的密码,比较是否与数据库中密码相同
String password= MD5.encrypt(ucenterMember.getPassword());//根据工具类获取加密后密码
if(!one.getPassword().equals(password)){//判断密码是否正确,不正确返回false,通过非运算获取到true,报异常
throw new CustomExceptionHandler(20001,"密码错误");
}
//判断当前是否禁用状态
if(one.getIsDisabled()){//如果isDisable为真值,表示现在此账号是禁用状态
throw new CustomExceptionHandler(20001,"此账号现在被禁用,可能有其他人登录");
}
}else{//没有值表示手机号没有注册
throw new CustomExceptionHandler(20001,"此手机号没有注册");
}
/**走到这说明信息全部正确,使用JWT根据id和昵称生成token字符串并返回**/
String token = JwtUtils.getJwtToken(one.getId(), one.getNickname());//传入我们查出来的对象的值,形参中,只有电话号和密码
return token;
}
}

5、测试


五、注册接口(后端,需要spring cloud的feign组件,跨服务调用阿里云短信微服务)
1、创建实体类,包含注册信息和验证码
package com.yzpnb.ucenter_service.entity.vo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.stereotype.Component;
@Component
@Data
public class RegisterVo {
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "验证码")
private String code;
}

2、controller
@ApiOperation("注册,判断注册手机号是否已经存在,密码需要加密存储,需要验证码正确才能注册成功")
@PostMapping("register")
public Result register(@ApiParam(name = "registerVo",value = "注册对象")
@RequestBody RegisterVo registerVo){
ucenterMemberService.register(registerVo);
return Result.ok();
}
3、service
/**
* 注册,判断注册手机号是否已经存在,密码需要加密存储,需要验证码正确才能注册成功
* @param registerVo
*/
@Autowired
RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();//创建redisTemplate对象,使用redis数据库
@Override
public void register(RegisterVo registerVo) {
/**获取数据**/
String mobile=registerVo.getMobile(); //获取手机号
String password=registerVo.getPassword(); //获取密码
String nickname=registerVo.getNickname(); //获取昵称
String code=registerVo.getCode(); //获取验证码
/**判断手机号和密码是否为空,为空直接终止程序,返回自定义异常信息,不为空,判断手机号是否重复*/
if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)){
throw new CustomExceptionHandler(20001,"手机号或密码为空");
}else{
//不为空则判断手机号是否重复
QueryWrapper<UcenterMember> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("mobile",mobile);
int count = baseMapper.selectCount(queryWrapper);//查询是否有相同电话号,返回符合匹配条件的数据条数
if(count>0){//如果满足条件的个数>0表示已经有此电话号,抛异常
throw new CustomExceptionHandler(20001,"此手机号已经被注册");
}
}
/**判断昵称是否为空,不为空判断是否重复,如果昵称为空,或者昵称重复,报自定义异常**/
if(StringUtils.isEmpty(nickname)){
throw new CustomExceptionHandler(20001,"请输入昵称");
}else{
//不为空判断是否昵称重复
QueryWrapper<UcenterMember> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("nickname",nickname);
int count = baseMapper.selectCount(queryWrapper);//查询是否有相同电话号,返回符合匹配条件的数据条数
if(count>0){//如果满足条件的个数>0表示已经有此昵称,抛异常
throw new CustomExceptionHandler(20001,"昵称重复");
}
}
/**判断当前手机号的验证码是否已经过期,或者没有生成**/
if(redisTemplate.hasKey(mobile)==false) throw new CustomExceptionHandler(20001,"此手机号的验证码没有生成或已过期");
/**确保发送过验证码以后,判断验证码是否为空,不为空则判断验证码是否正确,为空,报自定义异常**/
if(StringUtils.isEmpty(code)){
throw new CustomExceptionHandler(20001,"请输入验证码");
}else{
//判断验证码是否与redis中存储的key为当前手机号的value值相等
String value = redisTemplate.opsForValue().get(mobile);//获取key为当前手机号的value值
if(!value.equals(code)){
throw new CustomExceptionHandler(20001,"验证码错误");
}
}
/**走到这说明没问题,将密码加密,存入数据库**/
//一切信息正确且不重复,加密密码
password=MD5.encrypt(password);
UcenterMember ucenterMember=new UcenterMember();//创建用户对象
ucenterMember.setMobile(mobile);
ucenterMember.setPassword(password);
ucenterMember.setNickname(nickname);
ucenterMember.setIsDisabled(false);//用户不禁用
//设置默认头像
ucenterMember.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");
baseMapper.insert(ucenterMember);//添加数据
}
4、测试



六、根据token获取用户信息
1、controller
@ApiOperation("根据token获取用户信息")
@GetMapping("getUserInfo")
public Result getUserInfo(HttpServletRequest request){//使用原生HttpServlet请求对象,获取请求体
//通过JWT工具类方法,根据request请求头,返回用户id
String id = JwtUtils.getMemberIdByJwtToken(request);
//根据id查询数据
UcenterMember ucenterMember = ucenterMemberService.getById(id);
return Result.ok().data("userInfo",ucenterMember);
}
七、登陆与注册(前端,需要element ui,js-cookie,vue-qriously)
| 使用element ui |
|---|
import ElementUI from 'element-ui' //element-ui的全部组件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI

1、api接口,配置nginx


3、布局页面
<template>
<div class="sign">
<!--标题-->
<div class="logo">
<img src="~/assets/img/logo.png" alt="logo">
</div>
<!--表单-->
<nuxt/>
</div>
</template>

2、静态页面(代码全在GitHub中)



3、代码实现
1、注册(源代码到github中取)




2、登陆(因为我们登陆需要使用cookie,所以需要下载js-cookie插件)
| 1、下载js-cookie插件:npm install js-cookie |
|---|


| 4、将token存入cookie,请求登陆接口,获取用户信息,然后将用户信息,存入cookie中 |
|---|
| 注意这是第四步哦!但为我好理解,将这步放前面 |
| {domain:localhost}:表示只要当前访问的是localhost,cookie都会传递,你可以改成ip地址 |

| 2、创建前端拦截器,拦截request请求,查看cookie中是否有token字符串,有就将字符串放到header(请求头中) |
|---|


import axios from 'axios'
import cookie from 'js-cookie'
import { MessageBox, Message } from 'element-ui'
// 创建axios实例
const service = axios.create({
baseURL: 'http://localhost:9001', // api的base_url
timeout: 20000 // 请求超时时间
})
// http request 拦截器
service.interceptors.request.use(
config => {
//debugger
if (cookie.get('token')) {
config.headers['token'] = cookie.get('token');
}
return config
},
err => {
return Promise.reject(err);
})
// http response 拦截器
service.interceptors.response.use(
response => {
//debugger
if (response.data.code == 28004) {
console.log("response.data.resultCode是28004")
// 返回 错误代码-1 清除ticket信息并跳转到登录页面
//debugger
window.location.href="/login"
return
}else{
if (response.data.code !== 20000) {
//25000:订单支付中,不做任何提示
if(response.data.code != 25000) {
Message({
message: response.data.message || 'error',
type: 'error',
duration: 5 * 1000
})
}
} else {
return response;
}
}
},
error => {
return Promise.reject(error.response) // 返回接口返回的错误信息
});
export default service
| 3、根据token值调用接口,获取用户信息,将返回的信息存入cookie |
|---|


| 5、首页面显示coolie中的用户信息 |
|---|



八、OAuth2.0
1、OAuth2
| 针对特定问题的解决方案 |
|---|
| 1、开放系统间授权问题 |
| 2、分布式访问问题 |
| 开放系统间授权问题 |
|---|
| 举例:相片拥有者,将照片存储到云中,这时负责打印的,是没有访问云中照片的授权的,所以无法打印 |
| 而相片拥有者,这时通过授权,让打印机可以打印照片 |

| 方式1、适用于同一公司内部的多个系统,不适用于不受信的第三方应用 |
|---|

| 方式2、适用于合作商或者授信的不同业务部门之间 |
|---|

| 方式3、接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议 |
|---|

| 分布式访问问题,解决微服务安全 |
|---|




| 去哪里学习?《OAuth2最简向导》 |
|---|
| 川崎高彦:OAuth2领域专家,开发了一个OAuth2 sass服务,OAuth2 as Service,并且做成了一个公司 |
| 再融资的过程中为了向投资人解释OAuth2是什么,于是写了一篇文章,《OAuth2最简向导》 |
2、OAuth2.0实践了解








3、OAuth2的误解


九、微信扫码登录(难度较高,选择性观看)
1、申请资质
1、开发者资质(看看就好,想要注册你需要有一个公司)
| 注册网址https://open.weixin.qq.com |
|---|


2、申请网站应用名称(二维码下方显示你的应用名),网站域名地址(扫完码,会跳转这个域名)(看看就好)
| 尚硅谷提供的 |
|---|
wx:
open:
# 微信开放平台 appid
app_id: wxed9954c01bb89b47
# 微信开放平台appsecret
app_secret: a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url(guli.shop需要在微信开放平台配置)
redirect_url: http://guli.shop/api/ucenter/wx/callback
3、官方文档
| https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN |
|---|

2、配置application.yml
wx:
open:
# 微信开放平台 appid
app_id: wxed9954c01bb89b47
# 微信开放平台appsecret
app_secret: a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url(guli.shop需要在微信开放平台配置)
redirect_url: http://guli.shop/api/ucenter/wx/callback

3、配置类获取配置文件中的值
package com.yzpnb.ucenter_service.util;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConstantYmlUtil implements InitializingBean {
@Value("${wx.open.app_id}")
private String appId;
@Value("${wx.open.app_secret}")
private String appSecret;
@Value("${wx.open.redirect_url}")
private String redirectUrl;
public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;
@Override
public void afterPropertiesSet() throws Exception {
WX_OPEN_APP_ID = appId;
WX_OPEN_APP_SECRET = appSecret;
WX_OPEN_REDIRECT_URL = redirectUrl;
}
}

4、生成二维码

package com.yzpnb.ucenter_service.controller.api;
import com.yzpnb.service_base_handler.CustomExceptionHandler;
import com.yzpnb.ucenter_service.util.ConstantYmlUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@CrossOrigin
@Controller//注意这里没有配置 @RestController,如果你使用了此注解,此注解会将字符串返回,而不能重定向了,所以我们这个类不能用此注解
@RequestMapping("/ucenter_service/api/wx")
public class WxApiController {
//生成微信二维码
@GetMapping("login")
public String genQrConnect(HttpSession session) {
// 微信开放平台授权baseUrl
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" + //这里通过%s占位,之后我们可以通过String.format统一赋值,不容易出错,下面的内容都是
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
// 回调地址,redirectUrl,官方规定,我们需要对这个字符串进行URLEncoder编码
String redirectUrl = ConstantYmlUtil.WX_OPEN_REDIRECT_URL; //获取业务服务器重定向地址
try {
redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
} catch (UnsupportedEncodingException e) {
throw new CustomExceptionHandler(20001, e.getMessage());
}
// 防止csrf攻击(跨站请求伪造攻击)
//String state = UUID.randomUUID().toString().replaceAll("-", "");//一般情况下会使用一个随机数
String state = "imhelen";//为了让大家能够使用我搭建的外网的微信回调跳转服务器,这里填写你在ngrok的前置域名
System.out.println("state = " + state);
// 采用redis等进行缓存state 使用sessionId为key 30分钟后过期,可配置
//键:"wechar-open-state-" + httpServletRequest.getSession().getId()
//值:satte
//过期时间:30分钟
//生成qrcodeUrl
String qrcodeUrl = String.format(//为baseUrl中的占位符传值
baseUrl,
ConstantYmlUtil.WX_OPEN_APP_ID,
redirectUrl,
state);
return "redirect:" + qrcodeUrl;//重定向到地址,如果你使用了@RestController注解,此注解会将字符串返回,而不能重定向了,所以我们这个类不能用此注解
}
}

5、测试二维码


6、通过一些小手段,让我们扫描微信二维码后,跳转到我们本地(只是给我们这些没有公司的人用,比如和我一样还在上学的人,只能蹭人家的二维码)
| 尚硅谷的重定向接口地址:http://guli.shop/api/ucenter/wx/callback |
|---|
| 它封装里一些重定向代码,当我们扫描二维码时,会重定向到 |
| localhost:8150/api/ucenter/wx/callback?code=***** (省略内容) **** &state=******* |
| 1、把我们自己的端口号改为8150和重定向的端口一致 |
|---|

| 2、将我们的请求地址和重定向一致,改为api/ucenter/wx |
|---|

| 3、将我们的方法起名为和重定向地址相同的callback(代码还没写完,不需要照着写) |
|---|



7、创建HTTPClient工具类(HttpClient是sun公司java原生技术,可以不用浏览器模拟http请求)
1、引入依赖
<!--httpClient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
<!--commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>

2、工具类
package com.yzpnb.ucenter_service.util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
* @author zhaoyb
*
*/
public class HttpClientUtils {
public static final int connTimeout=10000;
public static final int readTimeout=10000;
public static final String charset="UTF-8";
private static HttpClient client = null;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(128);
cm.setDefaultMaxPerRoute(128);
client = HttpClients.custom().setConnectionManager(cm).build();
}
public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
}
public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
}
public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
return postForm(url, params, null, connTimeout, readTimeout);
}
public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
return postForm(url, params, null, connTimeout, readTimeout);
}
public static String get(String url) throws Exception {
return get(url, charset, null, null);
}
public static String get(String url, String charset) throws Exception {
return get(url, charset, connTimeout, readTimeout);
}
/**
* 发送一个 Post 请求, 使用指定的字符集编码.
*
* @param url
* @param body RequestBody
* @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
* @param charset 编码
* @param connTimeout 建立链接超时时间,毫秒.
* @param readTimeout 响应超时时间,毫秒.
* @return ResponseBody, 使用指定的字符集编码.
* @throws ConnectTimeoutException 建立链接超时异常
* @throws SocketTimeoutException 响应超时
* @throws Exception
*/
public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
throws ConnectTimeoutException, SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
String result = "";
try {
if (StringUtils.isNotBlank(body)) {
HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
post.setEntity(entity);
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(post);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 提交form表单
*
* @param url
* @param params
* @param connTimeout
* @param readTimeout
* @return
* @throws ConnectTimeoutException
* @throws SocketTimeoutException
* @throws Exception
*/
public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
try {
if (params != null && !params.isEmpty()) {
List<NameValuePair> formParams = new ArrayList<NameValuePair>();
Set<Entry<String, String>> entrySet = params.entrySet();
for (Entry<String, String> entry : entrySet) {
formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
post.setEntity(entity);
}
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entry : headers.entrySet()) {
post.addHeader(entry.getKey(), entry.getValue());
}
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res = null;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(post);
}
return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null
&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
}
/**
* 发送一个 GET 请求
*
* @param url
* @param charset
* @param connTimeout 建立链接超时时间,毫秒.
* @param readTimeout 响应超时时间,毫秒.
* @return
* @throws ConnectTimeoutException 建立链接超时
* @throws SocketTimeoutException 响应超时
* @throws Exception
*/
public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
throws ConnectTimeoutException,SocketTimeoutException, Exception {
HttpClient client = null;
HttpGet get = new HttpGet(url);
String result = "";
try {
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
get.setConfig(customReqConf.build());
HttpResponse res = null;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(get);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(get);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
get.releaseConnection();
if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 从 response 里获取 charset
*
* @param ressponse
* @return
*/
@SuppressWarnings("unused")
private static String getCharsetFromResponse(HttpResponse ressponse) {
// Content-Type:text/html; charset=GBK
if (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
String contentType = ressponse.getEntity().getContentType().getValue();
if (contentType.contains("charset=")) {
return contentType.substring(contentType.indexOf("charset=") + 8);
}
}
return null;
}
/**
* 创建 SSL连接
* @return
* @throws GeneralSecurityException
*/
private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
@Override
public void verify(String host, SSLSocket ssl)
throws IOException {
}
@Override
public void verify(String host, X509Certificate cert)
throws SSLException {
}
@Override
public void verify(String host, String[] cns,
String[] subjectAlts) throws SSLException {
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (GeneralSecurityException e) {
throw e;
}
}
public static void main(String[] args) {
try {
String str= post("https://localhost:443/ssl/test.shtml","name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
//String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
/*Map<String,String> map = new HashMap<String,String>();
map.put("name", "111");
map.put("page", "222");
String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/
System.out.println(str);
} catch (ConnectTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SocketTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

8、controller层接口
@Autowired
UcenterMemberService ucenterMemberService;
@ApiOperation("获取扫描人信息,添加数据")
@GetMapping("callback")
public String callback(@ApiParam(name = "code",value = "code是微信官方api生成的唯一验证码(有过期时间),这个值可以通过请求固定地址" +
"获取访问凭证(access_token)和当前微信的唯一id值(openid)" +
"获取的凭证和id值可以让我们获取到微信扫码人的信息(头像昵称等)")String code,
@ApiParam(name = "state",value = "state是生成二维码的时候原样传递过来的") String state){//获取code和state
/**1、通过获取的code值,请求固定地址获取访问凭证和微信id**/
//1、向认证服务器发送请求换取access_token
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";//占位形式拼接固定地址
String accessTokenUrl = String.format(baseAccessTokenUrl,
ConstantYmlUtil.WX_OPEN_APP_ID,
ConstantYmlUtil.WX_OPEN_APP_SECRET,
code);
String result = null;
try {
result = HttpClientUtils.get(accessTokenUrl);//根据http请求获取微信响应结果
System.out.println("accessToken=============" + result);
} catch (Exception e) {
throw new CustomExceptionHandler(20001, "获取access_token失败");
}
//解析json字符串
Gson gson = new Gson();
HashMap map = gson.fromJson(result, HashMap.class);
String accessToken = (String)map.get("access_token");//获取访问凭证
String openid = (String)map.get("openid"); //获取微信id
/**2、根据微信id获取微信的用户信息**/
//查询数据库当前用用户是否曾经使用过微信登录
QueryWrapper<UcenterMember> queryWrapper =new QueryWrapper<>();
queryWrapper.eq("openid",openid);
UcenterMember ucenterMember = ucenterMemberService.getOne(queryWrapper);
if(ucenterMember == null){//没有值表示没用过,需要获取信息并注册
System.out.println("新用户注册");
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openid);
String resultUserInfo = null;
try {
resultUserInfo = HttpClientUtils.get(userInfoUrl);
System.out.println("resultUserInfo==========" + resultUserInfo);
} catch (Exception e) {
throw new CustomExceptionHandler(20001, "获取用户信息失败");
}
//解析json
HashMap<String, Object> mapUserInfo = gson.fromJson(resultUserInfo, HashMap.class);
String nickname = (String)mapUserInfo.get("nickname");
String headimgurl = (String)mapUserInfo.get("headimgurl");
//向数据库中插入一条记录
ucenterMember = new UcenterMember();
ucenterMember.setNickname(nickname);
ucenterMember.setOpenid(openid);
ucenterMember.setAvatar(headimgurl);
ucenterMemberService.save(ucenterMember);
}//如果有值,直接登录即可
// 生成jwt
String token = JwtUtils.getJwtToken(ucenterMember.getId(),ucenterMember.getNickname());
//存入cookie
//CookieUtils.setCookie(request, response, "guli_jwt_token", token);
//因为端口号不同存在蛞蝓问题,cookie不能跨域,所以这里使用url重写
return "redirect:http://localhost:3000?token=" + token;
}
十、前端整合测试
1、修改nginx(记得改完保存然后重启)

2、改代码




3、测试




SSO与JWT整合实战

被折叠的 条评论
为什么被折叠?



