Spring Boot + security + jwt 测试安全策略

一、测试概述

  主要目的是测试security的用法。
  因测试搭建mysql和redis比较麻烦,所以我这里将自定义的jwt和用户信息缓存到程序的内存中。
本人测试的项目比较混乱,Spring Boot父类只标出有用的依赖。其子类用的版本为jdk11。


后续会继续深入oauth2,敬请期待。
代码地址:https://gitcode.net/qq_40539437/cloud.git
如果想使用自定义jwt工具类往redis里存储请查看源码cloud-jwt模块。

整体代码结构:
Spring Boot + security + jwt 测试安全策略_第1张图片

二、maven 相关依赖

1、父类maven相关依赖

 <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
        <junit.version>4.12junit.version>
        <log4j.version>1.2.17log4j.version>
        <lombok.version>1.16.18lombok.version>
        <mysql.version>5.1.47mysql.version>
        <druid.version>1.1.16druid.version>
        <spring.boot.version>2.2.5.RELEASEspring.boot.version>
        <spring.cloud.version>Hoxton.SR3spring.cloud.version>
        <spring.cloud.alibaba.version>2.2.1.RELEASEspring.cloud.alibaba.version> 
        <mybatis.spring.boot.version>1.3.0mybatis.spring.boot.version>
    properties>
 <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-dependenciesartifactId>
                <version>${spring.boot.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>${spring.cloud.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>${spring.cloud.alibaba.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>${lombok.version}version>
            dependency>
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>${junit.version}version>
            dependency>
        dependencies>
    dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <fork>truefork>
                    <addResources>trueaddResources>
                configuration>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
        plugins>
    build>

2、子类相关依赖

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>10source>
                    <target>10target>
                configuration>
            plugin>
        plugins>
    build>

    <dependencies>
        <dependency>
            <groupId>com.atguigu.cloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwt-apiartifactId>
            <version>0.11.2version>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwt-implartifactId>
            <version>0.11.2version>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwt-jacksonartifactId>
            <version>0.11.2version>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>joda-timegroupId>
            <artifactId>joda-timeartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

二、代码块

1、配置文件 application.yaml

server:
  port: 20000

2、启动类

package com.atguigu.cloud;

import com.atguigu.cloud.cache.MyLocaleCache;
import com.atguigu.cloud.utils.RandomStr;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import java.util.Random;

@SpringBootApplication
@EnableWebSecurity
public class CsSecurityTestApplication {


    public static void main(String[] args) throws Exception {
        SpringApplication.run(CsSecurityTestApplication.class, args);
        key();
    }

    public static void key() throws Exception {
        Random random = new Random();
        int i = random.nextInt(60);
        String randomString = RandomStr.getRandomString(i);
        System.err.println("公 私钥随机串: " + randomString);

        // 生成密钥对
        RsaLocaleUtils.generateKey(MyLocaleCache.publicKey_S, MyLocaleCache.privateKey_S, randomString, 2048);
    }

}

3、本地缓存

package com.atguigu.cloud.cache;

import com.atguigu.cloud.domain.UserInfo;

import java.util.HashMap;
import java.util.Map;

public class MyLocaleCache {

    public static String privateKey_S = "privateKey";
    public static String publicKey_S = "publicKey";
    private static Map<Long, UserInfo> cache = new HashMap<>();

    public static UserInfo get(Long key){
        return cache.get(key);
    }

    public static UserInfo set(Long key, UserInfo userInfo){
        return cache.put(key,userInfo);
    }
}

4.配置类

package com.atguigu.cloud.conf;

import com.atguigu.cloud.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 密码加密方式 相同密码每次加密方式不同,但是每次都能与之匹配
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /** 配置Spring Security 的拦截规则*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login1").anonymous()
                .antMatchers("/test/**").hasRole("ADMIN") //需要具有 "ADMIN" 角色的用户访问 "/test/**"
                .anyRequest().authenticated(); // 其他路径需要认证


        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /** 权限管理器*/
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * {noop} 指代的是当前密码以明文输入  此处测试是指使用security自带的验证页面,设置密码,当实现UserDetailsService之后就不需要进行配置了
     * @param auth
     * @throws Exception

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("manager").password("{noop}manager").roles("manager")
                .and()
                .withUser("admin").password("{noop}admin").roles("manager", "ADMIN");
    }
     */
}

5.登录及权限验证接口

登录接口 LoginController:
这里不能直接使用login直接当接口,因为会找security里的login接口

package com.atguigu.cloud.controller;

import com.atguigu.cloud.cache.MyLocaleCache;
import com.atguigu.cloud.domain.LoginUser;
import com.atguigu.cloud.domain.UserInfo;
import com.atguigu.cloud.utils.JwtUtils;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/login1")
public class LoginController {

    @Autowired
    private AuthenticationManager authenticationManager;


    @GetMapping()
    public Map<String, Object> login(String username, String password) throws Exception {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username , password);
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        if(authentication == null) {
            throw new RuntimeException("用户名或密码错误");
        }
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        UserInfo userInfo = loginUser.getUser();
        MyLocaleCache.set(userInfo.getId(),userInfo);
        Map<String , Long> params = new HashMap<>() ;
        params.put("userId" , userInfo.getId()) ;
        PrivateKey privateKey = RsaLocaleUtils.getPrivateKey(MyLocaleCache.privateKey_S);
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 5);

        // 构建返回数据
        Map<String , Object> result = new HashMap<>();
        result.put("token" , token) ;

        System.out.println(username + "=" + password);
        return result;
    }
}

测试接口TestController:

package com.atguigu.cloud.controller;


import com.atguigu.cloud.domain.UserInfo;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/test")
public class TestController {

    @GetMapping("/hello")
    @ResponseBody
    public Map<String,Object> hello(){
        UserInfo userInfo = (UserInfo)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Map<String, Object> map = new HashMap<>();
        map.put("测试:","hello");
        map.put("用户:",userInfo.toString());
        return map;
    }
}

6.实体类

从写UserDetails:

package com.atguigu.cloud.domain;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/***
 * 下方其他认证可根据业务情况重写
 */
public class LoginUser implements UserDetails {
 
    private UserInfo userInfo;
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List list = new ArrayList();
        list.add(new SimpleGrantedAuthority("ROLE_"+ userInfo.getRole()));
        return list;
    }
 
    @Override
    public String getPassword() {
        return userInfo.getPassword();
    }
 
    @Override
    public String getUsername() {
        return userInfo.getUsername();
    }
 
    @Override
    public boolean isAccountNonExpired() {          // 账号是否没有过期
        return true;
    }
 
    @Override
    public boolean isAccountNonLocked() {           // 账号是否没有被锁定
        return true;
    }
 
    @Override
    public boolean isCredentialsNonExpired() {      // 账号的凭证是否没有过期
        return true;
    }
 
    @Override
    public boolean isEnabled() {                    // 账号是否可用
        return true;
    }

    public LoginUser() {
    }

    public LoginUser(UserInfo user) {
        this.userInfo = user;
    }

    public UserInfo getUser() {
        return userInfo;
    }

    public void setUser(UserInfo user) {
        this.userInfo = user;
    }
}

载荷Payload:

package com.atguigu.cloud.domain;

import java.util.Date;

public class Payload<T> {
    private String id;
    private T userInfo;
    private Date expiration;

    public Payload() {
    }

    public Payload(String id, T userInfo, Date expiration) {
        this.id = id;
        this.userInfo = userInfo;
        this.expiration = expiration;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public T getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(T userInfo) {
        this.userInfo = userInfo;
    }

    public Date getExpiration() {
        return expiration;
    }

    public void setExpiration(Date expiration) {
        this.expiration = expiration;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Payload{");
        sb.append("id='").append(id).append('\'');
        sb.append(", userInfo=").append(userInfo);
        sb.append(", expiration=").append(expiration);
        sb.append('}');
        return sb.toString();
    }
}

用户信息UserInfo:

package com.atguigu.cloud.domain;

public class UserInfo {

    private Long id;

    private String username;

    private String password;

    /** 这里多个可以采用分割符分割后续可以尽心解析*/
    private String role;

    public UserInfo() {
    }

    public UserInfo(Long id, String username, String role) {
        this.id = id;
        this.username = username;
        this.role = role;
    }

    public UserInfo(Long id, String username, String password, String role) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.role = role;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("UserInfo{");
        sb.append("id=").append(id);
        sb.append(", username='").append(username).append('\'');
        sb.append(", password='").append(password).append('\'');
        sb.append(", role='").append(role).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

7.JWT过滤器

package com.atguigu.cloud.filter;

import com.atguigu.cloud.cache.MyLocaleCache;
import com.atguigu.cloud.domain.Payload;
import com.atguigu.cloud.domain.UserInfo;
import com.atguigu.cloud.utils.JwtUtils;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 1、从请求头中获取token,如果请求头中不存在token,直接放行即可!由Spring Security的过滤器进行校验!
        String token = request.getHeader("token");
        if(token == null || "".equals(token)) {
            filterChain.doFilter(request , response);
            return ;
        }

        // 2、对token进行解析,取出其中的userId
        Payload<UserInfo> info = null;
        try {
            info = JwtUtils.getInfoFromToken(token, RsaLocaleUtils.getPublicKey("publicKey"), UserInfo.class);

        }catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法") ;
        }

        // 3、使用userId从redis中查询对应的LoginUser对象
        UserInfo userInfo1 = MyLocaleCache.get(info.getUserInfo().getId());
        if(userInfo1 != null) {
            List list = new ArrayList<>();
            list.add(new SimpleGrantedAuthority("ROLE_"+ userInfo1.getRole()));
            // 4、然后将查询到的LoginUser对象的相关信息封装到UsernamePasswordAuthenticationToken对象中,然后将该对象存储到Security的上下文对象中
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userInfo1, null ,list) ;
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }

        // 5、放行
        filterChain.doFilter(request , response);
    }
}

8.业务类

package com.atguigu.cloud.service;

import com.atguigu.cloud.domain.LoginUser;
import com.atguigu.cloud.domain.UserInfo;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    static Map<String, UserInfo> dataBase = new HashMap<>();

    static {
        /** 测试管理员用户*/
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1L);
        userInfo.setUsername("admin");
        userInfo.setPassword(new BCryptPasswordEncoder().encode("admin")); //不加密方式 "{noop}admin"
        userInfo.setRole("ADMIN");

        /** 测试普通用户*/
        UserInfo userInfo2 = new UserInfo();
        userInfo2.setId(2L);
        userInfo2.setUsername("manager");
        userInfo2.setPassword(new BCryptPasswordEncoder().encode("manager")); //不加密方式 "{noop}admin"
        userInfo2.setRole("MANAGER");


        dataBase.put("admin",userInfo);
        dataBase.put("manager",userInfo2);
    }
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        // 根据用户名查询用户数据
        UserInfo userInfo = dataBase.get(username);
        System.out.println(userInfo);
        // 如果查询不到数据,说明用户名或者密码错误,直接抛出异常
        if(userInfo == null) {
            throw new RuntimeException("用户名或者密码错误") ;
        }
 
        // 将查询到的对象转换成Spring Security所需要的UserDetails对象  这里不需要比对密码
        return new LoginUser(userInfo);
    }
}

9.工具类

JsonUtils

package com.atguigu.cloud.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;

public class JsonUtils {

    public static final ObjectMapper mapper = new ObjectMapper();

    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj.getClass() == String.class) {
            return (String) obj;
        }
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            logger.error("json序列化出错:" + obj, e);
            return null;
        }
    }

    public static <T> T toBean(String json, Class<T> tClass) {
        try {
            return mapper.readValue(json, tClass);
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }

    public static <E> List<E> toList(String json, Class<E> eClass) {
        try {
            return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }

    public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
        try {
            return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }

    public static <T> T nativeRead(String json, TypeReference<T> type) {
        try {
            return mapper.readValue(json, type);
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }
}

JwtUtils

package com.atguigu.cloud.utils;

import com.atguigu.cloud.domain.Payload;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;


public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位分钟
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return Jws
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 获取token中的载荷信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setExpiration(body.getExpiration());
        return claims;
    }


    public static void main(String[] args) {

    }
}

随机加密工具类:

package com.atguigu.cloud.utils;

import java.util.Random;

public class RandomStr {
    //length用户要求产生字符串的长度
    public static String getRandomString(int length) {
        String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(62);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }
}

RsaLocaleUtils

package com.atguigu.cloud.utils;

import org.springframework.stereotype.Component;

import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class RsaLocaleUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;

    private static ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();

    public static void setCache(String key,String value){
        cache.put(key, value);
    }

    public static String getCache(String key){
        return cache.get(key);
    }


    /**
     *  @Author: CS
     *  @Description: 从本地缓存中获取  可以修改为redis
     */
    public static void generateKey(String publicKey, String privateKey, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        setCache(publicKey,Base64.getEncoder().encodeToString(publicKeyBytes));
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        setCache(privateKey,Base64.getEncoder().encodeToString(privateKeyBytes));
    }


    /**
     * 从Redis中 读取密钥
     *
     * @param privateKey 私钥保存的Key
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
        privateKey = getCache(privateKey).toString();
        byte[] bytes  = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 从文件中 获取公钥
     *
     * @param publicKey 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(String publicKey) throws Exception {
        publicKey = getCache(publicKey).toString();
        byte[] bytes = Base64.getDecoder().decode(publicKey);
        System.out.println(bytes.length);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }
}

RsaUtils

package com.atguigu.cloud.utils;

import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

//import io.jsonwebtoken.security.Keys;

public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;

    /**
     * 从文件中读取公钥
     *
     * @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
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {
        bytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        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.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }

    public static void testfor() {
        KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256);
        PrivateKey aPrivate = keyPair.getPrivate();
        PublicKey aPublic = keyPair.getPublic();
    }
}

10.测试类

package com.cloud.atguigu.jwt;

import com.atguigu.cloud.domain.Payload;
import com.atguigu.cloud.domain.UserInfo;
import com.atguigu.cloud.utils.JwtUtils;
import com.atguigu.cloud.utils.RandomStr;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.junit.Test;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Random;


public class JwtTest {

    private  String privateKey_S = "privateKey";
    private String publicKey_S = "publicKey";
    @Test
    public void testRSAByLocale() throws Exception {

        Random random = new Random();
        int i = random.nextInt(60);
        String randomString = RandomStr.getRandomString(i);
        randomString = "cs";
        System.err.println(randomString);

        // 生成密钥对
        RsaLocaleUtils.generateKey(publicKey_S, privateKey_S, randomString, 2048);
        // 获取私钥
        PrivateKey privateKey = RsaLocaleUtils.getPrivateKey(privateKey_S);
        System.out.println("privateKey = " + privateKey.toString());
        PublicKey publicKey = RsaLocaleUtils.getPublicKey(publicKey_S);
        System.out.println("publicKey = " + publicKey.toString());
    }


    @Test
    public void testJWTByLocale() throws Exception {

        Random random = new Random();
        int i = random.nextInt(60);
        String randomString = RandomStr.getRandomString(i);
        randomString = "cs";
        System.err.println(randomString);

        // 生成密钥对
        RsaLocaleUtils.generateKey(publicKey_S, privateKey_S, randomString, 2048);
        // 获取私钥
        PrivateKey privateKey = RsaLocaleUtils.getPrivateKey(privateKey_S);
        // 生成token
        String token = JwtUtils.generateTokenExpireInMinutes(new UserInfo(1L, "Jack", "guest"), privateKey, 5);


        System.out.println("token = " + token);


        System.err.println("privateKey:" + privateKey.toString());


        // 获取公钥
        PublicKey publicKey = RsaLocaleUtils.getPublicKey(publicKey_S);
        // 解析token
        Payload<UserInfo> info = JwtUtils.getInfoFromToken(token, publicKey, UserInfo.class);

        System.out.println("info.getExpiration() = " + info.getExpiration());
        System.out.println("info.getUserInfo() = " + info.getUserInfo());
        System.out.println("info.getId() = " + info.getId());
    }
}

你可能感兴趣的:(spring,boot,后端,java)