前言
1、用户登录
1.1 JWT
JSON Web Token简称JWT,用于对应用程序上用户进行身份验证的标记。使用 JWTS 之后不需要保存用户的 cookie 或其他session数据,同时可保证应用程序的安全。
JWT是经过加密处理与校验处理的字符串,形式为:A.B.C
- –A由JWT头部信息header加密得到
- –B由JWT用到的身份验证信息JSON数据加密得到
- –C由A和B加密得到,是校验部分
- –官方测试网站: https://jwt.io/
@Test
public void testCreateToken() {
//生成token
//1、准备数据
Map map = new HashMap();
map.put("id",1);
map.put("mobile","13800138000");
//2、使用JWT工具类生成token
long now = System.currentTimeMillis();
String token = Jwts.builder()
.signWith(SignatureAlgorithm.HS512, "itcast") //指定加密算法
.setClaims(map)//指定写入的数据
.setExpiration(new Date(now + 5000)) //设置失效时间
.compact();
System.out.println(token);
}
@Test
public void testParseToken() {
String token = "eyJhbGciOiJIUzUxMiJ9.e1yJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE2MTgzOTA4NzB9.WhZfFSJNEZJGeZAHqMaVIslvzvkFzQ1FCLCULSaR-yZYprbzgmuxNaSr3oW__zRFkBCnNRGllzaKb0ZJujs1GA";
//解析token
try {
Claims claims = Jwts.parser()
.setSigningKey("itcast")
.parseClaimsJws(token)
.getBody();
Object id = claims.get("id");
Object mobile = claims.get("mobile");
System.out.println(id + "--" + mobile);
}catch (ExpiredJwtException e) {
System.out.println("token已过期");
}catch (SignatureException e) {
System.out.println("token不合法");
}
}
JWT工具类
public class JwtUtils {
// TOKEN的有效期1小时(S)
private static final int TOKEN_TIME_OUT = 3_600;
// 加密KEY
private static final String TOKEN_SECRET = "itcast";
// 生成Token
public static String getToken(Map params){
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
.addClaims(params)
.compact();
}
/**
* 获取Token中的claims信息
*/
public static Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(TOKEN_SECRET)
.parseClaimsJws(token).getBody();
}
/**
* 是否有效 true-有效,false-失效
*/
public static boolean verifyToken(String token) {
if(StringUtils.isEmpty(token)) {
return false;
}
try {
Claims claims = Jwts.parser()
.setSigningKey("itcast")
.parseClaimsJws(token)
.getBody();
}catch (Exception e) {
return false;
}
return true;
}
}
ThreadLocal
-
线程内部的存储类,赋予了线程存储数据的能力。
-
线程内调用的方法都可以从ThreadLocal中获取同一个对象。
-
多个线程中ThreadLocal数据相互隔离
2.1 代码实现
2.1.1 ThreadLocal工具类
定义ThreadLocal工具类,仅需要调用set方法即可将数据存入ThreadLocal中
package com.tanhua.server.interceptor;
import com.tanhua.model.domain.User;
/**
* @author Administrator
*/
public class UserHolder {
private static ThreadLocal<User> tl = new ThreadLocal<User>();
/**
* 保存数据到线程
*/
public static void set(User user) {
tl.set(user);
}
/**
* 获取线程中的用户信息
*/
public static User get() {
return tl.get();
}
/**
* 从当前线程,获取用户对象的id
*/
public static Long getUserId() {
if (tl.get() == null) {
return null;
}
return tl.get().getId();
}
/**
* 从当前线程,获取用户对象的手机号码
*/
public static String getMobile() {
if (tl.get() == null) {
return null;
}
return tl.get().getMobile();
}
/**
* 移除线程中数据
*/
public static void remove() {
tl.remove();
}
}
2.2.2 定义拦截器
定义拦截器,在前置拦截方法preHandle中解析token并验证有效性,如果失效返回状态码401。如果有效,解析User对象,存入ThreadLocal中
package com.tanhua.server.interceptor;
import com.tanhua.model.domain.User;
import com.tanhua.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Administrator
*/
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1、获取请求头
String token = request.getHeader("Authorization");
//2、使用工具类,判断token是否有效
boolean verifyToken = JwtUtils.verifyToken(token);
//3、如果token失效,返回状态码401,拦截
if(!verifyToken) {
response.setStatus(401);
return false;
}
//4、如果token正常可用,放行
//解析token,获取id和手机号码,
Claims claims = JwtUtils.getClaims(token);
String mobile = (String) claims.get("mobile");
Integer id = (Integer) claims.get("id");
//构造User对象,存入Threadlocal
User user = new User();
user.setId(Long.valueOf(id));
user.setMobile(mobile);
UserHolder.set(user);
return true;
}
//清空
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.remove();
}
}
2.2.3 注册拦截器
拦截器需要注册到MVC容器中
/**
* @author Administrator
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login", "/user/loginVerification");
}
}
package com.tanhua.server.controller;
import com.tanhua.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class LoginController {
@Autowired
private UserService userService;
/**
* 获取登录验证
* 请求参数:phone(Map)
* 响应:void
*ResponseEntity
* @return
*/
@PostMapping("/login")
public ResponseEntity login(@RequestBody Map map){
String phone = (String) map.get("phone");
userService.sendMsg(phone);
// return ResponseEntity.status(500).body("出错了");
return ResponseEntity.ok(null);//正常返回状态码200
}
/**
*检验登录
* /user/loginVerification
* phone,verificationCode
*/
@PostMapping("/loginVerification")
public ResponseEntity loginVerification(@RequestBody Map map){
//1.调用map集合获取请求参数
String phone = (String) map.get("phone");
String code = (String) map.get("verificationCode");
//2.调用userService完成用户登录
Map retMap = userService.loginVerification(phone,code);
//3.构造返回
return ResponseEntity.ok(retMap);
}
}
package com.tanhua.server.service;
import com.tanhua.autoconfig.template.SmsTemplate;
import com.tanhua.commons.utils.JwtUtils;
import com.tanhua.dubbo.api.UserApi;
import com.tanhua.model.domain.User;
import com.tanhua.model.vo.ErrorResult;
import com.tanhua.server.exception.BusinessException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
@Autowired
private SmsTemplate smsTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@DubboReference
private UserApi userApi;
/**
* 发送短信验证码
* @param phone
*/
public void sendMsg(String phone) {
//1.随机生成6位数字
// String code = RandomStringUtils.randomNumeric(6);
String code = "123456";
//2.调用templat对象,发送手机短信
// smsTemplate.sendSms(phone,code);
//3.将验证码存入到redis
redisTemplate.opsForValue().set("CHECK_CODE" + phone,code, Duration.ofMinutes(5));
}
/**
* 验证登录
* @param phone
* @param code
* @return
*/
public Map loginVerification(String phone, String code) {
//1.从redis中获取下发的验证码
String redisCode = redisTemplate.opsForValue().get("CHECK_CODE" + phone);
//2.对验证码进行校验(验证码是否存在,是否和输入的验证码是否一致)
if (StringUtils.isEmpty(redisCode) || !redisCode.equals(code)){
//验证码无效
throw new BusinessException(ErrorResult.loginError());
}
//3.删除redis中的验证码
redisTemplate.delete("CHECK_CODE"+phone);
//4.通过手机号码查询用户
User user = userApi.findByMobile(phone);
boolean isNew = false;
//5.如果用户不存在,创建用户保存到数据库中
if (user == null){
user = new User();
user.setMobile(phone);
user.setPassword(DigestUtils.md5Hex("123456"));
Long userId = userApi.save(user);
user.setId(userId);
isNew = true;
}
//6.通过JWT生成token(存入id和手机号码)
Map tokenMap = new HashMap();
tokenMap.put("id",user.getId());
tokenMap.put("mobile",phone);
String token = JwtUtils.getToken(tokenMap);
//7.构造返回值
Map retMap = new HashMap();
retMap.put("token",token);
retMap.put("isNew",isNew);
return retMap;
}
}