登录开发
核心逻辑
- 通过mail找数据库记录
- 获取盐,和当前传递的密码就行加密后匹配
- 生成token令牌
JWT
-
JWT
- JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名
- 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息
{ id:888, name:'小D', expire:10000 } funtion 加密(object, appsecret){ xxxx return base64( token); } function 解密(token ,appsecret){ xxxx //成功返回true,失败返回false }
-
优点
- 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
- 存储在客户端,不占用服务端的内存资源
-
缺点
-
token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等
-
如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥
-
-
JWT格式组成 头部、负载、签名
- header+payload+signature
- 头部:主要是描述签名算法
- 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户
- 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token
- header+payload+signature
-
关于jwt客户端存储
- 可以存储在cookie,localstorage和sessionStorage里面
-
controller
@ApiOperation("用户登录")
@PostMapping("login")
public JsonData login(@ApiParam("用户登录对象") @RequestBody UserLoginRequest userLoginRequest) {
JsonData jsonData = userService.login(userLoginRequest);
return jsonData;
}
- service
/**
* 用户登录
* @param userLoginRequest
* @return
*/
@Override
public JsonData login(UserLoginRequest userLoginRequest) {
List<UserDO> userDOList = userMapper.selectList(new QueryWrapper<UserDO>().eq("mail", userLoginRequest.getMail()));
if (userDOList != null && userDOList.size() == 1) {
//已注册
UserDO userDO = userDOList.get(0);
String cryptPwd = Md5Crypt.md5Crypt(userLoginRequest.getPwd().getBytes(), userDO.getSecret());
if (cryptPwd.equals(userDO.getPwd())) {
//生成token
LoginUser loginUser = LoginUser.builder().build();
BeanUtils.copyProperties(userDO,loginUser);
String accessToken = JWTUtil.geneJsonWebToken(loginUser);
return JsonData.buildSuccess(accessToken);
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
}
} else {
//未注册
return JsonData.buildResult(BizCodeEnum.ACCOUNT_UNREGISTER);
}
}
登录拦截器开发
- 解密JWT
- 传递登录用户信息
- attribute传递
- threadLocal传递
- SpringBoot拦截器代码开发
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
String accessToken = request.getHeader("token");
if (accessToken == null) {
accessToken = request.getParameter("token");
}
if (StringUtils.isNotBlank(accessToken)) {
Claims claims = JWTUtil.checkJWT(accessToken);
if (claims == null) {
//告诉登录过期,重新登录
CommonUtil.sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
return false;
}
Long id = Long.valueOf( claims.get("id").toString());
String headImg = (String) claims.get("head_img");
String mail = (String) claims.get("mail");
String name = (String) claims.get("name");
//TODO 用户信息传递
return true;
}
} catch (Exception e) {
log.error("拦截器错误:{}",e);
}
CommonUtil.sendJsonMessage(response, JsonData.buildError("token不存在,重新登录"));
return false;
}
@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 {
}
}
ThreadLocal传递信息
- ThreadLocal
全称thread local variable(线程局部变量)功用非常简单,使用场合主要解决多线程中数据因并发产生不一致问题。
ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
总结起来就是:同个线程共享数据
注意:ThreadLocal不能使用原子类型,只能使用Object类型
- 核心应用场景
ThreadLocal 用作每个线程内需要独立保存信息,方便同个线程的其他方法获取该信息的场景。
每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,比如用户登录令牌解密后的信息传递(还有用户权限信息、从用户系统获取到的用户名、用户ID)
- 配置token解密信息传递
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String accessToken = request.getHeader("token");
if (accessToken == null) {
accessToken = request.getParameter("token");
}
if (StringUtils.isNotBlank(accessToken)) {
Claims claims = JWTUtil.checkJWT(accessToken);
if (claims == null) {
CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
return false;
}
long userId = Long.valueOf(claims.get("id").toString());
String headImg = (String) claims.get("head_img");
String name = (String) claims.get("name");
String mail = (String) claims.get("mail");
LoginUser loginUser = LoginUser
.builder()
.headImg(headImg)
.name(name)
.id(userId)
.mail(mail).build();
//通过ThreadLocal传递信息
threadLocal.set(loginUser);
return true;
}
CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
登录拦截器路径配置和开发
- 拦截器配置
- 拦截路径
- 不拦截路径
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
public LoginInterceptor loginInterceptor(){
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
//拦截的路径
.addPathPatterns("/api/user/*/**","/api/address/*/**")
//排查不拦截的路径
.excludePathPatterns("/api/user/*/send_code","/api/user/*/captcha",
"/api/user/*/register","/api/user/*/login","/api/user/*/upload");
}
}
标签:功能,return,String,登录,accessToken,response,token,public
From: https://www.cnblogs.com/youngleesin/p/16801330.html