在前两篇我们介绍了 JWT 是什么,以及项目中使用 JWT 时需要用到的工具类,本篇我们就来看看在项目中如何使用 JWT 进行鉴权。
依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
1.yml 配置 cookie 信息、domain 域
jwt:
secret: xusm@Login(Auth}*^31)&heiMa% # 密文,生成公钥、私钥的依据
pubKeyPath: C:\temp\rsa\xusm\rsa.pub # 公钥存放路径
priKeyPath: C:\temp\rsa\xusm\rsa.pri # 私钥存放路径
expire: 45 # JWT 过期时间,单位分钟
cookieName: XUSM_TOKEN # cookie名
cookieMaxAge: 45 # cookie过期时间,单位分钟
domain: bbs.xysmxh.com # cookie 的 domain 域,在 CookieUtils#setCookie() 会用到
- 关于过期时间
- jwt 可能只是 cookie 中的一部分数据而已,所以,我们需要分别设置 JWT 过期时间和 cookie 过期时间。但是,一般情况下,jwt 的 cookie 中只保存 jwt 这一个信息,所以,cookie 的过期时间和 jwt 的过期时间一般设置相同。
- 当到达过期时间后,cookie 就会过期,那么 jwt 也就会过期,那么在用户端发送请求后,服务端的 token 校验就无法通过了(需刷新过期时间)。
- jwt 和 cookie API 中默认的过期时间都是秒,但是这里我们设置的分钟(手动 * 60)
- 关于路径
- 当服务部署到 Linux 后,C:\temp\rsa.pub 和 C:\temp\rsa.pri 会被理解成相对路径,即在 jar 的目录下直接生成名为 C:\temp\rsa.pub 和 C:\temp\rsa.pri 的两个文件
2.读取 yml 中的 cookie 信息
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
private String secret; // 密文,生成公钥和私钥的依据
private String pubKeyPath; // 公钥存放路径
private String priKeyPath; // 私钥存放路径
private int expire; // token 过期时间
private String cookieName;
private Integer cookieMaxAge;
private PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
/**
* @PostContruct:在构造方法执行之后执行该方法
*/
@PostConstruct
public void init(){
try {
File pubKey = new File(pubKeyPath);
File priKey = new File(priKeyPath);
if (!pubKey.exists() || !priKey.exists()) {
// 生成公钥和私钥
RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
}
// 获取公钥和私钥
this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
} catch (Exception e) {
logger.error("初始化公钥和私钥失败!", e);
throw new RuntimeException();
}
}
// getter、setter ...
}
3.Controller-用户登录后发送 token
@Resource
private JwtProperties jwtProperties; // 获取 cookie 基本信息
// 短信登录
@GetMapping("/login")
public ResponseEntity<Void> smsLogin(
@RequestParam("phone") String phone,
@RequestParam("code") String code,
HttpServletResponse response,
HttpServletRequest request){
// 校验登录信息
if(this.userService.checkUserPhone(phone)){
return ResponseEntity.notFound().build();
}else if(!this.codeService.checkCacheCode(phone,code)){
return ResponseEntity.badRequest().build();
}
// 生成 token
String token = userService.generateToken(phone);
// 将 token 放到 cookie 中发送给 客户端
// 注:cookie 过期时间手动 *60、手动设置 domain
CookieUtils.setCookie(request,response,jwtProperties.getCookieName(),token,
jwtProperties.getCookieMaxAge()*60);
return ResponseEntity.ok().build();
}
4.Service-生成 token
@Resource
private JwtProperties jwtProperties; // 获取 jwt 基本信息
@Override
public String generateToken(String phone) {
// 构建 UserInfo
UserInfo userInfo = new UserInfo();
userInfo.setId(userInfoDao.selectOne(new UserInfo(phone)).getId());
try {
// 通过 JwtUtils 生成 token
// 1.传入私钥,生成签名
// 2.传入 userInfo 对象,将当前 user 的 id 放入 payload
String token = JwtUtils.generateToken(userInfo,jwtProperties.getPrivateKey(),jwtProperties.getExpire()*60);
return token;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
5.跨域配置中添加允许携带 cookie
ajax 默认不携带 cookie
CorsConfiguration config = new CorsConfiguration();
// 注:在要携带 cookie 时不能 *
config.addAllowedOrigin("http://localhosst:8080")
// 允许携带 cookie
config.setAllowCredentials(true);
6.写拦截器根据 token 校验请求合法性
@Component
/**
* 校验当前用户是否已经登录
*/
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Resource
private JwtProperties jwtProperties; // 获取 cookie 信息
// ThreadLoacal
private static final ThreadLocal<Long> THREAD_LOCAL = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{
// 获取 cookie 中的 token
String cookieValue = CookieUtils.getCookieValue(request, this.jwtProperties.getCookieName());
// 如果请求中没有 token
if(StringUtils.isBlank(cookieValue)){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 校验 token 合法性,是否包含了 user 信息
UserInfo user = JwtUtils.getInfoFromToken(cookieValue,jwtProperties.getPublicKey());
if(user == null){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 将 token 中保存的当前 user 的 id 放入 ThreadLocal
THREAD_LOCAL.set(user.getId());
return true;
}
public static Long getUserId(){
return THREAD_LOCAL.get();
}
/**
* 在请求处理完后删除 ThreadLocal 中保存的 id
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
THREAD_LOCAL.remove();
}
}
7.配置拦截路径
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginInterceptor).addPathPatterns("/user/detail")
.addPathPatterns("/upload/**")
.addPathPatterns("/article/post")
.addPathPatterns("/source/detail/*");
}
}
8.刷新 JWT 过期时间
当 JWT 到达过期时间后,它就会失效,所以我们需要写一个接口去刷新它的过期时间
@GetMapping("/user/verify")
public ResponseEntity<Map<String,Object>> Verify(
HttpServletResponse response,
HttpServletRequest request,
@CookieValue("XUSM_TOKEN") String token // 拿到指定 cookie
){
UserInfo userInfo = new UserInfo();
try {
// 获取 cookie 中的信息
userInfo = JwtUtils.getInfoFromToken(token,this.jwtProperties.getPublicKey());
if(userInfo == null){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 重新生成 token
token = JwtUtils.generateToken(userInfo,jwtProperties.getPrivateKey(),jwtProperties.getExpire()*60);
// 重新发送 cookie
CookieUtils.setCookie(request,response,jwtProperties.getCookieName(),token,jwtProperties.getCookieMaxAge()*60);
} catch (Exception e) {
e.printStackTrace();
}
// 查询用户名,头像等信息
Map<String,Object> map = this.authService.queryUserInfo(userInfo.getId());
return ResponseEntity.ok(map);
}
然后,我们可以在前的每个页面都加上这个接口,当用户每次刷新页面或者打开信息页面,就能刷新 token 时间。所以,说是刷新,其实就是重新发送一个。
本文标题:【项目杂记】JWT(下):使用 JWT 鉴权整体逻辑实例
本文链接:https://blog.quwenai.cn/post/9846.html
版权声明:本文不使用任何协议授权,您可以任何形式自由转载或使用。






还没有评论,来说两句吧...