一、登录要求
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);