参考资料
官网: https://jwt.io/
jwt解析token报错:Signed Claims JWSs are not supported.
一个简单的 JWT 认证
代码地址
https://github.com/laolang2016/jwt-study/tree/master/jwt-01
关键代码图解
登录接口与登录服务
AuthController
package com.laolang.shop.modules.auth.controller;
import com.laolang.shop.common.domain.R;
import com.laolang.shop.modules.auth.domain.LoginUser;
import com.laolang.shop.modules.auth.logic.AuthLogic;
import com.laolang.shop.modules.auth.rsp.LoginRsp;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequiredArgsConstructor
@RequestMapping("auth")
@RestController
public class AuthController {
private final AuthLogic authLogic;
/**
* 登录接口
*/
@PostMapping("login")
public R<LoginRsp> login(@RequestBody LoginUser loginUser) {
return R.ok(authLogic.login(loginUser));
}
}
AuthLogic
package com.laolang.shop.modules.auth.logic;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Sets;
import com.laolang.shop.common.exception.BusinessException;
import com.laolang.shop.modules.auth.domain.AuthUser;
import com.laolang.shop.modules.auth.domain.LoginUser;
import com.laolang.shop.modules.auth.rsp.LoginRsp;
import com.laolang.shop.modules.auth.service.TokenService;
import java.util.Set;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class AuthLogic {
private final TokenService tokenService;
private Set<String> usernames;
@PostConstruct
public void init() {
usernames = Sets.newHashSet();
usernames.add("admin");
usernames.add("laolang");
}
public LoginRsp login(LoginUser loginUser) {
if (!usernames.contains(loginUser.getUsername())) {
log.info("用户不存在");
throw new BusinessException("用户名或密码错误");
}
if (!StrUtil.equals("123456", loginUser.getPassword())) {
log.info("密码错误");
throw new BusinessException("用户名或密码错误");
}
AuthUser authUser = new AuthUser();
authUser.setUsername(loginUser.getUsername());
// 生成 token
String token = tokenService.buildToken(authUser);
log.info("token:{}", token);
LoginRsp rsp = new LoginRsp();
rsp.setToken(token);
return rsp;
}
}
TokenService
package com.laolang.shop.modules.auth.service;
import cn.hutool.core.util.IdUtil;
import com.google.common.collect.Maps;
import com.laolang.shop.common.consts.GlobalConst;
import com.laolang.shop.modules.auth.consts.logic.AuthBizCode;
import com.laolang.shop.modules.auth.domain.AuthUser;
import com.laolang.shop.modules.auth.exception.AuthBusinessException;
import com.laolang.shop.modules.auth.properties.TokenProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* token 工具服务
*/
@RequiredArgsConstructor
@Service
public class TokenService {
private final TokenProperties tokenProperties;
/**
* 生成 token
*/
public String buildToken(AuthUser authUser) {
authUser.setUuid(IdUtil.fastSimpleUUID());
Map<String, Object> claims = Maps.newHashMap();
Date iat = new Date();
Date exp = new Date(iat.getTime() + tokenProperties.getExpireTime() * 60 * 1000);
claims.put(GlobalConst.LOGIN_USER_KEY, authUser.getUuid());
claims.put("username", authUser.getUsername());
return Jwts.builder()
.setIssuedAt(iat) // 签发时间
.setExpiration(exp) // 过期时间
.setClaims(claims) // 自定义数据
.signWith(SignatureAlgorithm.HS512, tokenProperties.getSecret()).compact();
}
/**
* 校验 token
*/
public AuthUser verify(String token) {
try {
Claims claims = Jwts.parser().setSigningKey(tokenProperties.getSecret()).parseClaimsJws(token).getBody();
AuthUser authUser = new AuthUser();
authUser.setUsername(claims.get("username", String.class));
authUser.setUuid(claims.get(GlobalConst.LOGIN_USER_KEY, String.class));
return authUser;
} catch (ExpiredJwtException e) {
throw new AuthBusinessException(AuthBizCode.login_expired);
} catch (Exception e) {
throw new AuthBusinessException(AuthBizCode.error_token);
}
}
}
拦截器
package com.laolang.shop.config.web.interceptor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.shop.common.consts.GlobalConst;
import com.laolang.shop.common.domain.R;
import com.laolang.shop.common.util.ServletKit;
import com.laolang.shop.modules.auth.consts.logic.AuthBizCode;
import com.laolang.shop.modules.auth.consts.logic.AuthConsts;
import com.laolang.shop.modules.auth.domain.AuthUser;
import com.laolang.shop.modules.auth.exception.AuthBusinessException;
import com.laolang.shop.modules.auth.properties.TokenProperties;
import com.laolang.shop.modules.auth.service.TokenService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* jwt 拦截器
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final TokenService tokenService;
private final TokenProperties tokenProperties;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull Object handler) throws Exception {
try {
String token = getToken(request);
if (StrUtil.isBlank(token)) {
log.warn("token 不存在");
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.doOverdue()));
return false;
}
AuthUser authUser = tokenService.verify(token);
// 填充 authUser 信息
if (StrUtil.equals(authUser.getUsername(), "admin")) {
authUser.setId(1L);
} else {
authUser.setId(2L);
}
// TODO 一些其他操作, 比如刷新 token
// 放入到 request 中, 供后续使用
request.setAttribute(AuthConsts.AUTH_USER_ATTR_NAME, authUser);
} catch (AuthBusinessException e) {
log.warn("token 解析异常");
if (StrUtil.equals(AuthBizCode.login_expired.getCode(), e.getCode())) {
log.warn("token 过期");
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.doOverdue()));
} else {
log.warn("非法的 token");
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.failed("身份验证异常")));
}
return false;
} catch (Exception e) {
log.error("auth 拦截器错误:{}", ExceptionUtils.getMessage(e));
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.error("服务器内部错误")));
return false;
}
return true;
}
private String getToken(HttpServletRequest request) {
String token = request.getHeader(tokenProperties.getHeader());
if (StrUtil.isNotEmpty(token) && token.startsWith(GlobalConst.TOKEN_PREFIX)) {
token = token.replace(GlobalConst.TOKEN_PREFIX, "");
}
return token;
}
}
效果
vscode .http 文件
@baseurl = http://localhost:8091
# @name login
POST {{baseurl}}/auth/login HTTP/1.1
Content-Type: application/json
{
"username":"admin",
"password":"123456"
}
@authToken = {{login.response.body.body.token}}
###
GET {{baseurl}}/admin/sysdict/info HTTP/1.1
Authorization: {{authToken}}
效果图
登录
校验
添加一个匿名访问注解
注解
package com.laolang.shop.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}
拦截器
package com.laolang.shop.config.web.interceptor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.shop.common.annotation.AnonymousAccess;
import com.laolang.shop.common.consts.GlobalConst;
import com.laolang.shop.common.domain.R;
import com.laolang.shop.common.util.ServletKit;
import com.laolang.shop.modules.auth.consts.logic.AuthBizCode;
import com.laolang.shop.modules.auth.consts.logic.AuthConsts;
import com.laolang.shop.modules.auth.domain.AuthUser;
import com.laolang.shop.modules.auth.exception.AuthBusinessException;
import com.laolang.shop.modules.auth.properties.TokenProperties;
import com.laolang.shop.modules.auth.service.TokenService;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* jwt 拦截器
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final TokenService tokenService;
private final TokenProperties tokenProperties;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull Object handler) throws Exception {
try {
// 有匿名访问注解, 直接跳过
if( isAnonymousAccess(handler)){
return true;
}
// 开始校验 token
String token = getToken(request);
if (StrUtil.isBlank(token)) {
log.warn("token 不存在");
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.doOverdue()));
return false;
}
AuthUser authUser = tokenService.verify(token);
// 填充 authUser 信息
if (StrUtil.equals(authUser.getUsername(), "admin")) {
authUser.setId(1L);
} else {
authUser.setId(2L);
}
// TODO 一些其他操作, 比如刷新 token
// 放入到 request 中, 供后续使用
request.setAttribute(AuthConsts.AUTH_USER_ATTR_NAME, authUser);
} catch (AuthBusinessException e) {
log.warn("token 解析异常");
if (StrUtil.equals(AuthBizCode.login_expired.getCode(), e.getCode())) {
log.warn("token 过期");
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.doOverdue()));
} else {
log.warn("非法的 token");
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.failed("身份验证异常")));
}
return false;
} catch (Exception e) {
log.error("auth 拦截器错误:{}", ExceptionUtils.getMessage(e));
ServletKit.writeJson(response, JSONUtil.toJsonStr(R.error("服务器内部错误")));
return false;
}
return true;
}
/**
* 是否为匿名访问接口
*/
private boolean isAnonymousAccess(Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
AnonymousAccess anonymousAccess = handlerMethod.getMethod().getAnnotation(AnonymousAccess.class);
return Objects.nonNull(anonymousAccess);
}
/**
* 获取请求头中的 token
*/
private String getToken(HttpServletRequest request) {
String token = request.getHeader(tokenProperties.getHeader());
if (StrUtil.isNotEmpty(token) && token.startsWith(GlobalConst.TOKEN_PREFIX)) {
token = token.replace(GlobalConst.TOKEN_PREFIX, "");
}
return token;
}
}
使用
package com.laolang.shop.modules.admin.controller;
import com.google.common.collect.Maps;
import com.laolang.shop.common.annotation.AnonymousAccess;
import com.laolang.shop.common.domain.R;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("admin/test")
@RestController
public class SysTestController {
@AnonymousAccess
@GetMapping("testAnonymousAccess")
public R<Map<String, Object>> testAnonymousAccess() {
log.info("admin sysdict info");
Map<String, Object> body = Maps.newHashMap();
body.put("msg","这是一个匿名访问接口");
return R.ok(body);
}
}