【在线教育项目】整合JWT权限校验,登录成功生成token

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【在线教育项目】整合JWT权限校验,登录成功生成token

一、登录要求


1.登录


根据表emp中的empName,emp_password字段进行登录。


1.1 、帐号密码错误,提示登陆失败。


1.2 、帐号密码正确,登陆成功并跳转。


6d16fc1e1ecb423e8a26a5883b9adcfa.png


2.权限校验


2.1、登录成功生成token,并保存在前端。


2.2、清除token,会自动跳转到登陆页面。


a37c13522f2e4a1aae9c971331184a7e.png


二、搭建环境


1.拷贝坐标

<!--JavaBean工具类,用于JavaBean数据封装-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
        </dependency>
        <!--jwt工具-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
        <!--joda 时间工具类 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>

2、所需工具类

0a3a6f56e7d7470db22d58ea3d195362.png

JwtUtils

package com.czxy.zx.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.beanutils.BeanUtils;
import org.joda.time.DateTime;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
 * Created by liangtong.
 */
public class JwtUtils {
    /**
     *  私钥加密token
     * @param data 需要加密的数据(载荷内容)
     * @param expireMinutes 过期时间,单位:分钟
     * @param privateKey 私钥
     * @return
     */
    public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey)  {
        try {
            //1 获得jwt构建对象
            JwtBuilder jwtBuilder = Jwts.builder();
            //2 设置数据
            if( data == null ) {
                throw new RuntimeException("数据不能为空");
            }
            BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                // 获得属性名
                String name = propertyDescriptor.getName();
                // 获得属性值
                Object value = propertyDescriptor.getReadMethod().invoke(data);
                if(value != null) {
                    jwtBuilder.claim(name,value);
                }
            }
            //3 设置过期时间
            jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());
            //4 设置加密
            jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);
            //5 构建
            return jwtBuilder.compact();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 通过公钥解析token
     * @param token 需要解析的数据
     * @param publicKey 公钥
     * @param beanClass 封装的JavaBean
     * @return
     * @throws Exception
     */
    public static <T> T  getObjectFromToken(String token, PublicKey publicKey,Class<T> beanClass) throws Exception {
        //1 获得解析后内容
        Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();
        //2 将内容封装到对象JavaBean
        T bean = beanClass.newInstance();
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            // 获得属性名
            String name = propertyDescriptor.getName();
            // 通过属性名,获得对应解析的数据
            Object value = body.get(name);
            if(value != null) {
                // 将获得的数据封装到对应的JavaBean中
                BeanUtils.setProperty(bean,name,value);
            }
        }
        return bean;
    }
}

RsaUtils

package com.czxy.zx.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
 * Created by liangtong.
 */
public class RsaUtils {
    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }
    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }
    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }
    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }
    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws Exception
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }
    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }
    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        //创建父文件夹
        if(!dest.getParentFile().exists()){
            dest.getParentFile().mkdirs();
        }
        //创建需要的文件
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

3、网关配置、配置类

13823968f41d40679dca14dec4d87085.png

application.yml

JWT,网关配置yml

过滤器,网关配置yml

sc:
  jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟
  filter:
    allowPaths:
      - swagger
      - /api-docs
      - /user/login
      - /user/info
      - /user/register
      - /user/send
      - /user/active
      - /verifycode

cdc612858e6b48ef9c3437b466c5e436.png

4、过滤器

FilterProperties

package com.czxy.zx.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Data
@ConfigurationProperties(prefix = "sc.filter")
public class FilterProperties {
    private List<String> allowPaths;
}

LoginFilter

package com.czxy.zx.filter;
import com.czxy.zx.config.FilterProperties;
import com.czxy.zx.config.JwtProperties;
import com.czxy.zx.domain.EduUser;
import com.czxy.zx.utils.JwtUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Component
@EnableConfigurationProperties(FilterProperties.class)
public class LoginFilter implements GlobalFilter, Ordered {
    @Resource
    private FilterProperties filterProperties;
    @Resource
    private JwtProperties jwtProperties;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            //1 获得请求路径
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            System.out.println(path);
            //2 白名单
            List<String> allowPaths = filterProperties.getAllowPaths();
            for (String allowPath : allowPaths) {
                if(path.contains(allowPath)) {
                    // 放行
                    return chain.filter(exchange);
                }
            }
            //3 获得token
            String token = request.getHeaders().getFirst("X-Token");
            //4 校验token
            JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), EduUser.class);
            //5.1  成功,放行
            return chain.filter(exchange);
        } catch (Exception e) {
            e.printStackTrace();
            //5.2 失败,返回提示`token失效`
            ServerHttpResponse response = exchange.getResponse();
            // 响应状态 401 没有权限
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 响应数据的编码
            response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
            // 响应“没有权限”提示
            DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8));
            return exchange.getResponse().writeWith(Flux.just(wrap));
        }
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

JwtProperties

package com.czxy.zx.user.config;
import com.czxy.zx.utils.RsaUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
@Configuration
@ConfigurationProperties(prefix = "sc.jwt")
@Data
public class JwtProperties {
    private String secret;
    private String pubKeyPath;
    private String priKeyPath;
    private Integer expire;
    private PublicKey publicKey;
    private PrivateKey privateKey;
    @PostConstruct      //初始化方法注解
    public void init() {
        try {
            File pubFile = new File(pubKeyPath);
            File priFile = new File(priKeyPath);
            if(!pubFile.exists() || ! priFile.exists()) {
                RsaUtils.generateKey(pubKeyPath,priKeyPath,secret);
            }
            // 获得公钥和私钥对象
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
            this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6、登录代码,保存token

Mapper持久层

package com.czxy.zx.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.zx.domain.EduUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EduUserMapper extends BaseMapper<EduUser> {
}

service业务层

public interface EduUserService extends IService<EduUser> {
    EduUser login(EduUser eduUser);
}
@Service
@Transactional
public class EduUserServiceImpl extends ServiceImpl<EduUserMapper, EduUser> implements EduUserService {
    @Override
    public EduUser login(EduUser eduUser) {
        QueryWrapper<EduUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",eduUser.getUsername());
        queryWrapper.eq("password",eduUser.getPassword());
        List<EduUser> list = baseMapper.selectList(queryWrapper);
        if(list != null && list.size() == 1){
            return list.get(0);
        }
        return null;
    }
}

controller控制层

@PostMapping("/login")
    public BaseResult login(@RequestBody Emp emp){
        Emp e = empService.login(emp);
        if (e != null){
            //保存token
            String token = JwtUtils.generateToken(e, jwtProperties.getExpire(), jwtProperties.getPrivateKey());
            //append是将参数放回other中
            return BaseResult.ok("用户登录成功!").append("token",token);
        }
        return BaseResult.error("登录失败!");
    }
    @GetMapping("/info")
    public com.czxy.vo.BaseResult login(String token) {
        try {
            //通过token获得员工消息
            Emp emp = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), Emp.class);
            Map<String,Object> map = new HashMap<>();
            map.put("roles", Arrays.asList("admin"));
            map.put("avatar", "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
            map.put("name", emp.getEmpName());
            return com.czxy.vo.BaseResult.ok("登录成功",map);
        } catch (Exception e) {
            return com.czxy.vo.BaseResult.error("获得用户信息异常!");
        }
    }

三、前端代码

1、查询详情:获得token

登录成功后,默认跳转到 / 页面

44bb33f459604d67b8e54876e45f89bc.png

访问 / ,在路由中配置跳转的位置。

3d284ee72a8441b39672e1f2d017dd7f.png

在跳转 / 页面前,执行vuex中 user/getInfo

cfedd926ef634c4dbf5fee5c19ab0e61.png

通过vuex执行ajax请求,查询详情。

4a0bcd2a1dd34758bed61b714fb34aa9.png

22caef38fb9f4534b7f01b71265bf660.png

重要代码段:保存token,获取token

 //保存token
 String token = JwtUtils.generateToken(e, jwtProperties.getExpire(), jwtProperties.getPrivateKey());
//append是将参数放回other中
return BaseResult.ok("用户登录成功!").append("token",token);
//3 获得token
String token = request.getHeaders().getFirst("X-Token");
//4 校验token
JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), EduUser.class);
//通过token获得员工消息
 Emp emp = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), Emp.class);
相关文章
|
2月前
|
存储 中间件 API
ThinkPHP 集成 jwt 技术 token 验证
本文介绍了在ThinkPHP框架中集成JWT技术进行token验证的流程,包括安装JWT扩展、创建Token服务类、编写中间件进行Token校验、配置路由中间件以及测试Token验证的步骤和代码示例。
ThinkPHP 集成 jwt 技术 token 验证
|
21天前
|
JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(二)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
23 0
|
21天前
|
存储 JSON JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
67 0
|
2月前
|
JSON 安全 数据安全/隐私保护
从0到1搭建权限管理系统系列三 .net8 JWT创建Token并使用
【9月更文挑战第22天】在.NET 8中,从零开始搭建权限管理系统并使用JWT(JSON Web Tokens)创建Token是关键步骤。JWT是一种开放标准(RFC 7519),用于安全传输信息,由头部、载荷和签名三部分组成。首先需安装`Microsoft.AspNetCore.Authentication.JwtBearer`包,并在`Program.cs`中配置JWT服务。接着,创建一个静态方法`GenerateToken`生成包含用户名和角色的Token。最后,在控制器中使用`[Authorize]`属性验证和解析Token,从而实现身份验证和授权功能。
112 3
|
3月前
【Azure APIM】在APIM中实现JWT验证不通过时跳转到Azure登录页面
【Azure APIM】在APIM中实现JWT验证不通过时跳转到Azure登录页面
|
3月前
|
JSON API 数据安全/隐私保护
Django 后端架构开发:JWT 项目实践与Drf版本控制
Django 后端架构开发:JWT 项目实践与Drf版本控制
63 0
|
3月前
|
API
【Azure Developer】记录一段验证AAD JWT Token时需要设置代理获取openid-configuration内容
【Azure Developer】记录一段验证AAD JWT Token时需要设置代理获取openid-configuration内容
|
3月前
|
JSON Java API
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
|
3月前
|
JSON 算法 API
【Azure API 管理】APIM 配置Validate-JWT策略,验证RS256非对称(公钥/私钥)加密的Token
【Azure API 管理】APIM 配置Validate-JWT策略,验证RS256非对称(公钥/私钥)加密的Token
|
3月前
|
存储 开发框架 JSON
ASP.NET Core 标识(Identity)框架系列(二):使用标识(Identity)框架生成 JWT Token
ASP.NET Core 标识(Identity)框架系列(二):使用标识(Identity)框架生成 JWT Token