JWT+RAS

本文介绍JWT(JSON Web Token)与RSA非对称加密算法在微服务间数据共享和安全传输的应用。通过详细步骤讲解如何使用JWT生成和解析数据,以及如何利用RSA进行数据加密,确保微服务间通信的安全性。

JWT

		服务与服务之间共享数据,采用JWT先生成数据,在另一个服务中解析数据,为了保证数据安全性,使用RAS对数据进行加密。

1.1 RAS工具

RAS 非对称加密算法

  • 特点
    • 同时生产一对秘钥:公钥和私钥。
    • 公钥秘钥:用于加密
    • 私钥秘钥:用于解密
  • 使用工具
//生成公钥和私钥
RasUtils.generateKey(公钥位置,私钥位置,密码);
RasUtils.generateKey(pubKeyPath,priKeyPath,"234");
//获得公钥
RasUtils.getPublicKey(pubKeyPath);
//获得私钥
RasUtils.getPrivateKey(priKeyPath);

1.2 JWT工具

  • JWT 基于JSON的认证规范。(Json Web Token)

  • 使用JWT目的:生成数据、解析数据

  • 使用JWT

  • 步骤一:添加依赖(含之前的)

<properties>
        <jwt.jjwt.version>0.9.0</jwt.jjwt.version>
        <jwt.joda.version>2.9.7</jwt.joda.version>
        <lombok.version>1.16.20</lombok.version>
        <beanutils.version>1.9.3</beanutils.version>
    </properties>


    <dependencies>
        <!--网关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!--添加eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${beanutils.version}</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.jjwt.version}</version>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>${jwt.joda.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>


    </dependencies>
  • 步骤二:导入工具
  • JWT
package com.czxy.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;


public class JwtUtils {
    /**
     *  私钥加密token
     * @param data 需要加密的数据(载荷内容)
     * @param expireMinutes 过期时间,单位:分钟
     * @param privateKey 私钥
     * @return
     */
    public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {
        //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();
    }

    /**
     * 通过公钥解析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;
    }
}
  • RAS
package com.czxy.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;

public class RasUtils {

    /**
     * 从文件中读取公钥
     *
     * @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);
    }


}


  • 步骤三:使用
//生成数据, UserInfo --> String(加密)
//JwtUtils.generateToken(数据,过期时间(分钟), 私钥)
String token = JwtUtils.generateToken(userInfo,30, RasUtils.getPrivateKey(priKeyPath));

//解析数据, String(加密) --> UserInfo
// JwtUtils.getObjectFromToken(加密数据, 公钥, 封装对象.class);
UserInfo userInfo = JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), UserInfo.class);
### 结合 JWT、Shiro 和 CAS 实现单点登录及权限管理 为了实现基于 JWT、Shiro 和 CAS 的身份认证与授权,可以通过以下方式完成: #### 1. **技术栈概述** - Apache Shiro 提供了强大的安全功能,包括身份验证和授权[^1]。 - JSON Web Token (JWT) 被用来生成并传递用户的认证信息,在无状态环境下非常适用[^2]。 - Central Authentication Service (CAS) 是一种单点登录协议,允许用户一次登录多个应用系统[^3]。 通过结合这三种技术,可以构建一个高效的身份认证与授权体系。 --- #### 2. **架构设计** ##### a. 用户登录流程 当用户访问受保护资源时: 1. 如果未登录,则重定向到 CAS 登录页面。 2. 用户输入凭证并通过 CAS 认证成功后,返回票据(Ticket Granting Ticket, TGT)。 3. 应用程序接收到该票据,并向 CAS Server 请求验证。 4. 验证通过后,应用程序生成 JWT 并将其作为 Bearer Token 返回给前端。 ##### b. 权限校验流程 每次请求到达服务端时: 1. JWT Filter 截获请求中的 Token。 2. 解析 Token 获取用户名及其他声明信息。 3. 使用 LoginRealm 对解析后的数据进行权限校验。 --- #### 3. **核心组件实现** 以下是各模块的具体实现细节: ##### a. `JwtUtil.java` 工具类 此工具类负责生成、刷新以及验证 Token。 ```java public class JwtUtil { private static final String SECRET_KEY = "your-secret-key"; public static String generateToken(String username) { Date now = new Date(); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(new Date(now.getTime() + 86400000)) // 设置过期时间为一天 .signWith(SignatureAlgorithm.HS512, SECRET_KEY.getBytes()) .compact(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY.getBytes()).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } } ``` ##### b. `JWTFilter.java` 过滤器 拦截所有非登录请求,提取 Token 并交给 Shiro 处理。 ```java @Component public class JWTFilter extends BasicHttpAuthenticationFilter { @Override protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; String headerAuth = req.getHeader("Authorization"); return StringUtils.isNotEmpty(headerAuth) && headerAuth.startsWith("Bearer "); } @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Authorization").replace("Bearer ", ""); JwtToken jwtToken = new JwtToken(token); getSubject(request, response).login(jwtToken); return true; } } ``` ##### c. `LoginRealm.java` 自定义 Realm 在此类中实现用户认证逻辑。 ```java public class LoginRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 假设从数据库查询角色和权限 List<String> roles = Arrays.asList("admin", "user"); // 示例数据 Set<String> permissions = Sets.newHashSet("read", "write"); info.setRoles(new HashSet<>(roles)); info.setStringPermissions(permissions); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { JwtToken jwtToken = (JwtToken) token; if (!JwtUtil.validateToken((String) jwtToken.getCredentials())) { throw new IncorrectCredentialsException("Invalid token!"); } String username = Jwts.parser().setSigningKey("your-secret-key".getBytes()).parseClaimsJws((String) jwtToken.getCredentials()).getBody().getSubject(); return new SimpleAuthenticationInfo(username, jwtToken.getCredentials(), getName()); } } ``` ##### d. `ShiroConfig.java` 配置类 配置 ShiroManager 及其拦截规则。 ```java @Configuration public class ShiroConfig { @Bean public SecurityManager securityManager(LoginRealm loginRealm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(loginRealm); return manager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); definition.addPathDefinition("/auth/login", "anon"); // 放行登录接口 definition.addPathDefinition("/**", "jwt"); // 其他路径需经过 JWT 校验 return definition; } } ``` --- #### 4. **注意事项** - 确保 CAS Server 正常运行并与客户端正确交互。 - 在生产环境中,应加密存储 Secret Key[^4]。 - 定义清晰的角色与权限映射关系以便于维护。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值