首先我们为什么要用token呢?
session和token都是用来保持会话,功能相同;
session用于临时保存用户信息,session存储在服务端,生命周期跟随服务器状态而变化。
token比session更安全。token不存在于服务端,可跨域调用接口信息。token的信息是加密且有实效性的,每次请求过来都需要重新验证。
接下来我们直接展示实现代码:
pom文件需要先引入jwt依赖:
com.auth0
java-jwt
3.3.0
然后是加密的用户信息实体类:
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* (TahLoginUser)实体类
*
* @author makejava
* @since 2021-12-14 15:09:14
*/
@Data
public class TahLoginUser implements Serializable {
private static final long serialVersionUID = 833285830459008034L;
private Integer id;
/**
* 登录名(不能重复)
*/
@ApiModelProperty(value = "登录名(不能重复)",required = true)
private String userName;
/**
* 密码
*/
@ApiModelProperty(value = "密码",required = true)
private String password;
/**
* 钉钉id
*/
@ApiModelProperty(value = "钉钉id",required = true)
private String userId;
/**
* 真实姓名
*/
@ApiModelProperty("真实姓名")
private String realName;
/**
* 电话
*/
@ApiModelProperty("电话")
private String mobile;
/**
* 邮箱
*/
@ApiModelProperty("邮箱")
private String email;
}
接下来是jwt加密工具类:
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.gson.Gson;
import com.haileer.dd.dingdingserver.entity.TahLoginUser;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class JwtUtil {
final static String SECRET = "jwtToken";//私钥
final static Gson gson = new Gson();
final static long TOKEN_EXP = 60 * 60 * 24 * 30;//过期时间 三十天
// final static long TOKEN_EXP = 240 ;//过期时间,测试使用
public static String createJwt(TahLoginUser tahLoginUser) throws UnsupportedEncodingException {
Algorithm al = Algorithm.HMAC256(SECRET);
Instant instant = LocalDateTime.now().plusSeconds(TOKEN_EXP).atZone(ZoneId.systemDefault()).toInstant();
Date expire = Date.from(instant);
Gson gson = new Gson();
String s = gson.toJson(tahLoginUser);
String token = JWT.create()
.withSubject("userInfo")
.withClaim("user", s)
.withExpiresAt(expire)
.sign(al);
return token;
}
/**
* @Param: 传入token
* @return:
*/
public static boolean verify(String token) throws UnsupportedEncodingException {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
if (jwt.getExpiresAt().before(new Date())) {
System.out.println("token已过期11");
return false;
}
} catch (Exception e) {
System.out.println("token已过期22");
return false;
}
return true;
}
/**
* 获取用户信息
*
* @param request
* @return
*/
public static TahLoginUser getUserIdByToken(HttpServletRequest request) throws UnsupportedEncodingException {
String header = request.getHeader("Authorization");
String token = header.substring(7);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
Claim claim = jwt.getClaim("user");
String json = claim.asString();
TahLoginUser tbLoginUser = gson.fromJson(json, TahLoginUser.class);
return tbLoginUser;
}
}
封装用户信息工具类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
*
* Title: SecurityUserHolder.java
*
*
*
* Description: SpringSecurity用户获取工具类,该类的静态方法可以直接获取已经登录的用户信息
*
*
* @author honghu
* @version honghu_b2b2c 8.0
* @date 2014-4-24
*/
public class SecurityUserHolder {
@SuppressWarnings("unused")
private static Logger logger = LoggerFactory.getLogger(SecurityUserHolder.class);
public static String prefix = "user_token";
//新增头部参数
private static String outfallTypePrefix = "outfall_type";
public static Integer getCurrentUserId() {
Integer userId = null;
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
try {
userId = (Integer) request.getSession().getAttribute(prefix);
} catch (Exception e) {
request.getSession().removeAttribute(prefix);
return null;
}
}
return userId;
}
public static Integer getOutfallType() {
Integer outfallType = null;
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
try {
outfallType = (Integer) request.getSession().getAttribute(outfallTypePrefix);
} catch (Exception e) {
request.getSession().removeAttribute(outfallTypePrefix);
return null;
}
}
return outfallType;
}
public static void setCurrentUserId(Integer userId) {
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.getSession().setAttribute(prefix, userId);
}
}
public static void setOutfallType(Integer outfallType) {
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.getSession().setAttribute(outfallTypePrefix, outfallType);
}
}
public static void removeCurrentUser() {
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.getSession().setAttribute(prefix, null);
request.getSession().removeAttribute(prefix);
}
}
public static void removeOutfallType() {
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.getSession().setAttribute(outfallTypePrefix, null);
request.getSession().removeAttribute(outfallTypePrefix);
}
}
}
配置拦截器以及token解析拦截类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器链配置
*
* @author wsj
* @date 2019/08/13
*/
@Configuration
public class InteceptorChainConfig implements WebMvcConfigurer {
@Bean
public HandlerInterceptor getMyInterceptor() {
/**
* bean注解提前加载 解决@Autowired为空的情况
*/
return new AuthenticationInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//请求参数转换 过滤登录导出图片路径
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**").excludePathPatterns(
"/login/login",//登录接口不拦截 ,其他不需要拦截的接口也在此配置
"/login/webLogin",
"/login-user/web-login",
"/swagger-resources/**",//swagger相关请求路径也不需要拦截
"/webjars/**",
"/v2/**",
"/swagger-ui.html/**",
"/doc.html", "/doc.html/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Configuration
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
ApplicationArguments applicationArguments;
@Autowired
TahLoginUserService loginUserService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if ("OPTIONS".equals(request.getMethod())) {//这里通过判断请求的方法,判断此次是否是预检请求,如果是,立即返回一个204状态吗,标示,允许跨域;预检后,正式请求,这个方法参数就是我们设置的post了
response.setStatus(HttpStatus.NO_CONTENT.value()); //HttpStatus.SC_NO_CONTENT = 204
response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS, DELETE");//当判定为预检请求后,设定允许请求的方法
response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, Token"); //当判定为预检请求后,设定允许请求的头部类型
response.addHeader("Access-Control-Max-Age", "1");
return true;
}
//过滤静态资源图片
String servletUrl = request.getServletPath();
if (servletUrl != null && ("/favicon.ico".equals(servletUrl) || "/error".equals(servletUrl))) {
return true;
}
ResultDto resultDto = new ResultDto<>();
// 从 http 请求头中取出 token
String header = request.getHeader("Authorization");
if (StringUtils.isEmpty(header)) {
SecurityUserHolder.removeCurrentUser();
resultDto.setError(401, "用户未登录");
System.out.println("未登录url="+servletUrl);
throw new CustomException(resultDto);
}
String userId = "";
if (!StringUtils.isEmpty(header)) {
if (!header.startsWith("Bearer")) {
SecurityUserHolder.removeCurrentUser();
resultDto.setError(401, "token错误");
throw new CustomException(resultDto);
}
String token = header.substring(7);
if (null == header || header.trim().equals("")) {
SecurityUserHolder.removeCurrentUser();
resultDto.setError(401, "请求头中没有token");
throw new CustomException(resultDto);
}
boolean verity = JwtUtil.verify(token);
if (!verity) {
SecurityUserHolder.removeCurrentUser();
resultDto.setError(401, "token已过期");
throw new CustomException(resultDto);
}
//拿到userid
userId = JwtUtil.getUserIdByToken(request).getUserId();
}
TahLoginUser user = loginUserService.queryByUserId(userId);
if (user == null) {
SecurityUserHolder.removeCurrentUser();
resultDto.setError(401, "用户不存在");
throw new CustomException(resultDto);
}
//存入userId
SecurityUserHolder.setCurrentUserId(userId);
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 {}
}
所用到的自定义CustomException文件:
/**
* 自定义异常
*
* @author ruoyi
*/
public class CustomException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private String message;
public CustomException(ResultDto resultDto) {
this.message = resultDto.getMessage();
this.code = resultDto.getCode();
}
@Override
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
接下来展示登录接口:
@PostMapping("/web-login")
@ApiOperation("web端登录")
public String weblogin1(@RequestParam String username, @RequestParam String password) {
try {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return "用户名或密码不能为空";
}
TahLoginUser tbLoginUser = new TahLoginUser();
tbLoginUser.setUserName(username);
tbLoginUser.setPassword(password);
List list = loginUserService.queryUserInfo(tbLoginUser);
if (list == null || list.isEmpty()) {
return "该用戶不存在";
}
if (!list.get(0).getPassword().equals(password)) {
return "密码不正确";
}
TahLoginUser user = list.get(0);
//加密用户信息
String token = JwtUtil.createJwt(user);
return token;
} catch (Exception e) {
return e.getMessage();
}
}
从登录接口获取的token,存储在客户端, 客户端下次发起请求时放在header中Authorization中,
AuthenticationInterceptor拦截器直接解析,解析通过后放行,否则抛出异常。
到此token登录代码部分就全部完成了,下次见!