springboot 添加JWT接口认证
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | 低调码农哥!
来源 | urlify.cn/6fuQVj
注:此认证方式生成token基本jdk8.0, jdk其它版本有些方法不支持
pom.xml文件添加引用
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
完整pom文件
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.xjgroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>demoname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
<kotlin.version>1.4.0kotlin.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>3.0.0version>
<scope>compilescope>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
<version>1.18.12version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
dependency>
<dependency>
<groupId>commons-beanutilsgroupId>
<artifactId>commons-beanutilsartifactId>
<version>1.9.3version>
dependency>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.2.1version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>net.sf.ezmorphgroupId>
<artifactId>ezmorphartifactId>
<version>1.0.6version>
dependency>
<dependency>
<groupId>net.sf.json-libgroupId>
<artifactId>json-libartifactId>
<version>2.2.3version>
<classifier>jdk15classifier>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.7version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>3.0.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<mainClass>com.xj.demo.DemoApplicationmainClass>
configuration>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-jar-pluginartifactId>
<version>2.6version>
<configuration>
<archive>
<manifest>
<addClasspath>trueaddClasspath>
<classpathPrefix>lib/classpathPrefix>
<mainClass>com.xj.demo.DemoApplicationmainClass>
manifest>
archive>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-dependency-pluginartifactId>
<version>2.10version>
<executions>
<execution>
<id>copy-dependenciesid>
<phase>packagephase>
<goals>
<goal>copy-dependenciesgoal>
goals>
<configuration>
<outputDirectory>${project.build.directory}/configoutputDirectory>
configuration>
execution>
executions>
plugin>
plugins>
build>
project>
jwt密钥配置 application.properties
#基本配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/business?useUnicode=true&characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
#使用mysql
spring.jpa.database = mysql
#是否显示sql语句
spring.jpa.show-sql=true
#mybatis配置 mybatis.config-location=classpath:mybatis-config.xml // 配置文件位置
mybatis.typeAliasesPackage=com.xj.demo.model
mybatis.mapper-locations=classpath:mapper/*.xml
#代表这个JWT的接收对象,存入audience
audience.clientId=098f6bcd4621d373cade4e832627b4f6
# 密钥, 经过Base64加密, 可自行替换
audience.base64Secret=MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=
#JWT的签发主体,存入issuer
audience.name=springbootapi
# 过期时间,48小时过期
audience.expiresSecond= 172800
#配置mybaits自定义类型转换类所在的包
# mybatis.type-handlers-package=com.hl.handler
JWTToken类
package com.xj.demo.common;
import com.xj.demo.common.exception.CustomException;
import com.xj.demo.common.response.ResultCode;
import com.xj.demo.config.Audience;
import io.jsonwebtoken.*;
import org.apache.logging.log4j.util.Base64Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
/**
* JWTToken类
*/
public class JwtTokenUtil {
private static Logger log = LoggerFactory.getLogger(JwtTokenUtil.class);
public static final String AUTH_HEADER_KEY = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 解析jwt
* @param jsonWebToken
* @param base64Security
* @return
*/
public static Claims parseJWT(String jsonWebToken, String base64Security) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(jsonWebToken).getBody();
return claims;
} catch (ExpiredJwtException eje) {
log.error("===== Token过期 =====", eje);
throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED);
} catch (Exception e){
log.error("===== token解析异常 =====", e);
throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
}
}
/**
* 构建jwt
* @param userId
* @param username
* @param role
* @param audience
* @return
*/
public static String createJWT(String userId, String username, String role, Audience audience) {
try {
// 使用HS256加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(audience.getBase64Secret());
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//userId是重要信息,进行加密下
String encryId = Base64Util.encode(userId);
//添加构成JWT的参数
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
// 可以将基本不重要的对象信息放到claims
.claim("role", role)
.claim("userId", userId)
.setSubject(username) // 代表这个JWT的主体,即它的所有人
.setIssuer(audience.getClientId()) // 代表这个JWT的签发主体;
.setIssuedAt(new Date()) // 是一个时间戳,代表这个JWT的签发时间;
.setAudience(audience.getName()) // 代表这个JWT的接收对象;
.signWith(signatureAlgorithm, signingKey);
//添加Token过期时间
int TTLMillis = audience.getExpiresSecond();
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp) // 是一个时间戳,代表这个JWT的过期时间;
.setNotBefore(now); // 是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的
}
//生成JWT
return builder.compact();
} catch (Exception e) {
log.error("签名失败", e);
throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
}
}
/**
* 从token中获取用户名
* @param token
* @param base64Security
* @return
*/
public static String getUsername(String token, String base64Security){
return parseJWT(token, base64Security).getSubject();
}
/**
* 从token中获取用户ID
* @param token
* @param base64Security
* @return
*/
public static String getUserId(String token, String base64Security){
String userId = parseJWT(token, base64Security).get("userId", String.class);
//return Base64Util.decode(userId);
return userId;
}
/**
* 是否已过期
* @param token
* @param base64Security
* @return
*/
public static boolean isExpiration(String token, String base64Security) {
return parseJWT(token, base64Security).getExpiration().before(new Date());
}
}
token验证拦截器(filter)
package com.xj.demo.filter;
import com.xj.demo.annotation.JwtIgnore;
import com.xj.demo.common.JwtTokenUtil;
import com.xj.demo.common.exception.CustomException;
import com.xj.demo.common.response.ResultCode;
import com.xj.demo.config.Audience;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ========================
* token验证拦截器
* Created with IntelliJ IDEA.
* User:xj
* Date:2020/8/18
* Version: v1.0
* ========================
*/
@Slf4j
//@WebFilter(urlPatterns = "/filter-api/*", filterName = "jwkTokenFilter")
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Autowired
private Audience audience;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 忽略带JwtIgnore注解的请求, 不做后续token认证校验
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
if (jwtIgnore != null) {
return true;
}
}
if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
// 获取请求头信息authorization信息
final String authHeader = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
log.info("## authHeader= {}", authHeader);
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
log.info("### 用户未登录,请先登录 ###");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
}
// 获取token
final String token = authHeader.substring(7);
if(audience == null){
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
audience = (Audience) factory.getBean("audience");
}
try {
// 验证token是否有效--无效已做异常抛出,由全局异常处理后返回对应信息
JwtTokenUtil.parseJWT(token, audience.getBase64Secret());
}
catch (Exception e){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
}
return true;
}
}
JWT验证忽略注解
package com.xj.demo.annotation;
import java.lang.annotation.*;
/**
* ========================
* JWT验证忽略注解
* Created with IntelliJ IDEA.
* User:xj
* Date:2020/8/18 9:50
* Version: v1.0
* ========================
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JwtIgnore {
}
自定义异常类型
package com.xj.demo.common.exception;
import com.xj.demo.common.response.ResultCode;
import java.text.MessageFormat;
/**
* 自定义异常类型
* @author xj
**/
public class CustomException extends RuntimeException {
//错误代码
ResultCode resultCode;
public CustomException(ResultCode resultCode){
super(resultCode.message());
this.resultCode = resultCode;
}
public CustomException(ResultCode resultCode, Object... args){
super(resultCode.message());
String message = MessageFormat.format(resultCode.message(), args);
resultCode.setMessage(message);
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return resultCode;
}
}
统一响应结果集
package com.xj.demo.common.response;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* ========================
* 统一响应结果集
* Created with IntelliJ IDEA.
* User:xj
* Date:2020/8/18 9:50
* Version: v1.0
* ========================
*/
public class Result<T> {
//操作代码
int code;
//提示信息
String message;
//结果数据
T data;
public Result(ResultCode resultCode){
this.code = resultCode.code();
this.message = resultCode.message();
}
public Result(ResultCode resultCode, T data){
this.code = resultCode.code();
this.message = resultCode.message();
this.data = data;
}
public Result(String message){
this.message = message;
}
public static Result SUCCESS(){
return new Result(ResultCode.SUCCESS);
}
public static
Result SUCCESS(T data){ return new Result(ResultCode.SUCCESS, data);
}
public static Result FAIL(){
return new Result(ResultCode.FAIL);
}
public static Result FAIL(String message){
return new Result(message);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
package com.xj.demo.common.response;
/**
* ========================
* 通用响应状态
* Created with IntelliJ IDEA.
* User:xj
* Date:2020/8/18
* Time:10:10
* Version: v1.0
* ========================
*/
public enum ResultCode {
/* 成功状态码 */
SUCCESS(1,"操作成功!"),
/* 错误状态码 */
FAIL(-1,"操作失败!"),
/* 参数错误:10001-19999 */
PARAM_IS_INVALID(10001, "参数无效"),
PARAM_IS_BLANK(10002, "参数为空"),
PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"),
PARAM_NOT_COMPLETE(10004, "参数缺失"),
/* 用户错误:20001-29999*/
USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),
USER_LOGIN_ERROR(20002, "账号不存在或密码错误"),
USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),
USER_NOT_EXIST(20004, "用户不存在"),
USER_HAS_EXISTED(20005, "用户已存在"),
/* 数据错误:50001-599999 */
RESULT_DATA_NONE(50001, "数据未找到"),
DATA_IS_WRONG(50002, "数据有误"),
DATA_ALREADY_EXISTED(50003, "数据已存在"),
/* 权限错误:70001-79999 */
PERMISSION_UNAUTHENTICATED(70001,"此操作需要登陆系统!"),
PERMISSION_UNAUTHORISE(70002,"权限不足,无权操作!"),
PERMISSION_EXPIRE(70003,"登录状态过期!"),
PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),
PERMISSION_LIMIT(70005, "访问次数受限制"),
PERMISSION_TOKEN_INVALID(70006, "无效token"),
PERMISSION_SIGNATURE_ERROR(70007, "签名失败");
//操作代码
int code;
public int code() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String message() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
//提示信息
String message;
ResultCode(int code, String message){
this.code = code;
this.message = message;
}
}
登录接口
package com.xj.demo.controller;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.xj.demo.annotation.JwtIgnore;
import com.xj.demo.common.JwtTokenUtil;
import com.xj.demo.common.response.Result;
import com.xj.demo.config.Audience;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jackson.JsonObjectDeserializer;
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.HttpServletResponse;
import java.util.UUID;
public class LoginController {
private Audience audience;
public Result adminLogin(HttpServletResponse response, String username, String password) {
// 这里模拟测试, 默认登录成功,返回用户ID和角色信息
String userId = UUID.randomUUID().toString();
String role = "admin";
// 创建token
String token = JwtTokenUtil.createJWT(userId, username, role, audience);
log.info("### 登录成功, token={} ###", token);
// 将token放在响应头
response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, JwtTokenUtil.TOKEN_PREFIX + token);
return Result.SUCCESS(token);
}
}
验证登录截图
源码下载地址:https://download.csdn.net/download/haojuntu/12805887
粉丝福利:108本java从入门到大神精选电子书领取
???
?长按上方锋哥微信二维码 2 秒 备注「1234」即可获取资料以及 可以进入java1234官方微信群
感谢点赞支持下哈
评论