一、测试概述
主要目的是测试security的用法。
因测试搭建mysql和redis比较麻烦,所以我这里将自定义的jwt和用户信息缓存到程序的内存中。
本人测试的项目比较混乱,Spring Boot父类只标出有用的依赖。其子类用的版本为jdk11。
后续会继续深入oauth2,敬请期待。
代码地址:https://gitcode.net/qq_40539437/cloud.git
如果想使用自定义jwt工具类往redis里存储请查看源码cloud-jwt模块。
整体代码结构:
二、maven 相关依赖
1、父类maven相关依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<spring.boot.version>2.2.5.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR3</spring.cloud.version>
<spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version> <!-- 版本丛2.1.0.RELEASE升到2.2.1.RELEASE 解决nacos 域名访问问题 -->
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springboot 2.2.5-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2、子类相关依赖
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>10</source>
<target>10</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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<Claims>
*/
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());
}
}