4.9整合JWT
- 生成token:在用户登录成功,根据用户的登录信息,生成登录标识token,并返回给浏览器。
- 使用token:完善ajax请求,在请求之前添加请求头,设置token
- 校验token:在网关中编写过滤器,进行请求进行拦截,并校验token。
- 白名单:在白名单中的请求,是不需要token可以直接访问的。
4.9.1生成Token
- 用户登录成功,生成token,并将token响应给浏览器。(认证服务 AuthService)
- 步骤一:查看 application.yml文件,确定 jwt配置信息
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/5ac1bce528e2502fd57151e647e84bec.png)
- 步骤二:创建JwtProperties文件,用于加载sc.jwt配置信息
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a6c7bd305bc956664fbb32a730ef08db.png)
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.RsaUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
@Data
@ConfigurationProperties(prefix = "sc.jwt")
@Component
public class JwtProperties {
private String secret;
private String pubKeyPath;
private String priKeyPath;
private int expire;
private PublicKey publicKey;
private PrivateKey privateKey;
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
@PostConstruct
public void init(){
try {
File pubFile = new File(this.pubKeyPath);
File priFile = new File(this.priKeyPath);
if( !pubFile.exists() || !priFile.exists()){
RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
}
this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
- 步骤三:修改AuthController,注入JwtProperties,并使用JwtUtils生成token
package com.czxy.changgou4.controller;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Resource
private AuthService authService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private JwtProperties jwtProperties;
@PostMapping("/login")
public BaseResult login(@RequestBody AuthUser user){
String redisCode = stringRedisTemplate.opsForValue().get( "login" + user.getUsername() );
stringRedisTemplate.delete( "login" + user.getUsername() );
if(redisCode == null) {
return BaseResult.error("验证码无效");
}
if(! redisCode.equalsIgnoreCase(user.getCode())) {
return BaseResult.error("验证码错误");
}
AuthUser loginUser = authService.login(user);
if(loginUser != null ) {
String token = JwtUtils.generateToken(loginUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey());
return BaseResult.ok("登录成功").append("loginUser",loginUser).append("token", token);
} else {
return BaseResult.error("用户名或密码不匹配");
}
}
}
4.9.2使用token
- 步骤一:登录成功后保存token,修改 Login.vue页面
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/543494a82baff54adbb1baa02d7756b0.png)
async loginFn() {
let { data } = await this.$request.login( this.user )
if( data.code == 20000) {
sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) )
sessionStorage.setItem('token' , data.other.token )
this.$router.push('/')
} else {
this.errorMsg = data.message
}
}
- 步骤二:请求是自动携带token,修改apiclient.js,将token添加到请求头
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/09c87fa84d5cd76130992d255a8832bd.png)
let token = sessionStorage.getItem('token')
if( token ) {
$axios.setToken( token )
}
- 步骤三:检查 nuxt.conf.js,插件模式改成“client”
- 否则抛异常“sessionStorage is not defined”
plugins: [
{ src: '~plugins/apiclient.js', mode: 'client' }
],
4.9.3校验token
- token的校验在网关项目处完成
- 步骤一:修改application.yml添加jwt配置
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/df64db6e7047b7392af9ea6a454b4ca2.png)
#自定义内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
- 步骤二:创建 JwtProperties,用于加载配置文件
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3a9a9d3234030ab2074b387dd2593660.png)
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.RsaUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
@Data
@ConfigurationProperties(prefix = "sc.jwt")
public class JwtProperties {
private String secret;
private String pubKeyPath;
private String priKeyPath;
private int expire;
private PublicKey publicKey;
private PrivateKey privateKey;
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
@PostConstruct
public void init(){
try {
File pubFile = new File(this.pubKeyPath);
File priFile = new File(this.priKeyPath);
if( !pubFile.exists() || !priFile.exists()){
RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
}
this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
- 步骤三:编写过滤器,对所有路径进行拦截
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/242648ec85ec0baf08c0085acf0a6820.png)
package com.czxy.changgou4.filter;
import com.czxy.changgou4.config.FilterProperties;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.utils.RsaUtils;
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;
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Resource
private JwtProperties jwtProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
String token = request.getHeaders().getFirst("Authorization");
try {
JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class);
return chain.filter(exchange);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
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;
}
}
- 步骤四:修改前端 apiclient.js 文件,用于处理401异常
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ad8f8206c4e6e89d25904bee15be3b92.png)
$axios.onError(error => {
if(error.response.status === 401) {
console.error(error.response.data)
redirect('/login')
}
})
var axios = null
export default ({ $axios, redirect, process }, inject) => {
let token = sessionStorage.getItem('token')
if( token ) {
$axios.setToken( token )
}
$axios.onError(error => {
if(error.response.status === 401) {
console.error(error.response.data)
redirect('/login')
}
})
axios = $axios
inject('request', request)
}
4.9.4白名单
- 不需要拦截的资源都配置到yml文件中,在过滤器直接放行
- 步骤一:修改application.yml文件
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0c005ecf7cf24eac8d3fef2762c25ff0.png)
#自定义内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
filter:
allowPaths:
- /checkusername
- /checkmobile
- /sms
- /register
- /login
- /verifycode
- /categorys
- /news
- /brands
- /specifications
- /search
- /goods
- /comments
- swagger
- /api-docs
- 步骤二:创建FilterProperties配置文件,用于存放允许放行的路径
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cbbee4081420884c7292c723835e2874.png)
package com.czxy.changgou4.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Data
@ConfigurationProperties(prefix="sc.filter")
public class FilterProperties {
private List<String> allowPaths;
}
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cb4be8ae307b6fea229c4165a41f590b.png)
- 步骤三:修改 LoginFilter,放行名单中配置的路径
package com.czxy.changgou4.filter;
import com.czxy.changgou4.config.FilterProperties;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.utils.RsaUtils;
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;
@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) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
for (String allowPath : filterProperties.getAllowPaths()) {
if(path.contains(allowPath)){
return chain.filter(exchange);
}
}
String token = request.getHeaders().getFirst("Authorization");
try {
JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class);
return chain.filter(exchange);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
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;
}
}