单点登录常见解决方式和阿里云短信服务

1 单点登录(SSO)

三种常见方式:

1.1 第一种:session广播机制实现(已淘汰)

概念:就是session复制,一个模块登录后,该模块session存放用户登录信息,再把该session复制到其它模块中,实现session广播,让所有的模块都能共享session;
缺点:当模块很多时,session需要需要复制多次,造成资源和内存的浪费

1.2 第二种:使用cookie+redis实现

概念:在项目中的任意一个模块进行登录,登录之后把数据放在redis和cookie中。
1.2.1 redis+cookie存放用户数据
第一步:
redis
key:唯一随机值(ip、用户id等)
value:存放用户登录数据
cookie
把redis里面的唯一key值放到cookie里面
第二步:
访问项目中的其他模块时,发送请求携带cookie进行发送,服务器接收到请求,获取cookie值,从cookie中获取redis存放的主键,通过redis key去查询对应用户数据,如果结果不为空,那么说明就已经登录了。

1.3 第三种:使用token实现

  1. 在项目某个模块进行登录,登录之后按照一定的规则生成字符串(token令牌-包含用户信息),再将这个字符串返回。返回方式可以通过cookie返回,也可以通过地址栏返回。
  2. 再去访问项目其他模块,每次访问都携带token令牌(地址栏里面带上token参数),服务器从地址栏中获取字符串(token令牌),根据token获取用户信息,如果结果不为空,那么就已经登录过了。

2 JWT(Json Web Token)

传统的登录鉴权方式:

2.1 session认证

概念:因为http协议本身是无状态的协议,那就意味着当有用户向系统使用账号和密码进行用户认证之后,下一次登录请求还要再进行一次用户认证才行。因为我们不能通过http协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至session),然后在认证成功后返回cookie值传递给浏览器,那么用户在下一次请求时就可以带上cookie值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。
缺点:因为session是保存在服务器里面的,所以对于分布式应用来说,由于session不共享,所以造成每一块微服务都需要进行用户认证…

2.2 token认证

概念:这种方式跟session的方式流程差不多,不同的地方在于保存的是一个token值到redis,token一般是一串随机的字符(比如UUID),value一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上token在请求头,后端接收到token则根据token查一下redis是否存在,如果存在则表示用户已认证,如果token不存在则跳到登录界面让用户重新登录,登录成功后返回一个token值给客户端。
优点是多台服务器都是使用redis来存取token,不存在不共享的问题,所以容易扩展。缺点是每次请求都需要查一下redis,会造成redis的压力,还有增加了请求的耗时,每个已登录的用户都要保存一个token在redis,也会消耗redis的存储空间。

2.3 JWT认证

token是按照一定的规则来生成字符串的,而JWT就是官方推荐使用的通用规则,利用JWT规则可以生成字符串。
JWT生成的字符串包含三部分:
第一部分:JWT头信息,它是一个描述JWT元数据的Json对象,例如:
{
“alg”: “HS256”, //算法
“typ”: “JWT” //令牌类型
}
第二部分:有效载荷,除了包含需要传递的数据(用户信息),还有七个默认的字段供选择。
{
//默认字段
“sub”:“主题123”,
//自定义字段
“name”:“java技术爱好者”,
“isAdmin”:“true”,
“loginTime”:“2021-12-05 12:00:03”
}
第三部分:签名(防伪标志)
首先需要指定一个secret,该secret仅仅保存在服务器中,保证不能让其他用户知道。然后使用Header指定的算法对Header和Payload进行计算,然后就得出一个签名哈希,也就是Signature。
那么如何进行验证呢?可以利用JWT前两段,用同一套哈希算法和同一个secret计算一个签名值,然后把计算出来的签名值和收到的JWT第三段比较,如果相同则认证通过。

2.3.1 JWT的使用

(1)引入依赖:

 <!-- JWT-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

(2)创建JWT工具类

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;
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    public static String getJwtToken(String id, String nickname){
    
    

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .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 {
    
    
            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");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

3 阿里云短信服务(SMS)

在这里插入图片描述

3.1 基于阿里云短信服务的短信验证码发送和接收

要想使用阿里云的短信服务,首先需要开通该服务,并且必须要签名和模板(签名就是短信抬头,一般是企业或者相关单位;模板也就是具体的短信内容)。该服务是收费的,按照条数收费,一条几分钱。
在这里插入图片描述

但是目前申请签名和模板需要提供公司资质证明和已备案的网站地址!
如果只是想测试下阿里云的短信服务,例如收发短信验证码,对此阿里云提供了免费的测试API。
在这里插入图片描述

根据阿里云提供的SDK示例,可以整合融入到我们自己的代码中从而实现验证码的手法。
在这里插入图片描述

3.1.1 阿里云短信验证码测试API示例

依赖引入:

 <!--   阿里云短信服务测试API依赖     -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>2.2.1</version>
        </dependency>
 <!--   单元测试     -->
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.4.RELEASE</version>
        </dependency>

service:

package com.zhmsky.msmService.service;

/**
* @author zhmsky
* @date 2022/7/6 21:00
*/
public interface MsmService {
    
    
    
    /**
    * 阿里云发送短信验证码
    * @param phone 接收验证码的手机号
    * @param code 被发送的验证码
    * @return
    */
    String sendCodeMsg(String phone,String code);
    
}

serviceImpl:

package com.zhmsky.msmService.service.impl;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.zhmsky.msmService.service.MsmService;
import org.springframework.stereotype.Service;

/**
 * @author zhmsky
 * @date 2022/7/6 21:01
 */
@Service
public class MsmServiceImpl implements MsmService {
    
    

    /**
     * 发送短信验证码
     * @param phone 手机号
     * @param code 被发送的验证码
     * @return
     */
    @Override
    public String sendCodeMsg(String phone, String code) {
    
    
        String checkCode="";
        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI5tDfHQPQ5WA5dBkrfFxu", "eRID7ZigveAH7fMRKbCDq92jjRl68R");


        IAcsClient client = new DefaultAcsClient(profile);

        SendSmsRequest request = new SendSmsRequest();
        request.setSignName("阿里云短信测试");
        request.setTemplateCode("SMS_154950909");
        request.setPhoneNumbers(phone);
        request.setTemplateParam("{\"code\":\""+code+"\"}");

        try {
    
    
            SendSmsResponse response = client.getAcsResponse(request);
            checkCode = response.getCode();
        } catch (ServerException e) {
    
    
            e.printStackTrace();
        } catch (ClientException e) {
    
    
            System.out.println("ErrCode:" + e.getErrCode());
            System.out.println("ErrMsg:" + e.getErrMsg());
            System.out.println("RequestId:" + e.getRequestId());
        }
        return checkCode;
    }
}

接口测试示例:

import com.zhmsky.msmService.MsmApplication;
import com.zhmsky.msmService.service.MsmService;
import com.zhmsky.msmService.utils.RandomUtil;
import javafx.application.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author zhmsky
 * @date 2022/7/6 23:58
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MsmApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MainTest {
    
    

    @Autowired
    private MsmService msmService;

    @Test
    public void msgSendTest(){
    
    
        String sixBitRandom = RandomUtil.getSixBitRandom();
        String s = msmService.sendCodeMsg("你的手机号(需要在阿里云短信服务测试API绑定)", sixBitRandom);
        if("OK".equals(s)){
    
    
            System.out.println("发送成功!");
        }else {
    
    
            System.out.println("发送失败!");
        }
    }
}

通过API文档可以查看对应的请求响应体:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42194695/article/details/125650464