谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现(一)

本文涉及的产品
短信服务,200条 3个月
数字短信套餐包(仅限零售电商行业),100条 12个月
短信服务,100条 3个月
简介: 谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现(一)

一、使用JWT进行跨域身份验证


1、传统用户身份验证


2904cdcfd4b64e191517735bc9c11ae9.jpg


Internet服务无法与用户身份验证分开。一般过程如下:


用户向服务器发送用户名和密码。

验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。

服务器向用户返回session_id,session信息都会写入到用户的Cookie。

用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。

服务器收到session_id并对比之前保存的数据,确认用户的身份。


这种模式最大的问题是,没有分布式架构,无法支持横向扩展。


2、解决方案


  1. session广播
  2. 将透明令牌存入cookie,将用户身份信息存入redis


另外一种灵活的解决方案:使用自包含令牌,通过客户端保存数据,而服务器不保存会话数据。 JWT是这种解决方案的代表。


二、JWT令牌


1、访问令牌的类型


9773f96c821ca24257c5ddc249f6fe99.jpg

2、JWT的组成


典型的,一个JWT看起来如下图:



a6c9cea9c28a9711c7953989b58c0bc5.png

该对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。

每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名


JWT头


JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

{
  "alg": "HS256",
  "typ": "JWT"
}

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。


有效载荷


有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。


iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT


除以上默认字段外,我们还可以自定义私有字段,如下例:


{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}


请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。


JSON对象也使用Base64 URL算法转换为字符串保存。


签名哈希


签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。


首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。


HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。


Base64URL算法


如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。


作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。


3、JWT的原则


JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。

{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}

之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。


服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。


4、JWT的用法


客户端接收服务器返回的JWT,将其存储在CookielocalStorage中。


此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。


三、整合JWT令牌


1、在common_utils模块中添加jwt工具依赖

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

2、创建JWT工具类

public class JwtUtils {
    //常量
    public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥
    //生成token字符串的方法
    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)  //设置token主体部分 ,存储用户信息
                .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");
    }
}

四、阿里云短信服务


帮助文档:https://help.aliyun.com/product/44282.html?spm=5176.10629532.0.0.38311cbeYzBm73


1、开通阿里云短信服务


4fbfed613d8a3e550eaa1edb4118818d.png


2、添加签名管理与模板管理


(1)添加签名管理


选择 国内消息 - 签名管理 - 添加签名


aebdec317a9b9fdce624cc356bf81df8.png


点击添加签名,进入添加页面,填入相关信息(签名要有实际意义)


image.png

点击提交,等待审核,审核通过后可以使用.


(2)添加模板管理


选择 国内消息 - 模板管理 - 添加模板


91c158fb065ca6e112e72b10d41d744a.png


另外由于阿里云对个人用户限制较多,可以使用腾讯云短信服务代替.申请一个微信公众号便可进行申请使用.


五、新建短信微服务


1、在service模块下创建子模块service_sms

2、创建controller和service代码

3、配置application.properties

server: #服务端口
    port: 8005
spring:
    application: #服务名称
        name: service-msm
    jackson: #Json格式
        date-format: yyyy-MM-dd HH:mm:ss
        time-zone: GMT+8
    redis: #redis配置
        database: 0
        host: 192.168.174.128
        lettuce:
            pool:
                max-active: 20
                max-idle: 5
                max-wait: -1
                min-idle: 0
        port: 6379
        timeout: 1800000
    #nacos服务地址
    cloud:
        nacos:
            discovery:
                server-addr: 192.168.174.128:8848
#国阳云短信服务
gyyun:
  sms:
    appCode: xxx
    smsSignId: xxx
    templateId: xxx

4、创建启动类


创建SmsApplication.java

@ComponentScan({"com.rg"})//扫描swagger配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
public class SmsApplication {
    public static void main(String[] args) {
        SpringApplication.run(SmsApplication.class, args);
    }
}


5、在service_sms的pom中引入依赖

<dependencies>
    <!--阿里云短信发送依赖-->
   <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
   </dependency>
   <dependency>
       <groupId>com.aliyun</groupId>
       <artifactId>aliyun-java-sdk-core</artifactId>
   </dependency>
    <!--javaMail-->
    <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>javax.mail-api</artifactId>
        <version>1.5.6</version>
    </dependency>
    <dependency>
        <groupId>com.sun.mail</groupId>
        <artifactId>javax.mail</artifactId>
        <version>1.5.3</version>
    </dependency>
    <dependency>
        <groupId>net.sf.jasperreports</groupId>
        <artifactId>jasperreports</artifactId>
        <version>6.8.0</version>
    </dependency>
</dependencies>

6、编写工具类


RandomUtil:随机数工具类

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");
    public static String getFourBitRandom() {
        return fourdf.format(random.nextInt(10000));
    }
    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;
    }
}

MailUtils:发邮件工具类

/**
 * 发邮件工具类
 */
public final class MailUtils {
    private static final String USER = "2422737092@qq.com"; // 发件人称号,同邮箱地址
    private static final String PASSWORD = "cfznjonpuhlndhfj"; // 如果是qq邮箱可以使户端授权码,或者登录密码
    /**
     *
     * @param to 收件人邮箱
     * @param text 邮件正文
     * @param title 标题
     */
    /* 发送验证信息的邮件 */
    public static boolean sendMail(String to, String text, String title){
        try {
            final Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", "smtp.qq.com");
            // 发件人的账号
            props.put("mail.user", USER);
            //发件人的密码
            props.put("mail.password", PASSWORD);
            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);
            // 设置收件人
            InternetAddress toAddress = new InternetAddress(to);
            message.setRecipient(Message.RecipientType.TO, toAddress);
            // 设置邮件标题
            message.setSubject(title);
            // 设置邮件的内容体
            message.setContent(text, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }
    public static void main(String[] args) throws Exception { // 做测试用
        MailUtils.sendMail("2422737092@qq.com","你好啊,李哥,我正在用代码给你发送邮件哈哈哈。","测试邮件");
        System.out.println("发送成功");
    }
}


相关文章
|
1月前
|
存储 弹性计算 Linux
阿里云账号注册、完成实名认证、试用云服务器和购买云服务器流程参考
本文为大家介绍新手用户从注册阿里云账号,完成实名认证,然后试用云服务器和购买云服务器的主要流程,适合初次购买和试用阿里云服务器的新手用户参考。
阿里云账号注册、完成实名认证、试用云服务器和购买云服务器流程参考
|
2月前
|
安全 Java 应用服务中间件
Shiro + JWT 进行登录验证
Shiro + JWT 进行登录验证
34 2
|
28天前
|
小程序 数据安全/隐私保护
阿里云新手入门:注册账号、实名认证、申请免费云服务器
阿里云新手指南:注册账号(手机号或支付宝快捷注册),完成实名认证(个人/企业)。通过免费服务器获取3个月试用。创建后,设置密码,远程连接,配置安全组规则,部署应用,如建站与环境安装。详询官方教程。
|
28天前
|
小程序 数据安全/隐私保护
阿里云账号注册、完成实名认证,最后申请免费云服务器全流程
**阿里云新手指南:** 1. 注册阿里云账号,支持手机号或第三方快速注册。 2. 完成实名认证,个人选个人认证,企业选企业认证,支付宝认证快速。 3. 访问[免费服务器页面](https://free.aliyun.com/?source=5176.11533457&userCode=r3yteowb)申请3个月免费云服务器。 4. 选服务器,点击“立即试用”,按提示操作。 5. 服务器创建后,设密码,远程连接,配置安全组,部署应用,参考阿里云官方教程。
|
23天前
|
存储 JavaScript 前端开发
文本,三步走构思,富文本点击提交能够存储到数据库当中(下),最快的方法,还是会看资料,因此会整合资料最好,直接看资料最快,因为是JWT的资料,我们要设置好登录的内容,看登录的地方怎样写的
文本,三步走构思,富文本点击提交能够存储到数据库当中(下),最快的方法,还是会看资料,因此会整合资料最好,直接看资料最快,因为是JWT的资料,我们要设置好登录的内容,看登录的地方怎样写的
|
24天前
|
安全 NoSQL Java
JWT和Security 登录权限判断和token访问和让token失效
JWT和Security 登录权限判断和token访问和让token失效
|
1月前
|
JSON 算法 Go
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
|
2月前
|
开发工具
阿里云本机一键登录授权页面修改
阿里云本机一键登录授权页面修改
165 0
|
2月前
|
开发工具 iOS开发
阿里云本机一键登录集成
阿里云本机一键登录集成
63 0
|
5天前
|
弹性计算 应用服务中间件 Linux
阿里云服务器开放端口完整图文教程
笔者近期开发完成的服务端程序部署在阿里云的ECS云服务器上面,一些应用程序配置文件需要设置监听的端口(如Tomcat的8080、443端口等),虽然通过CentOs 7系统的的「防火墙」开放了对应的端口号,任然无法访问端口号对应的应用程序,后面了解到原来还需要设置云服务器的「安全组规则」,开放相应的端口权限,服务端的接口才能真正开放。
80 1
阿里云服务器开放端口完整图文教程

热门文章

最新文章