首页 > 其他分享 >登录业务优化

登录业务优化

时间:2024-07-27 14:55:30浏览次数:9  
标签:return String 登录 业务 token claims import 优化 public

登录认证

如果不进行登录认证(对访问用户的状态进行检查),就会出现越过登录直接访问数据的bug,为了应对这种情况,我们使用JWT令牌进行验证用户登录状态。

令牌是一个字符串:

  1. 承载数据,减少访问数据库次数

  2. 防篡改

JWT令牌:

全称:JSON Web Token(https:lwt.iol) 定义了一种简洁的、自包含的格式,用于通信双方以jso数据格式安全的传输信息。 组成:

◆第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}

◆第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{id":"1","username'":"Tom"),注意:第二部分不要放重要信息,如(用户密码)。

◆第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、.payload,并加入指定秘钥,通过指定签名算法计算而来。

使用:

1.在pom依赖中引入坐标:

<!--      java-jwt依赖-->
      <dependency>
          <groupId>com.auth0</groupId>
          <artifactId>java-jwt</artifactId>
          <version>4.4.0</version>
      </dependency>

2.设置令牌:

@Test
    public void testGen(){
        Map<String ,Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("username","张三");
        //生成jwt的代码
        String token = JWT.create()
                .withClaim("user",claims)//添加载荷
                .withExpiresAt(new Date(System.currentTimeMillis()+ 1000*60*60*3))//设置令牌过期时间
                .sign(Algorithm.HMAC256("itheima"));//指定算法,配置密钥
        System.out.println(token);
    }

3.验证令牌:

@Test
    public void testParse(){
        String token ="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
                ".eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3MjExMjk3OTR9" +
                ".l9HW5XNmtB9iO95sBPTIi0T5CvuSOLToPoggfAkZx1s";
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("itheima")).build();
​
        DecodedJWT decodedJWT = jwtVerifier.verify(token);//验证token,生成一个解析后的JWT对象
​
        Map<String, Claim> claims = decodedJWT.getClaims();
        System.out.println(claims.get("user"));
        //改了任何一段都会验证失败
        //令牌过期也会验证失败
    }

将两段打包到JwtUtil:

​
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
​
import java.util.Date;
import java.util.Map;
​
public class JwtUtil {
​
    private static final String KEY = "itheima";
    
    //接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256(KEY));
    }
​
    //接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }
​
}

在login方法中加上生成JWT令牌的流程:

@PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password){
        //根据用户名查询用户
        User loginUser = userService.findByUserName(username);
        //判断用户是否存在
        if(loginUser==null){
            return Result.error("用户名错误");
        }
        //判断密码是否正确
        if(Md5Util.getMD5String(password).equals(loginUser.getPassword())){
            Map<String,Object> claims = new HashMap<>();
            claims.put("id",loginUser.getId());
            claims.put("username",loginUser.getUsername());
            String token = JwtUtil.genToken(claims);
​
            return Result.success(token);
        }
        return Result.error("密码错误");
    }

验证代码:

@RestController
@RequestMapping("/article")
public class ArticleController {
    @GetMapping("/list")
    public Result<String> list(@RequestHeader(name = "Authorization") String token, HttpServletResponse response){
        //验证token
        try {
            Map<String,Object> claims = JwtUtil.parseToken(token);
            return Result.success("所有的文章");
        } catch (Exception e) {
            //http响应状态码为401
            response.setStatus(401);
            return Result.error(("未登录"));
        }
        
    }
}

我们可以通过拦截器来进行验证

流程:

1.写一个拦截器:

import com.atguigu.pojo.Result;
import com.atguigu.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
​
import java.util.Map;
​
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //令牌验证
        String token = request.getHeader("Authorization");
        try {
            Map<String,Object> claims = JwtUtil.parseToken(token);
            //放行
            return true;
        } catch (Exception e) {
            //http响应状态码为401
            response.setStatus(401);
            //不放行
            return false;
        }
​
    }
}

2.注册拦截器,要过滤登录和注册接口

@Configuration
public class WebConfig implements WebMvcConfigurer {
​
    @Autowired
    private LoginInterceptor loginInterceptor;
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录接口和注册接口不拦截
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}

这样之后,业务中的验证令牌流程就可以注释掉了。

@RestController
@RequestMapping("/article")
public class ArticleController {
    @GetMapping("/list")
    public Result<String> list(/*@RequestHeader(name = "Authorization") String token, HttpServletResponse response*/){
        //验证token
//        try {
//            Map<String,Object> claims = JwtUtil.parseToken(token);
//            return Result.success("所有的文章");
//        } catch (Exception e) {
//            //http响应状态码为401
//            response.setStatus(401);
//            return Result.error(("未登录"));
//        }
        return Result.success("所有的文章");
    }
}

数据返回

忽略密码

在返回对象时转为json格式会将所有属性数据列出,我们要将密码隐藏,就要在实体类的属性上加上注解:

@Data
public class User {
    private Integer id;//主键ID
    private String username;//用户名
    @JsonIgnore//让springmvc把当前对象转换为json对象时,忽略这个字段
    private String password;//密码
    private String nickname;//昵称
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}
数据库字段格式(驼峰)

在application.yml中配置

mybatis:
  configuration:
    map-underscore-to-camel-case: true #开启驼峰命名和下划线命名的自动转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

用户信息存储

我们每次要用到当前用户信息时都要进行传参,如果有好几个类都要这个信息,每个都传太过麻烦。还有一个问题就是在同一个线程中共享数据,让多个service、Controller、dao拿到。所以我们使用ThreadLocal来保存用户信息。当登录时,拦截器顺便把用户信息存到ThreadLocal中,我们要用的时候直接调用ThreadLocal就行。步骤如下:

1.课程中提供了ThreadLocalUtil工具类,我们直接导入utils包中:

import java.util.HashMap;
import java.util.Map;
​
/**
 * ThreadLocal 工具类
 */
@SuppressWarnings("all")
public class ThreadLocalUtil {
    //提供ThreadLocal对象,
    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
​
    //根据键获取值
    public static <T> T get(){
        return (T) THREAD_LOCAL.get();
    }
    
    //存储键值对
    public static void set(Object value){
        THREAD_LOCAL.set(value);
    }
​
​
    //清除ThreadLocal 防止内存泄漏
    public static void remove(){
        THREAD_LOCAL.remove();
    }
}

2.在拦截器中添加ThreadLocal存储,还要重写afterCompletion方法添加remove方法防止内存泄漏(THREAD_LOCAL 是全局唯一变量,如果不清除会一直驻留):

import com.atguigu.pojo.Result;
import com.atguigu.utils.JwtUtil;
import com.atguigu.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
​
import java.util.Map;
​
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //令牌验证
        String token = request.getHeader("Authorization");
        try {
            Map<String,Object> claims = JwtUtil.parseToken(token);
            
            //把业务数据存储到ThreadLocal中
            ThreadLocalUtil.set(claims);
            
            //放行
            return true;
        } catch (Exception e) {
            //http响应状态码为401
            response.setStatus(401);
            //不放行
            return false;
        }
​
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清空ThreadLocal中的数据
        ThreadLocalUtil.remove();
    }
}

3.在业务中使用,用map取出数据:

@GetMapping("/userInfo")
    public Result<User> userInfo(/*@RequestHeader(name = "Authorization") String token*/){
        //根据用户名查询用户
//        Map<String,Object> map = JwtUtil.parseToken(token);
//        String username = (String) map.get("username");
        
        Map<String,Object> map = ThreadLocalUtil.get();
        String username = (String) map.get("username");
        User user = userService.findByUserName(username);
        return Result.success(user);
    }

后续可以进一步通过redis进行优化:

登录优化(Redis令牌失效)-CSDN博客

标签:return,String,登录,业务,token,claims,import,优化,public
From: https://blog.csdn.net/m0_63624728/article/details/140720973

相关文章

  • 业务场景---Token无感刷新
    业务场景描述假设用户正在填写一个复杂的表单,由于表单内容繁多,用户花费了很长时间才填完。这时,如果Token已经过期,系统会让用户重新登录,这种体验显然是非常糟糕的。为了避免这种情况,我们需要在Token即将过期或已经过期时,自动刷新Token,而不影响用户正在进行的操作。技术实现思路......
  • 19.延迟队列优化
    问题前面所讲的延迟队列有一个不足之处,比如现在有一个需求需要延迟半个小时的消息,那么就只有添加一个新的队列。那就意味着,每新增一个不同时间需求,就会新创建一个队列。解决方案应该讲消息的时间不要跟队列绑定,应该交给消息的生产者,由发送消息来指定延迟时间。这样就可以定......
  • Vue3 - 最新详细实现网站接入Google谷歌授权登录配置流程及示例代码教程,手机移动端、p
    前言如果您需要Vue2版本,请访问这篇文章。在vue3|nuxt3网站开发中,详解实现vue3接入新版google谷歌快捷登录教程,电脑PC网站、手机网站集成谷歌授权登录服务及拿到用户个人信息头像邮箱等,国内第三方web站点使用google账号登陆及授权重定向,提供详细的本地调试方法以......
  • 秒杀优化-基于阻塞队列实现秒杀优化
    秒杀优化VoucherOrderServiceImpl修改下单动作,现在我们去下单时,是通过lua表达式去原子执行判断逻辑,如果判断我出来不为0,则要么是库存不足,要么是重复下单,返回错误信息,如果是0,则把下单的逻辑保存到队列中去,然后异步执行@Slf4j@ServicepublicclassVoucherOrderServiceImplex......
  • 【独家首发】Matlab实现凌日优化算法TSOA优化Transformer-BiLSTM实现负荷数据回归预测
    %假设您有负荷数据load_data和相应的回归标签regression_labels%1.数据预处理%在这一步中,您需要对负荷数据进行适当的预处理,例如归一化、序列化等操作%2.划分数据集为训练集和测试集%这里假设您将数据划分为train_data,train_labels,test_data,test_label......
  • 【独家首发】Matlab实现粒子群优化算法PSO优化Transformer-BiLSTM实现负荷数据回归预
    %假设您有负荷数据load_data和相应的回归标签regression_labels%1.数据预处理%在这一步中,您需要对负荷数据进行适当的预处理,例如归一化、序列化等操作%2.划分数据集为训练集和测试集%这里假设您将数据划分为train_data,train_labels,test_data,test_label......
  • 面试官:说说单点登录都是怎么实现的?
     在数字化时代,用户账户安全和便捷体验成为了众多互联网产品设计的重要考量。       而“单点登录”(SingleSign-On,SSO)作为提升用户体验、简化登录流程的关键技术,已经成为各类企业应用的标准配置。       那么,当你在面试现场被问到‘单点登录是如何实现的?’,......
  • Kylin查询优化器深度解析:大数据查询性能的加速引擎
    Kylin查询优化器深度解析:大数据查询性能的加速引擎ApacheKylin是一个开源的分布式分析引擎,专为Hadoop和Spark平台上的大数据集提供快速的SQL查询能力。Kylin的核心优势之一是其强大的查询优化器,它能够智能地优化查询计划,显著提高查询性能。本文将深入探讨Kylin的查询优化......
  • 斜率优化
    斜率优化[HNOI2008]玩具装箱状态转移方程:设A为\(sum_i+i\),B为\(sum_j+j+L+1\)简化可得:\[f_i=min(f_i,f_j+A^2-2AB+B^2)\]稍微分解一下,有:\[f_i=f_j+A^2-2AB+B^2\\f_j+B^2=2AB+f_i-A^2\]设\(f_j+B^2\)为点\(y\),\(2A\)为\(k\),\(B\)为\(x\),\(f_i-A^2\)为\(b......
  • 从零开始使用GPT-4o mini:配置、微调与优化
    引言随着人工智能技术的不断发展,OpenAI推出的GPT-4omini模型吸引了众多开发者的关注。作为一种更经济实惠且高效的语言模型,GPT-4omini在多模态推理和成本效益方面表现出色。本篇文章旨在分享使用GPT-4omini的经验,从初始设置到性能优化,涵盖各个应用场景,并提供实际的开发建议......