SpringBoot整合JJWT实现Token登录认证

Java技术 潘老师 4年前 (2020-09-29) 4347 ℃ (0) 扫码查看

如果你还不知道什么是JWT,可以参考文章:

JWT是什么?有什么作用?一文帮你了解JWT!

一、什么是JWT? 1、引用官网对JWT的描述,翻译意思大概如下: JWT全称为JSON Web Token, […]

我们这里用到SpringBoot整合JJWT实现Token登录认证,JJWT是指Java JWT,适用于 Java 和 Android 的 JSON Web Token(JWT)库。下面我们通过SpringBoot整合JJWT来实现Token登录认证。

1、为了方便演示,我这里只创建了只有Spring Web模块的SpringBoot项目,没有涉及数据库操作,项目创建很简单,我在此只贴两张IDEA中创建的两张图:
SpringBoot整合JJWT实现Token登录认证
只选择Spring Web模块
SpringBoot整合JJWT实现Token登录认证

2、在resources目录中新建application.yml配置文件(去除自动生成的application.properties),只简单配置下服务器端口为8080:

server:
  port: 8080
提示:可以参考JJWT的官方github:点击直达

1、在application.yml新增JWT相关配置,如下:

# jwt 配置
custom:
  jwt:
    # header:凭证(校验的变量名)
    header: Authorization
    # 有效期1天(单位:s)
    expire: 5184000
    # secret: 秘钥(普通字符串) 不能太短,太短可能会导致报错
    secret: 99c2918fe19d30bce25abfac8a3733ec
    # 签发者
    issuer: panziye

2、在pom.xml中引入jjwt相关依赖(我们使用目前最新版0.11.2版本)和fastjson依赖,方便后面操作json,新增配置如下:

<!--    fastjson    -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.9</version>
</dependency>

<!--    jjwt    -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

3、在项目中新建util包,在util包下新建JwtUtil类,主要实现创建Token、解析Token和判断Token是否过期功能代码如下:

package com.panziye.jwtdemo.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "custom.jwt")
public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
// 秘钥
private String secret;
// 有效时间
private Long expire;
// 用户凭证
private String header;
// 签发者
private String issuer;
/**
* 生成token签名
* @param subject
* @return
*/
public String createToken(String subject) {
Date now = new Date();
// 过期时间
Date expireDate = new Date(now.getTime() + expire * 1000);
//创建Signature SecretKey
final SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
//header参数
final Map<String, Object> headerMap = new HashMap<>();
headerMap.put("alg", "HS256");
headerMap.put("typ", "JWT");
//生成token
String token = Jwts.builder()
.setHeader(headerMap)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expireDate)
.setIssuer(issuer)
.signWith(key,SignatureAlgorithm.HS256)
.compact();
logger.info("JWT[" + token + "]");
return token;
}
/**
* 解析token
*
* @param token token
* @return
*/
public Claims parseToken(String token) {
Claims claims = null;
try {
//创建Signature SecretKey
final SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
logger.info("Parse JWT token success");
} catch (JwtException  e) {
logger.info("Parse JWT errror " + e.getMessage());
return null;
}
return claims;
}
/**
* 判断token是否过期
*
* @param expiration
* @return
*/
public boolean isExpired(Date expiration) {
return expiration.before(new Date());
}
//getter and setter
public void setSecret(String secret) {
this.secret = secret;
}
public void setExpire(Long expire) {
this.expire = expire;
}
public void setHeader(String header) {
this.header = header;
}
//用于其他地方获取Header配置信息
public String getHeader() {
return header;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
}

4、在项目中新建annotation包,在annotation下新建PassLogin注解类,方便用于后面针对某些请求方法添加此注解可以忽略Token验证,PassLogin.java代码如下:

package com.panziye.jwtdemo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassLogin {
boolean required() default true;
}

5、在项目中新建http包,在http下新建HttpEnum枚举类,用于存放响应状态码和对应的提示信息,这里面我们只列出一些常用的,可以根据自己需要补充,代码如下:

package com.panziye.jwtdemo.http;
public enum HttpEnum {
/**
* 请求处理正常
*/
OK(200, "请求成功"),
/**
* 请求成功并且服务器创建了新的资源。
*/
CREATED(201, "创建成功"),
/**
* 用户发出的请求有错误,服务器没有进行新建或修改数据的操作
*/
INVALID_REQUEST(400, "非法请求"),
/**
* 访问内容不存在
*/
NOTFOUND(404, "访问内容不存在"),
/**
* 表示用户没有权限(令牌、用户名、密码错误)
*/
UNAUTHORIZED(401,"抱歉,您没有权限"),
/**
* 表示用户得到授权(与401错误相对),但是访问是被禁止的
*/
FORBIDDEN(403,"禁止访问"),
/**
* 系统内部错误
*/
INTERNAL_SERVER_ERROR(500, "系统内部错误");
private String msg;
private int code;
private HttpEnum(int code, String msg) {
this.msg = msg;
this.code = code;
}
//获取code
public int code(){
return code;
}
//获取msg
public String msg(){
return msg;
}
}

6、在http下新建ResponseResult类,用于封装响应状态码、提示信息和数据,这里只构建了一些常用的实例,可以根据自己的需求扩展,代码如下:

package com.panziye.jwtdemo.http;
import java.io.Serializable;
public class ResponseResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
//响应编码
private int code;
//提示信息
private String msg;
//响应数据
private T data;
//根据属性构建ResponseResult
private static <T> ResponseResult<T> build( int code,String msg,T data) {
return new ResponseResult<T>().setCode(code).setMsg(msg).setData(data);
}
//构建一些常用的
//请求正常
public static <T> ResponseResult<T> ok(){
return build(HttpEnum.OK.code(),HttpEnum.OK.msg(),null);
}
public static <T> ResponseResult<T> ok(T data){
return build(HttpEnum.OK.code(),HttpEnum.OK.msg(),data);
}
//创建成功
public static <T> ResponseResult<T> created(){
return build(HttpEnum.CREATED.code(),HttpEnum.CREATED.msg(),null);
}
//非法请求
public static <T> ResponseResult<T> invalid_request(){
return build(HttpEnum.INVALID_REQUEST.code(),HttpEnum.INVALID_REQUEST.msg(),null);
}
//访问内容不存在
public static <T> ResponseResult<T> notFound(){
return build(HttpEnum.NOTFOUND.code(),HttpEnum.NOTFOUND.msg(),null);
}
//没有权限
public static <T> ResponseResult<T> unauthorized(){
return build(HttpEnum.UNAUTHORIZED.code(),HttpEnum.UNAUTHORIZED.msg(),null);
}
//禁止访问
public static <T> ResponseResult<T> forbidden(){
return build(HttpEnum.FORBIDDEN.code(),HttpEnum.FORBIDDEN.msg(),null);
}
//系统内部错误
public static <T> ResponseResult<T> internal_server_error(){
return build(HttpEnum.INTERNAL_SERVER_ERROR.code(),HttpEnum.INTERNAL_SERVER_ERROR.msg(),null);
}
//getter和setter
public int getCode() {
return code;
}
//注意返回值类型
public ResponseResult<T> setCode(int code) {
this.code = code;
return this;
}
public String getMsg() {
return msg;
}
//注意返回值类型
public ResponseResult<T> setMsg(String msg) {
this.msg = msg;
return this;
}
public T getData() {
return data;
}
//注意返回值类型
public ResponseResult<T> setData(T data) {
this.data = data;
return this;
}
}

7、在项目中新建interceptor包,在interceptor下新建TokenInterceptor拦截器类,用于在请求被处理之前对Token进行拦截验证,代码如下:

package com.panziye.jwtdemo.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.panziye.jwtdemo.annotation.PassLogin;
import com.panziye.jwtdemo.http.ResponseResult;
import com.panziye.jwtdemo.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class TokenInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
@Autowired
private JwtUtil jwtUtil;
//在业务处理请求之前处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//设置response响应数据类型为json和编码为utf-8
response.setContentType("application/json;charset=utf-8");
// 判断对象是否是映射到一个方法,如果不是则直接通过
if (!(handler instanceof HandlerMethod)) {
// instanceof运算符是用来在运行时指出对象是否是特定类的一个实例
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//检查方法是否有PassLogin注解,有则跳过认证
if (method.isAnnotationPresent(PassLogin.class)){
return true;
}
// 从HTTP请求头中获取Authorization信息,
String authorization = request.getHeader(jwtUtil.getHeader());
//判断Authorization是否为空
if(StringUtils.isEmpty(authorization)){
logger.info("token无效");
response.getWriter().write(JSONObject.toJSONString(ResponseResult.unauthorized()));
return false;
}
//获取TOKEN,注意要清除前缀"Bearer "
String token = authorization.replace("Bearer ","");
// HTTP请求头中TOKEN解析出的用户信息
Claims claims = jwtUtil.parseToken(token);
if(claims == null){
logger.info("token无效");
response.getWriter().write(JSONObject.toJSONString(ResponseResult.unauthorized()));
return false;
}
//校验是否过期
boolean flag = jwtUtil.isExpired(claims.getExpiration());
if(flag){
logger.error("token过期");
response.getWriter().write(JSONObject.toJSONString(ResponseResult.unauthorized()));
return false;
}
//token正常,获取用户信息,比如这里的subject存的是用户id
String subject = claims.getSubject();
//将用户信息存入request,以便后面处理请求使用
request.setAttribute("subject",subject);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

8、在interceptor下新建TokenConfig拦截器配置类,用于配置TokenInterceptor,我们这里配置拦截所有请求,代码如下:

package com.panziye.jwtdemo.interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class TokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有请求
registry.addInterceptor(tokenInterceptor())
.addPathPatterns("/**");
}
@Bean
public TokenInterceptor tokenInterceptor() {
return new TokenInterceptor();
}
}

1、在项目中新建model包,在model下新建User实体类,代码如下:

package com.panziye.jwtdemo.model;
public class User {
private Long userId;
private String username;
private String password;
public User(){}
public User(Long userId, String username, String password) {
this.userId = userId;
this.username = username;
this.password = password;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

2、在项目中新建controller包,在controller下新建UserController实体类,代码如下:

package com.panziye.jwtdemo.controller;
import com.panziye.jwtdemo.annotation.PassLogin;
import com.panziye.jwtdemo.http.ResponseResult;
import com.panziye.jwtdemo.model.User;
import com.panziye.jwtdemo.util.JwtUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/user")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
@PassLogin
public ResponseResult login(User user, HttpServletResponse response){
//在此省略了service和dao层代码
if("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())){
logger.info("登录成功");
//生成Token,以用户id作为载荷中的subject去创建
String token = jwtUtil.createToken("1");
logger.info("token="+token);
User u = new User(1L,"admin","123456");
response.setHeader(jwtUtil.getHeader(),"Bearer "+token);
ResponseResult result = ResponseResult.ok(u);
return result;
}else {
logger.info("登录失败");
return ResponseResult.invalid_request();
}
}
@GetMapping("/findUser")
public ResponseResult findUser(HttpServletRequest request){
//在拦截器中已经将从token中解析好的subject存入了request域,所以这里能获取到
Long userId = Long.parseLong(request.getAttribute("subject").toString());
System.out.println("发起请求用户的id===="+userId);
//模拟返回一个数据
ResponseResult result = ResponseResult.ok(userId);
return result;
}
}
提示:
1)@RestController结合了@Controller和@ResponseBody两项功能,用于类上,类中的方法返回的数据都会被转化为json格式响应
2)@GetMapping注解表示该方法只能接收get请求,@PostMapping注解表示该方法只能接受post请求,相当于@RequestMapping注解加上method属性指定请求方式效果

3、使用Postman工具测试,Postman工具下载地址:点击去下载

1)测试正常登录,发送登录post请求,携带用户名和密码参数,发现用户信息正常响应了
SpringBoot整合JJWT实现Token登录认证
再查看下Header响应头,发现Authorization有返回的Token令牌(不包含Bearer ),我们复制下这个令牌,用于后面测试。
SpringBoot整合JJWT实现Token登录认证
2)测试findUser的get请求,我们先不携带Token直接,发送get请求,发现返回401无权限
SpringBoot整合JJWT实现Token登录认证
然后我们在Header中的Type选择Bearer Token,把我们刚才复制的Token粘贴进去,再发送该请求,发现正常响应数据:
SpringBoot整合JJWT实现Token登录认证


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/java/1349.html
喜欢 (10)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】