4.9整合JWT
- 生成token:在用户登录成功,根据用户的登录信息,生成登录标识token,并返回给浏览器。
- 使用token:完善ajax请求,在请求之前添加请求头,设置token
- 校验token:在网关中编写过滤器,进行请求进行拦截,并校验token。
- 白名单:在白名单中的请求,是不需要token可以直接访问的。
4.9.1生成Token
- 用户登录成功,生成token,并将token响应给浏览器。(认证服务 AuthService)
- 步骤一:查看 application.yml文件,确定 jwt配置信息
- 步骤二:创建JwtProperties文件,用于加载sc.jwt配置信息
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;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Data
@ConfigurationProperties(prefix = "sc.jwt")
@Component
public class JwtProperties {
private String secret; // 密钥
private String pubKeyPath;// 公钥
private String priKeyPath;// 私钥
private int expire;// token过期时间
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;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
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;
/**
* Created by liangtong.
*/
@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 ) {
//生成Token
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页面
async loginFn() {
let { data } = await this.$request.login( this.user )
if( data.code == 20000) {
//成功
sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) )
//保存token
sessionStorage.setItem('token' , data.other.token )
//跳转到首页
this.$router.push('/')
} else {
this.errorMsg = data.message
}
}
- 步骤二:请求是自动携带token,修改apiclient.js,将token添加到请求头
//参考 https://axios.nuxtjs.org/helpers
let token = sessionStorage.getItem('token')
if( token ) {
// Adds header: `Authorization: 123` to all requests
// this.$axios.setToken('123')
$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配置
#自定义内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
- 步骤二:创建 JwtProperties,用于加载配置文件
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;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Data
@ConfigurationProperties(prefix = "sc.jwt")
public class JwtProperties {
private String secret; // 密钥
private String pubKeyPath;// 公钥
private String priKeyPath;// 私钥
private int expire;// token过期时间
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());
}
}
}
- 步骤三:编写过滤器,对所有路径进行拦截
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;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Resource
private JwtProperties jwtProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1 获得请求路径
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
//2 白名单放行
//3 获得token
String token = request.getHeaders().getFirst("Authorization");
//4 校验token
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异常
//处理响应异常
$axios.onError(error => {
// token失效,服务器响应401
if(error.response.status === 401) {
console.error(error.response.data)
redirect('/login')
}
})
- api.js 完整代码
var axios = null
export default ({ $axios, redirect, process }, inject) => {
//参考 https://axios.nuxtjs.org/helpers
let token = sessionStorage.getItem('token')
if( token ) {
// Adds header: `Authorization: 123` to all requests
// this.$axios.setToken('123')
$axios.setToken( token )
}
//处理响应异常
$axios.onError(error => {
// token失效,服务器响应401
if(error.response.status === 401) {
console.error(error.response.data)
redirect('/login')
}
})
//赋值
axios = $axios
//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this.$request.xxx()
// 使用方式2:在nuxt的asyncData中,content.app.$request.xxx()
inject('request', request)
}
4.9.4白名单
- 不需要拦截的资源都配置到yml文件中,在过滤器直接放行
- 步骤一:修改application.yml文件
#自定义内容
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配置文件,用于存放允许放行的路径
package com.czxy.changgou4.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
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.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;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Component
//2.1 加载JWT配置类
@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) {
//1 获得请求路径
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
//2 白名单放行
for (String allowPath : filterProperties.getAllowPaths()) {
//判断包含
if(path.contains(allowPath)){
return chain.filter(exchange);
}
}
//3 获得token
String token = request.getHeaders().getFirst("Authorization");
//4 校验token
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;
}
}
- 完成上面步骤 JWT 就可以正常使用了.