目录
一、登录要求
1.登录
根据表emp中的empName,emp_password字段进行登录。
1.1 、帐号密码错误,提示登陆失败。
1.2 、帐号密码正确,登陆成功并跳转。
2.权限校验
2.1、登录成功生成token,并保存在前端。
2.2、清除token,会自动跳转到登陆页面。
二、搭建环境
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、所需工具类
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、网关配置、配置类
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
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
登录成功后,默认跳转到 / 页面
访问 / ,在路由中配置跳转的位置。
在跳转 / 页面前,执行vuex中 user/getInfo
通过vuex执行ajax请求,查询详情。
重要代码段:保存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);