springboot集成jwt令牌【前后端分离模式下的身份认证方式】
一,传统Cookie+Session与JWT对比
1, 在传统的用户登录认证中,因为http是无状态的,所以都是采用session
方式。用户登录成功,服务端会保证一个session
,当然会给客户端一个sessionId
,客户端会把sessionId
保存在cookie
中,每次请求都会携带这个sessionId
。
2,cookie+session
这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session
共享问题,随着用户量的增多,开销就会越大。而JWT
不是这样的,只需要服务端生成token,客户端保存这个token
,每次请求携带这个token
,服务端认证解析就可。
3, JWT
方式校验方式更加简单便捷化,无需通过redis
缓存,而是直接根据token
取出保存的用户信息,以及对token
可用性校验,单点登录,验证token
更为简单。
二,springboot集成jwt
1,jwt的整合依赖
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2,jwt的自定义配置
server:
port: 8080
spring:
application:
name: springboot-jwt
# 自定义配置
config:
jwt:
# 加密密钥
secret: abcdefg1234567
# token有效时长
expire: 3600
# header 名称
header: token
3,编写JwtConfig
JwtConfig
负责
- 生成
token
- 获取
token
中的注册信息 - 验证
token
是否过期失效 - 获取
token
失效时间 - 从
token
中获取用户名 - 获取
jwt
发布时间
package com.ftx.jwt.config;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName JwtConfig.java
* @Description TODO
* @createTime 2020年06月22日 15:43:00
*/
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* JWT的token,区分大小写
*/
@ConfigurationProperties(prefix = "config.jwt", ignoreUnknownFields = true)
@Component
public class JwtConfig {
private String secret;
private long expire;
private String header;
/**
* 生成token
* @param subject
* @return
*/
public String createToken (String subject){
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);//过期时间
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 获取token中注册信息
* @param token
* @return
*/
public Claims getTokenClaim (String token) {
try {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}catch (Exception e){
// e.printStackTrace();
return null;
}
}
/**
* 验证token是否过期失效
* @param expirationTime
* @return
*/
public boolean isTokenExpired (Date expirationTime) {
return expirationTime.before(new Date());
}
/**
* 获取token失效时间
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
return getTokenClaim(token).getExpiration();
}
/**
* 获取用户名从token中
*/
public String getUsernameFromToken(String token) {
return getTokenClaim(token).getSubject();
}
/**
* 获取jwt发布时间
*/
public Date getIssuedAtDateFromToken(String token) {
return getTokenClaim(token).getIssuedAt();
}
// --------------------- getter & setter ---------------------
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
4,配置拦截器
在拦截器中对token
进行验证。
package com.ftx.jwt.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName TokenInterceptor.java
* @Description TODO
* @createTime 2020年06月22日 15:47:00
*/
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Resource
private JwtConfig jwtConfig ;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws SignatureException {
/** 地址过滤 */
String uri = request.getRequestURI() ;
if (uri.contains("/login")){
return true ;
}
/** Token 验证 */
String token = request.getHeader(jwtConfig.getHeader());
if(StringUtils.isEmpty(token)){
token = request.getParameter(jwtConfig.getHeader());
}
if(StringUtils.isEmpty(token)){
throw new SignatureException(jwtConfig.getHeader()+ "不能为空");
}
Claims claims = null;
try{
claims = jwtConfig.getTokenClaim(token);
if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
}
}catch (Exception e){
throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
}
//该token可用,放行
return true;
}
}
注册拦截器到SpringMvc
package com.ftx.jwt.config;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName WebConfig.java
* @Description TODO
* @createTime 2020年06月22日 15:53:00
*/
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private TokenInterceptor tokenInterceptor ;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
}
}
5,编写测试controller接口
package com.ftx.jwt.controller;
import com.alibaba.fastjson.JSONObject;
import com.ftx.jwt.config.JwtConfig;
import com.ftx.jwt.util.ResultTool;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName TokenController.java
* @Description TODO
* @createTime 2020年06月22日 16:05:00
*/
@RestController
public class TokenController {
@Resource
private JwtConfig jwtConfig ;
/**
* 用户登录接口
* @param userName
* @param passWord
* @return
*/
@PostMapping("/login")
public JSONObject login (@RequestParam("userName") String userName,
@RequestParam("passWord") String passWord){
JSONObject json = new JSONObject();
/** 验证userName,passWord和数据库中是否一致,如不一致,直接return ResultTool.errer(); 【这里省略该步骤】*/
// 这里模拟通过用户名和密码,从数据库查询userId
// 这里把userId转为String类型,实际开发中如果subject需要存userId,则可以JwtConfig的createToken方法的参数设置为Long类型
String userId = 5 + "";
String token = jwtConfig.createToken(userId) ;
if (!StringUtils.isEmpty(token)) {
json.put("token",token) ;
}
return ResultTool.success(json) ;
}
/**
* 需要 Token 验证的接口
*/
@PostMapping("/info")
public JSONObject info (){
return ResultTool.success("info") ;
}
/**
* 根据请求头的token获取userId
* @param request
* @return
*/
@GetMapping("/getUserInfo")
public JSONObject getUserInfo(HttpServletRequest request){
String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
return ResultTool.success(usernameFromToken) ;
}
}
用PostMan
测试工具测试一下,访问登录接口,当对账号密码验证通过时,则返回一个token
给客户端
说明:token
是在请求头处,request.getHeader()
得到token
。
当直接去访问info
接口时,会返回token
为空的异常
当在请求头加上正确token
时,则拦截器验证通过,可以正常访问到接口
当在请求头加入一个错误token
,则会返回token
失效的异常
接下来测试一下获取用户信息,因为这里存的subject
为userId
,所以直接返回上面写死的假数据5
以上内容参考自https://blog.csdn.net/akiranicky/article/details/99307713
三,知识点概述
1,@Resource注解
1, @Autowired
与@Resource
都可以用来装配bean. 都可以写在字段上,或写在setter
方法上。
2, @Autowired
默认按类型装配(这个注解是属于spring
的),默认情况下必须要求依赖对象必须存在,如果要允许null
值,可以设置它的required
属性为false
,如:@Autowired(required=false)
3,@Resource
(这个注解属于J2EE
的),默认按照名称进行装配,名称可以通过name
属性进行指定,如果没有指定name
属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter
方法上默认取属性名进行装配。当找不到与名称匹配的bean
时才按照类型进行装配。但是需要注意的是,如果name
属性一旦指定,就只会按照名称进行装配。
2,@ConfigurationProperties注解
@ConfigurationProperties
:告诉SpringBoot
将本类中的所有属性和配置文件中相关的配置进行绑定;prefix = "xxx"
:配置文件中哪个下面的所有属性进行一一映射
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!