SpringBoot使⽤JWT
SpringBoot 使⽤ JWT
登录⽅式对⽐
传统登录⽅式 Session + Cookie
1. 客户端向服务器发送⽤户名和密码
2. 服务器验证通过,并把相关数据保存在 Session 中,例如登录时间之类的
3. 服务器返回给⽤户⼀个 SessionId ,客户端把这个 SessionId 写⼊ Cookie
4. ⽤户每次请求都会通过 Cookie 提交 SessionId 到服务器
5. 服务器收到 SessionId 后查数据,就可以知道⽤户⾝份
特点:
数据存储在服务器,安全性较强,但是占⽤服务器资源
因为使⽤到了 Cookie ,所以会被伪造
如果服务器较多,或者跨域访问之类的操作,就要求共享 Session 资源,否则就需要⽤户和服务器重复登录验证操作,或者记录⽤户登录的服务器,对服务器和⽤户体验都不好。
JWT ⽅式登录
1. 客户端向服务器发送⽤户名和密码
2. 服务器验证通过,对⽤户数据进⾏加密,⽣成 Token 返回给客户端
3. 浏览器(客户端)接收到 Token 后,将 Token 存储在 Local Storage,需要使⽤ JavaScript 代码获取,⽽ Cookie 是⾃动携带
4. ⽤户每次请求都把 Token 提交到服务器
5. 服务器对传来的 Token 进⾏解密,再去查询⽤户数据,⼀次知道⽤户⾝份
特点:
存储在客户端,不占⽤服务器资源,但是同样会被伪造
前后端分离,带上 Token 进⾏请求,不需要考虑⽤户是在哪个服务器上登录的,多服务器和跨域请求都没有问题
建议:对数据库的增删改,必须加上 Token 验证,查询不加 Token ,这样效率会⽐较⾼,同时查询操作也⽆法获取 Token ,更安全
如何强制token失效?
在数据库⾥保存⼀份 Token ,验证时再拿出来校验,重新登录就刷新覆盖这个值
JWT
JSON Web Token(JWT)是⽬前最流⾏的跨域⾝份验证解决⽅案。
先看概念
JWT 结构
JWT 分为三个部分
Header,算法和令牌类型
{
"alg": "HS256",
"typ": "JWT"
}
Payload:数据,实际需要传递的 JSON 对象
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Verify Signature:签名,⽤于防伪验证
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
加密之后的结果:
终于可以上代码了
代码
新建⼀个 SpringBoot 项⽬
application.properties ,我只配置了最基本的
#配置程序端⼝,默认为8080
server.port= 8080
# 配置默认访问路径,默认为/
t-path=/jwt_demo
# 配置 Tomcat
# 配置Tomcat编码,默认为UTF-8
# 配置最⼤线程数
JWT 依赖库
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
模型类
因为我要⽤ RESTful 所以可以⽤⼀个模型类
public class UserModel
{
private String _username;
private String _password;
public String get_username()
{
return _username;
}
public void set_username(String _username)
{
this._username = _username;
}
public String get_password()
{
return _password;
}
public void set_password(String _password)
{
this._password = _password;
}
}
控制器
DemoController ,⽤于测试访问数据,Foo_01()不需要Token,Foo_02()需要 Token @RestController
@RequestMapping("/v1/Demo")
public class DemoController
{
springboot和过滤器@RequestMapping(value = "/Get1",method = RequestMethod.GET,produces = "application/json")
public List<String> Foo_01()
{
List<String> list=new ArrayList<>();
list.add("Foo_01");
list.add("Test");
return list;
}
@RequestMapping("/Get2")
public List<String> Foo_02()
{
List<String> list=new ArrayList<>();
list.add("Foo_02");
list.add("Test");
return list;
}
}
LoginController ,⽤于登录并获取 Token
@RestController
@RequestMapping("/v1/Login")
public class LoginController
{
@RequestMapping(value = "/Login", method = RequestMethod.POST, produces = "application/json") public String Login(@RequestBody UserModel user)
{
if (_username().equals("abc") && _password().equals("123456"))
{
Map<String, String> claimMap = new HashMap<>();
claimMap.put("username", "abc");
return TokenUtli.GenerateToken(claimMap);
}
return "登录失败";
}
}
封装 JWT ⼯具类
其实我封装的很随便啦,仅⽤于本案例
public class TokenUtli
{
//Issuer
public static final String ISSUER = "Test";
//Audience
public static final String AUDIENCE = "Client";
//密钥
public static final String KEY = "ThisIsMySecretKey";
//算法
public static final Algorithm ALGORITHM = Algorithm.HMAC256(TokenUtli.KEY);
/
/Header
public static final Map<String, Object> HEADER_MAP = new HashMap<>()
{
{
put("alg", "HS256");
put("typ", "JWT");
}
};
/**
* ⽣成 Token 字符串
*
* @param claimMap claim 数据
* @return Token 字符串
*/
public static String GenerateToken(Map<String, String> claimMap)
{
Date nowDate = new Date();
//120 分钟过期
Date expireDate = TokenUtli.AddDate(nowDate, 2 * 60);
//Token 建造器
JWTCreator.Builder tokenBuilder = ate();
for (Map.Entry<String, String> entry : Set())
{
//Payload 部分,根据需求添加
tokenBuilder.Key(), Value());
}
//token 字符串
String token = tokenBuilder.withHeader(TokenUtli.HEADER_MAP)//Header 部分
.withIssuer(TokenUtli.ISSUER)//issuer
.withAudience(TokenUtli.AUDIENCE)//audience
.withIssuedAt(nowDate)//⽣效时间
.withExpiresAt(expireDate)//过期时间
.sign(TokenUtli.ALGORITHM);//签名,算法加密
return token;
}
/**
* 时间加法
*
* @param date 当前时间
* @param minute 持续时间(分钟)
* @return 时间加法结果
*/
private static Date AddDate(Date date, Integer minute)
{
if (null == date)
{
date = new Date();
}
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.add(Calendar.MINUTE, minute);
Time();
}
}
在此⽰例中,我们指定了必须考虑哪些参数才能将 JWT 视为有效。根据我们的代码,以下项⽬认为令牌有效:
验证⽣成令牌的服务器 Issuer
验证令牌的接收者被授权接收 Audience
检查令牌是否未过期以及颁发者的签名密钥是否有效 Lifetime
验证令牌的签名 IssuerSigningKey
此外,我们指定Issuer、Audience、SigningKey的值。在本例中,我将这些值存储在常量中。
验证 Token ,Token 错误或者过期就抛出异常,这⾥我就只判断了⼀个空字符串,其它验证规则可以⾃⼰写啦/**
* 验证 Token
*
* @param webToken 前端传递的 Token 字符串
* @return Token 字符串是否正确
* @throws Exception 异常信息
*/
public static boolean VerifyJWTToken(String webToken) throws Exception
{
String[] token = webToken.split(" ");
if (token[1].equals(""))
{
throw new Exception("token错误");
}
//JWT验证器
JWTVerifier verifier = quire(TokenUtli.ALGORITHM).withIssuer(TokenUtli.ISSUER).build();
//解码
DecodedJWT jwt = verifier.verify(token[1]);//如果 token 过期,此处就会抛出异常
//Audience
List<String> audienceList = Audience();
String audience = (0);
//Payload
Map<String, Claim> claimMap = Claims();
for (Map.Entry<String, Claim> entry : Set())
{
}
//⽣效时间
Date issueTime = IssuedAt();
/
/过期时间
Date expiresTime = ExpiresAt();
return true;
}
注册
JWTInterceptor
public class JWTInterceptor implements HandlerInterceptor
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
//从请求头内获取token
String token = Header("authorization");
//验证令牌,如果令牌不正确会出现异常会被全局异常处理
return TokenUtli.VerifyJWTToken(token);
}
}
注册
@Configuration
public class InterceptorConfig implements WebMvcConfigurer
{
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(new JWTInterceptor()).addPathPatterns("/**")//全部路径
.excludePathPatterns("/v1/Demo/Get1")//排除不需要Token的路径
.excludePathPatterns("/v1/Login/Login");//开放登录路径
}
}
测试
因为懒得写前端页⾯,所以使⽤ postman 调试
⾸先,不登录去访问 DemoController ⾥的两个函数
报了 500错误
登录,以及⽣成的 Token
这次就访问到数据了
SpringBoot 使⽤ JWT 结束
项⽬结构
写完才发现,我Util⼜拼错了,拉倒吧,之后整 AOP 重新写过吧
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论