基于JAVA JWT 实现OATUH TOKEN验证

最近在前后端分离开发中遇到了JWT TOKEN鉴权登录,顺便做个笔记

1.为什么使用JWT?

随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

2.jwt的优点

体积小,因而传输速度更快
多样化的传输方式,可以通过URL传输、POST传输、请求头Header传输(常用)
简单方便,服务端拿到jwt后无需再次查询数据库校验token可用性,也无需进行redis缓存校验
在分布式系统中,很好地解决了单点登录问题
很方便的解决了跨域授权问题,因为跨域无法共享cookie

客户端与服务器端关于token的验证示意图:

更详细请参照JWT官网: https://jwt.io/
JWT(Java版)的github地址:https://github.com/jwtk/jjwt

下面来说一下具体实现:

1.pom.xml 导入jwt的包

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>2.2.0</version>
   </dependency>

2.编写jwt的工具类,有加密解密功能就好

package com.lgom.jwt;

import java.util.Date;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.alibaba.fastjson.JSON;
import com.lgom.custom.pojo.CheckResult;
import com.lgom.custom.pojo.Constant;
import com.lgom.custom.pojo.SubjectModel;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

public class Jwtutils {
	public static SecretKey generalKey() {
		try {
			//方式一:byte[] encodedKey = Base64.decode(Constant.JWT_SECERT); 引入com.sun.org.apache.xerces.internal.impl.dv.util.Base64
			//方式二:不管哪种方式最终得到一个byte[]类型的key就行
			byte[] encodedKey = Constant.JWT_SECERT.getBytes("UTF-8");
		    SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
		    return key;
		} catch (Exception e) {
			e.printStackTrace();
			 return null;
		}
	}
	/**
	 * 签发JWT
	 * @param id  jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
	 * @param iss jwt签发者
	 * @param subject jwt所面向的用户
	 * @param ttlMillis 有效期,单位毫秒
	 * @return token
	 * @throws Exception
	 */
	public static String createJWT(String id,String iss, String subject, long ttlMillis) {
		SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
		long nowMillis = System.currentTimeMillis();
		Date now = new Date(nowMillis);
		SecretKey secretKey = generalKey();
		JwtBuilder builder = Jwts.builder()
				.setId(id)
				.setIssuer(iss)
				.setSubject(subject)
				.setIssuedAt(now)
				.signWith(signatureAlgorithm, secretKey);
		if (ttlMillis >= 0) {
			long expMillis = nowMillis + ttlMillis;
			Date expDate = new Date(expMillis);
			builder.setExpiration(expDate);
		}
		return builder.compact();
	}
	
	/**
	 * 验证JWT
	 * @param jwtStr
	 * @return
	 */
	public static CheckResult validateJWT(String jwtStr) {
		CheckResult checkResult = new CheckResult();
		Claims claims = null;
		try {
			claims = parseJWT(jwtStr);
			checkResult.setSuccess(true);
			checkResult.setClaims(claims);
		} catch (ExpiredJwtException e) {
			checkResult.setErrCode(Constant.JWT_ERRCODE_EXPIRE);
			checkResult.setSuccess(false);
		} catch (SignatureException e) {
			checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL);
			checkResult.setSuccess(false);
		} catch (Exception e) {
			checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL);
			checkResult.setSuccess(false);
		}
		return checkResult;
	}
	
	/**
	 * 
	 * 解析JWT字符串
	 * @param jwt
	 * @return
	 * @throws Exception
	 */
	public static Claims parseJWT(String jwt) throws Exception {
		SecretKey secretKey = generalKey();
		return Jwts.parser()
			.setSigningKey(secretKey)
			.parseClaimsJws(jwt)
			.getBody();
	}
       /**
	 * 生成subject信息
	 * @param user
	 * @return
	 */
	public static String generalSubject(SubjectModel sub){
		return JSON.toJSONString(sub);
	}
}

用到的SubjectModel是把需要在token返回的参数封装了一下

/**
 * jwt所面向的用户
 * @author wangbaoyuan 
 */
public class SubjectModel {

	private Long userId;
	
	private String userName;

     //省略get,set
}

3.jwt有了,ssm要如何去利用,用户验证的第一步是登录,登录时根据用户传来的username和password到数据库验证身份,如果合法,便给该用户jwt加密生成token

@RequestMapping("/login")
	@ResponseBody
	@ApiOperation(value = "用户登录接口", notes = "用户登录接口", httpMethod = "POST", response = ResultUtil.class)
	public ResultUtil login(HttpServletRequest req, String username, String password) {
		//*****登陆逻辑开始......结束*****
		if(user!=null) {
			//登陆验证通过后
			SubjectModel sub = new SubjectModel(user.getId(), user.getUsername());//用户信息
			String token = Jwtutils.createJWT(UUIDGenerator.getUUID(),Constant.JWT_ISS,Jwtutils.generalSubject(sub), Constant.JWT_TTL);
			ResultUtil res = new ResultUtil(Constant.RESCODE_SUCCESS, "登录成功!",0L, null, token);
			return res;
		}else {
			return ResultUtil.error("登录失败!");
		}
	}

这里用到的全局配置常量类Constant:

/**
 * 全局配置常量
 * @author wangbaoyuan
 *
 */
public class Constant {
	/**
	 * 成功
	 */
	public static final int RESCODE_SUCCESS = 0;//成功(无返回数据)
	
	/**
	 * 失败
	 */
	public static final int RESCODE_EXCEPTION = 1001;//请求抛出异常
	public static final int RESCODE_NOLOGIN = 1002;	//未登陆状态
	public static final int RESCODE_NOAUTH = 1003;//无操作权限
	public static final int RESCODE_LOGINEXPIRE = 1004;//登录过期
	
	/**
	 * token
	 */
	public static final int JWT_ERRCODE_EXPIRE = 1005;//Token过期
	public static final int JWT_ERRCODE_FAIL = 1006;//验证不通过

	/**
	 * jwt
	 */
	public static final String JWT_ISS = "erbaojun";	//jwt签发者
	public static final String JWT_SECERT = "46cc793c53dc451b8a4fe2cd0bb00847";//密匙
	public static final long JWT_TTL = 2 *60*60*1000;//token有效时间,单位毫秒
	
}

4.在用户登录时,服务端生成token,客户端然后把token存储在请求头文件中,以后用户每次请求时,都得带上token,后台拿到token进行校验

下面放出拦截器代码

package com.lgom.Filter;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;
import com.lgom.custom.pojo.CheckResult;
import com.lgom.custom.pojo.Constant;
import com.lgom.custom.pojo.SubjectModel;
import com.lgom.jwt.Jwtutils;
import com.lgom.util.ResultUtil;

import io.jsonwebtoken.Claims;

public class AppInterceptor implements HandlerInterceptor {

	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		response.setContentType("text/html;charset=UTF-8"); 
		//请求URL
		String url =request.getServletPath().toString();

		String tokenStr = request.getHeader("token");
		System.out.println(tokenStr);
		if (tokenStr == null || tokenStr.equals("")) {
			PrintWriter printWriter = response.getWriter();
			printWriter.print(JSON.toJSONString(new ResultUtil(Constant.RESCODE_NOLOGIN, "用户未登录",0L, null, null)));
			printWriter.flush();
			printWriter.close();
			return false;
		}
		
		// 验证JWT的签名,返回CheckResult对象
		CheckResult checkResult = Jwtutils.validateJWT(tokenStr);
		if (checkResult.isSuccess()) {
			Claims claims = checkResult.getClaims();
			System.out.println("token校检通过checkResult:"+JSON.toJSONString(checkResult));
			SubjectModel user =JSON.parseObject(claims.getSubject(), SubjectModel.class);
			System.out.println("token校检通过user:"+JSON.toJSONString(user));
			return true;
		} else {
			switch (checkResult.getErrCode()) {
			// 签名过期,返回过期提示码
			case Constant.JWT_ERRCODE_EXPIRE:
				PrintWriter printWriter = response.getWriter();
				printWriter.print(JSON.toJSONString(new ResultUtil(Constant.RESCODE_LOGINEXPIRE, "登录过期",0L, null, null)));
				printWriter.flush();
				printWriter.close();
				break;
			// 签名验证不通过
			case Constant.JWT_ERRCODE_FAIL:
				PrintWriter printWriter2 = response.getWriter();
				printWriter2.print(JSON.toJSONString(new ResultUtil(Constant.RESCODE_NOAUTH, "拒绝授权",0L, null, null)));
				printWriter2.flush();
				printWriter2.close();
				break;
			default:
				break;
			}
			return false;
		}
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		// TODO Auto-generated method stub

	}

}

贴出封装的结果类代码

import java.io.Serializable;

public class ResultUtil implements Serializable {
	private Integer code;
	private String msg="";
	private Long count=0L;
	private Object data;
	private String token;
	
	public ResultUtil() {
		super();
	}

	public ResultUtil(Integer code) {
		super();
		this.code = code;
	}

	public ResultUtil(Integer code, String msg) {
		super();
		this.code = code;
		this.msg = msg;
	}
	
	public Integer getCode() {
		return code;
	}

	public void setCode(Integer code) {
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}


	public Long getCount() {
		return count;
	}

	public void setCount(Long count) {
		this.count = count;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	public static ResultUtil ok(){
		return new ResultUtil(0);
	}
	
	public String getToken() {
		return token;
	}

	public void setToken(String token) {
		this.token = token;
	}

	public static ResultUtil ok(Object list){
		ResultUtil result = new ResultUtil();
		result.setCode(0);
		result.setData(list);;
		return result;
	}
	public static ResultUtil ok(String msg){
		ResultUtil result = new ResultUtil();
		result.setCode(0);
		result.setMsg(msg);
		return result;
	}
	
	public ResultUtil(Integer code, String msg, Long count, Object data, String token) {
		super();
		this.code = code;
		this.msg = msg;
		this.count = count;
		this.data = data;
		this.token = token;
	}

	public static ResultUtil error(String str){
		return new ResultUtil(500,str);
	}
	
}

因为是每个登录过后的每个请求,这里用springmvc的拦截器做

<mvc:interceptors>
	    <mvc:interceptor>
	    <!-- 默认拦截所有请求 -->
	      <mvc:mapping path="/**"/>
	      <!-- 登录接口不拦截 -->
	      <mvc:exclude-mapping path="/sys/login" />
	      <bean class="com.lgom.Filter.AppInterceptor"/>
	    </mvc:interceptor>
	</mvc:interceptors>

码字不易,有用请记得打赏哝!

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注