单点登录及JWT
部分内容引用https://achang.blog.csdn.net/article/details/114419056
单点登录概念
单点登录(single sign on),简称SSO
应用于集群分布中,假设有多个服务器或多个tomcat,用户在一个服务器中登录,则其他的服务器就不需要登录(好比登录了QQ农场就不用登录QQ牧场)
另外,如果是单一服务器的化,直接用session实现登录更为方便,如
session.setAttribute(“user”,user)
session.getAttribute()
实现方式
1、session广播机制
即session的复制,但浪费资源,用比较少
过期时间设置:session默认过期时间30min,可修改这个来决定过期时间
2、cookie + redis实现
(1)在项目任意一个模块登录,然后将数据放到cookie和redis中
在redis中,key中存入随机唯一值(用户id、uid等),value中存放用户数据
(2)把redis中生成的key值放到cookie中
(3)访问项目其他模块,发送请求带着cookie,从cookie中的值拿到redis做查询,如果能查询出数据,则已经登录,否则没有登录
过期时间设置:可以设置redis中的过期时间
3、使用token实现
token(令牌):按照一定规则(加密)生成的字符串,字符串可以包含用户信息
(1)在项目某模块登录,然后按照规则生成字符串,把登录之后用户包含到生成字符串里面,把字符串返回(可以
通过cookie返回,或通过地址栏返回)
(2)再去访问项目其他模块,每次访问在地址栏带着生成字符串,在访问模块里面获取地址栏字符串,根据字符串获取用户信息,如果可以获取就是已登录
JWT
token是按照一定规则生成的字符串,JWT是规定好的规则,使用JWT可以生成字符串
组成如下:
java中实现
依赖pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
JwtUtils.java(能看懂即可)
package com.liang.commonutils;
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;
public class JwtUtils {
//token过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
//秘钥,每个公司生成规则不一样
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
//生成token字符串方法
public static String getJwtToken(String id, String nickname) {
String JwtToken = Jwts.builder()
//设置jwt头信息,红色部分,内容固定,不需要改
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置过期时间
//设置token主体部分,存储用户信息,可设置多个值
.claim("id", id)
.claim("nickname", nickname)
//设置签名哈希(防伪标志)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
*
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if (StringUtils.isEmpty(jwtToken)) return false;
try {
//根据设置的防伪码解析token
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;
//根据设置的防伪码解析token
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");
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");
}
}
aly短信服务
建立相关模块并配置
创建模块service-msm,创建相关controller及service等文件夹
配置application.properties文件,将数据库信息、redis信息等配置好
# 用于aly短信服务
# 服务器端口
server.port=8005
# 服务名
spring.application.name=service-msm
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# nacos注册中心
spring.cloud.nacos.discovery.server-addr= localhost:8848
#配置mapper xml文件的路径
#mybatis-plus.mapper-locations=classpath:com/liang/msmservice/mapper/xml/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# redis
spring.redis.host=192.168.19.***
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#spring.redis.password=你设置的redis密码,没有可以不写
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
# mysql数据库连接
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=****
#环境测试:dev,test,prod
spring.profiles.active=dev
实现功能
在aly中开通服务(服务名称叫:短信服务),并申请
不过现在是企业才能申请了,所以这里使用aly的测试版
程序可看里面的示例代码
建立可以生成码的类:
package com.liang.msmservice.utils;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
public class RandomUtil {
private static final Random random = new Random();
private static final DecimalFormat fourdf = new DecimalFormat("0000");
private static final DecimalFormat sixdf = new DecimalFormat("000000");
//生成4位随机数
public static String getFourBitRandom() {
return fourdf.format(random.nextInt(10000));
}
//生成6位随机数
public static String getSixBitRandom() {
return sixdf.format(random.nextInt(1000000));
}
/**
* 给定数组,抽取n个数据
* @param list
* @param n
* @return
*/
public static ArrayList getRandom(List list, int n) {
Random random = new Random();
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
// 生成随机数字并存入HashMap
for (int i = 0; i < list.size(); i++) {
int number = random.nextInt(100) + 1;
hashMap.put(number, i);
}
// 从HashMap导入数组
Object[] robjs = hashMap.values().toArray();
ArrayList r = new ArrayList();
// 遍历数组并打印数据
for (int i = 0; i < n; i++) {
r.add(list.get((int) robjs[i]));
System.out.print(list.get((int) robjs[i]) + "\t");
}
System.out.print("\n");
return r;
}
}
然后根据aly的示例代码进行修改,这里需要导入相关依赖
<!-- 用于阿里云短信测试示例代码-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.2.1</version>
</dependency>
controller.java
@Autowired
public MsmService msmService;
//发送短信方法
@GetMapping("sent/{phone}")
public R sendMsm(@PathVariable String phone){
//验证码的值是程序设计的
String code = RandomUtil.getFourBitRandom();
Map<String,Object> map = new HashMap<>();
map.put("code",code);
boolean isSend = msmService.send(map,phone);
if(isSend){
return R.ok();
}else{
return R.error().message("发送短信失败!");
}
}
Service.java
@Override
//发送短信方法
public boolean send(Map<String, Object> map, String phone) {
System.out.println(Sample.keyId+"," + Sample.keySecret);
DefaultProfile profile = DefaultProfile.getProfile("cn-shanghai",Sample.keyId,Sample.keySecret);
IAcsClient client = new DefaultAcsClient(profile);
//登录平台
SendSmsRequest request = new SendSmsRequest();
request.setSignName("阿里云短信测试");
request.setTemplateCode("SMS_154950909");
request.setPhoneNumbers(phone);
String code = (String)map.get("code");
request.setTemplateParam("{\"code\":\"" + code + "\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println(new Gson().toJson(response));
} catch (ServerException e) {
e.printStackTrace();
return false;
} catch (ClientException e) {
System.out.println("ErrCode:" + e.getErrCode());
System.out.println("ErrMsg:" + e.getErrMsg());
System.out.println("RequestId:" + e.getRequestId());
return false;
}
return true;
}
这里的keyId和keySecret就是阿里云里面的,上述代码的Sample是自建类,就不列出来了,还有接口部分就省略不贴了。
在swagger进行测试:
不久后手机收到信息,成功