当前位置: 首页 > article >正文

后台管理系统的通用权限解决方案(九)SpringBoot整合jjwt实现登录认证鉴权

  • 1)创建maven工程jjwt-login-demo,并配置其pom.xml文件如下
<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>org.example</groupId>
    <artifactId>jjwt-login-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>
  • 2)创建resources/keys目录,将通过RSA算法生成的公钥和私钥复制到此目录下

  • 3)在resources目录下创建application.yml文件,配置jjwt相关配置
# jjwt相关配置
auth:
  token:
    expire: 3600 #令牌失效时间,单位秒
    priKey: keys/pri.key #私钥地址
    pubKey: keys/pub.key #公钥地址
  • 4)创建属性类AuthTokenProperties,用于接收application.yml文件中的配置
package com.itweid.jjwt.config;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = "auth.token")
public class AuthTokenProperties {
    /**
     * 过期时间。默认2小时=7200秒
     */
    private Integer expire = 7200;
    /**
     * 私钥
     */
    private String priKey;
    /**
     * 公钥
     */
    private String pubKey;
}
  • 5)定义实体类LoginUserTokenLoginUser对象保存当前登录用户的信息,这些信息被加密后则使用Token对象保存
package com.itweid.jjwt.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements Serializable {
    /**
     * id
     */
    private Long userId;
    /**
     * 账号
     */
    private String account;
    /**
     * 姓名
     */
    private String name;
    /**
     * 手机
     */
    private String telephone;
}
package com.itweid.jjwt.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Token implements Serializable {
    private static final long serialVersionUID = -8482946147572784305L;
    /**
     * token
     */
    private String token;
    /**
     * 有效时间:单位:秒
     */
    private Integer expire;
}
  • 6)定义一个异常类BaseException,用于接收token生成和解析时的异常
package com.itweid.jjwt.exception;

import lombok.Getter;

@Getter
public class BaseException extends RuntimeException {
    
    protected int code;
    protected String message;

    public BaseException(int code, String message) {
        this.code = code;
        this.message = message;
    }
}
  • 7)定义RSA帮助类RsaKeyHelper,封装获取公钥和密钥的方法
package com.itweid.jjwt.utils;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA帮助类
 */
public class RsaKeyHelper {

    /**
     * 获取公钥
     * @param filename 公钥文件地址
     * @return PublicKey
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public PublicKey getPublicKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(filename);
        try (DataInputStream dis = new DataInputStream(resourceAsStream)) {
            byte[] keyBytes = new byte[resourceAsStream.available()];
            dis.readFully(keyBytes);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePublic(spec);
        }
    }

    /**
     * 获取密钥
     * @param filename 密钥文件地址
     * @return PrivateKey
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public PrivateKey getPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(filename);
        try (DataInputStream dis = new DataInputStream(resourceAsStream)) {
            byte[] keyBytes = new byte[resourceAsStream.available()];
            dis.readFully(keyBytes);
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(spec);
        }
    }
}
  • 8)定义JWT帮助类JwtHelper,封装生成token、从token中获取登录用户信息的方法
package com.itweid.jjwt.utils;

import com.itweid.jjwt.exception.BaseException;
import com.itweid.jjwt.pojo.LoginUser;
import com.itweid.jjwt.pojo.Token;
import io.jsonwebtoken.*;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;

public class JwtHelper {
    private static final RsaKeyHelper RSA_KEY_HELPER = new RsaKeyHelper();
    private static final String JWT_KEY_ACCOUNT = "account";
    private static final String JWT_KEY_NAME = "name";
    private static final String JWT_KEY_TELEPHONE = "telephone";

    /**
     * 生成token
     * @param loginUser
     * @param priKeyPath
     * @param expire
     * @return
     * @throws BaseException
     */
    public static Token generateToken(LoginUser loginUser, String priKeyPath, int expire) throws BaseException {
        JwtBuilder jwtBuilder = Jwts.builder()
                //设置主题
                .setSubject(String.valueOf(loginUser.getUserId()))
                .claim(JWT_KEY_ACCOUNT, loginUser.getAccount())
                .claim(JWT_KEY_NAME, loginUser.getName())
                .claim(JWT_KEY_TELEPHONE, loginUser.getTelephone());
        return generateToken(jwtBuilder, priKeyPath, expire);
    }

    /**
     * 生成token
     * @param builder
     * @param priKeyPath
     * @param expire
     * @return Token
     * @throws BaseException
     */
    protected static Token generateToken(JwtBuilder builder, String priKeyPath, int expire) throws BaseException {
        try {
            // 获取过期时间
            ZonedDateTime zdt = LocalDateTime.now().plusSeconds(expire).atZone(ZoneId.systemDefault());
            // 返回的字符串便是我们的jwt串了
            String compactJws = builder.setExpiration(Date.from(zdt.toInstant()))
                    //设置算法(必须)
                    .signWith(SignatureAlgorithm.RS256, RSA_KEY_HELPER.getPrivateKey(priKeyPath))
                    .compact();
            return new Token(compactJws, expire);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new BaseException(1, "生成token失败");
        }
    }

    /**
     * 获取token中的用户信息
     * @param token
     * @param pubKeyPath
     * @return LoginUser
     * @throws BaseException
     */
    public static LoginUser getLoginUserFromToken(String token, String pubKeyPath) throws BaseException {
        Jws<Claims> claimsJws = parserToken(token, pubKeyPath);
        Claims body = claimsJws.getBody();
        String userId = body.getSubject();
        String account = (String) body.get(JWT_KEY_ACCOUNT);
        String name = (String) body.get(JWT_KEY_NAME);
        String telephone = (String) body.get(JWT_KEY_TELEPHONE);
        return new LoginUser(Long.parseLong(userId), account, name, telephone);
    }

    /**
     * 使用公钥解析token
     * @param token
     * @param pubKeyPath 公钥路径
     * @return Jws<Claims>
     * @throws BaseException
     */
    private static Jws<Claims> parserToken(String token, String pubKeyPath) throws BaseException {
        try {
            return Jwts.parser().setSigningKey(RSA_KEY_HELPER.getPublicKey(pubKeyPath)).parseClaimsJws(token);
        } catch (ExpiredJwtException ex) {
            //过期
            throw new BaseException(2, "token已过期");
        } catch (SignatureException ex) {
            //签名错误
            throw new BaseException(3, "签名错误");
        } catch (IllegalArgumentException ex) {
            //token为空
            throw new BaseException(4, "token为空");
        } catch (Exception e) {
            // 其他错误
            throw new BaseException(5, "未知错误," + e.getMessage());
        }
    }
}
  • 9)定义token工具类TokenUtils,读取application.yml配置文件中的配置生成token或解析token
package com.itweid.jjwt.utils;

import com.itweid.jjwt.config.AuthTokenProperties;
import com.itweid.jjwt.exception.BaseException;
import com.itweid.jjwt.pojo.LoginUser;
import com.itweid.jjwt.pojo.Token;
import lombok.AllArgsConstructor;

/**
 * token工具类
 */
@AllArgsConstructor
public class TokenUtils {

    private AuthTokenProperties authTokenProperties;

    /**
     * 生成token
     * @param loginUser
     * @param expire
     * @return Token
     * @throws BaseException
     */
    public Token generateLoginUserToken(LoginUser loginUser, Integer expire) throws BaseException {
        if (expire == null || expire <= 0) {
            expire = authTokenProperties.getExpire();
        }
        return JwtHelper.generateToken(loginUser, authTokenProperties.getPriKey(), expire);
    }

    /**
     * 解析token
     * @param token
     * @return
     * @throws BaseException
     */
    public LoginUser getLoginUserFromToken(String token) throws BaseException {
        return JwtHelper.getLoginUserFromToken(token, authTokenProperties.getPubKey());
    }
}
  • 10)创建配置类AuthTokenConfiguration,使用@Bean注解注入TokenUtils
package com.itweid.jjwt.config;

import com.itweid.jjwt.utils.TokenUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@EnableConfigurationProperties(value = {
        AuthTokenProperties.class,
})
public class AuthTokenConfiguration {
    @Bean
    public TokenUtils getTokenUtils(AuthTokenProperties authTokenProperties) {
        return new TokenUtils(authTokenProperties);
    }
}
  • 11)定义注解@EnableAuthToken,用于启动认证服务
package com.itweid.jjwt.config;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AuthTokenConfiguration.class)
@Documented
@Inherited
public @interface EnableAuthToken {
}
  • 12)创建UserController类,实现登录功能
package com.itweid.jjwt.controller;

import com.itweid.jjwt.pojo.LoginUser;
import com.itweid.jjwt.pojo.Token;
import com.itweid.jjwt.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private TokenUtils tokenUtils;

    //用户登录功能,如果登录成功则签发jwt令牌给客户端
    @GetMapping("/login")
    public Token login(){
        String userName = "admin";
        String password = "admin123";
        //查询数据库进行用户名密码校验...

        //如果校验通过,则为客户端生成jwt令牌
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(1L);
        loginUser.setAccount(userName);
        loginUser.setName(userName);
        loginUser.setTelephone("18922102545");
        Token token = tokenUtils.generateLoginUserToken(loginUser, null);

        //实际应该是在过滤器中进行jwt令牌的解析
        LoginUser userInfo = tokenUtils.getLoginUserFromToken(token.getToken());
        System.out.println(userInfo);
        
        return token;
    }
}
  • 13)创建启动类JjwtApp,添加@EnableAuthToken注解
package com.itweid.jjwt;

import com.itweid.jjwt.config.EnableAuthToken;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAuthToken
public class JjwtApp {
    public static void main(String[] args) {
        SpringApplication.run(JjwtApp.class, args);
    }
}
  • 14)启动项目,在浏览器访问http://127.0.0.1:8080/user/login

可见,jwt令牌已经生成,且可以成功解析。后续调用服务的其他请求,只需要将该令牌通过请求头传递到后端服务即可。

本节完,更多内容查阅:后台管理系统的通用权限解决方案

延伸阅读:后台管理系统的通用权限解决方案(八)认证机制介绍、JWT介绍与jjwt框架的使用


http://www.kler.cn/a/379567.html

相关文章:

  • OpenEuler 使用ffmpeg x11grab捕获屏幕流,rtsp推流,并用vlc播放
  • 平衡者:陈欣的宇宙使命
  • HTTP 405 Method Not Allowed:解析与解决
  • Linux(inode + 软硬链接 图片+大白话)
  • 数据结构之线段树
  • Pycharm贪吃蛇小游戏后续2
  • iOS中OC对象的本质
  • HTTP的初步了解
  • 代码随想录第十五天| 110.平衡二叉树 、 257. 二叉树的所有路径 、404.左叶子之和、222.完全二叉树的节点个数
  • 基于深度学习的数据安全与可追溯性增强
  • qt QPixmap详解
  • 深入了解 Kotlin 高阶函数
  • SpringBoot实现:高效在线试题库系统
  • koa + sequelize做距离计算(MySql篇)
  • 使用WordPress快速搭建个人网站
  • 汽车电子行业数字化转型的实践与探索——以盈趣汽车电子为例
  • Python酷库之旅-第三方库Pandas(193)
  • 【工具变量】中国制造2025试点城市数据集(2000-2023年)
  • Maven核心概念
  • Linux-计算机网络-epoll的LT,ET模式
  • 力扣150:逆波兰表达式求值
  • 使用Web Workers实现JavaScript的多线程编程
  • 【WebRTC】WebRTC的简单使用
  • 【嵌入式面试高频知识点】-MQTT协议
  • 【appium 安卓10 QQ发送消息】
  • 不用买PSP,画质甚至更好,这款免费神器让你玩遍经典游戏