前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >25.<Spring博客系统②(实现JWT令牌登录接口+强制登录+获取用户信息+获取作者信息)>

25.<Spring博客系统②(实现JWT令牌登录接口+强制登录+获取用户信息+获取作者信息)>

作者头像
用户11288958
发布2024-11-21 15:12:55
发布2024-11-21 15:12:55
11500
代码可运行
举报
文章被收录于专栏:学习学习
运行总次数:0
代码可运行

PS:带删除线的方法

可以使用但是不建议使用(方法提供方说的) 加上@Deprecated注解。就代表这个方法可以使用,但是不建议使用。也就会带删除线了

前言

对于用户登录。 我们之前的做法都是 1.用户登录,后端验证用户名和密码正确,则存储Session中。把SessionId存储在Cookie中 2.用户再次访问的时候,后端从Cookie中获取SessionId。根据SessionId获取Session。

存在问题: 1.Session丢失:Session存储在服务器内存中,如果服务器重启。那么Session就丢失了 如果用户刚登录成功。服务器进行重启Session丢失。客户端就需要重新登陆了。 2.多机部署的情况 :如果是单机,这台机器只要挂掉(①机器出现问题/②修改代码后服务器重启)。整个服务就挂掉。因此公司通常多机部署。 假如现在有一个客户端,和三个服务器。 ①用户登录,用户请求到了服务器1。服务器1存储Session。 ②用户访问。请求到了服务器2。服务器2根据用户的SessionId查找Session。但找不到。就会告诉用户未登录。这就出现了bug。

解决办法: 1.数据共享,把Session放在同一个地方。比如redis。 2.把数据放在客户端上。(类似身份证,由公安机关发放。用于每个人的身份校验。(我们把这个“身份证”就称作token令牌))

服务器具备生成令牌和验证令牌的能力 使用令牌技术后 1.用户登录,用户发起登录请求, 经过负载均衡, 把请求转给了第一台服务器, 第一台服务器进行账号密码验证, 验证成功后, 生成一个令牌, 并返回给客户端. 2.客户端收到令牌之后, 把令牌存储起来. 可以存储在Cookie中, 也可以存储在其他的存储空间(比如localStorage) 3.查询操作,用户登录成功之后, 携带令牌继续执行查询操作, 比如查询博客列表. 此时请求转发到了第二台机器, 第⼆台机器会先进行权限验证操作. 服务器验证令牌是否有效, 如果有效, 就说明用户已经执行了登录操作, 如果令牌是无效的, 就说明用户之前未执行登录操作.

我们将token。也称作令牌。 令牌的优缺点 优点: 解决了集群环境下的认证问题。 减轻服务器的存储压力(无需在服务器存储) 缺点: 需要自己实现,包括令牌的生成、令牌的传递、令牌的校验。

一、JWT令牌(一种流行的公共令牌技术)

1.1JWT令牌简介

全称: JSON Web Token(官网)

token令牌其实就是一个字符串。用于校验用户身份。

对上⾯部分的信息, 使用Base64Url 进行编码, 合并在一起就是jwt令牌Base64是编码方式,而不是加密方式 。

简介: JWT由三部分组成、每部分中间使用(.)分隔。 ①Header(头部):包括令牌类型(JWT)、以及使用的哈希算法(如HMAC、SHA256、RSA) ②Payload(负载):负载部分是存放有效信息的地方。里面是一些自定义内容比如。{"userId":"123","userName":"zhangsan"}。也可以存在jwt提供的现场字段, 比如exp(过期时间戳)等.此部分不建议存放敏感信息, 因为此部分可以解码还原原始内容. ③Signature(签名):此部分用于防止jwt内容被篡改, 确保安全性。 注:防止被篡改, 而不是防止被解析. WT之所以安全, 就是因为最后的签名. jwt当中任何一个字符被篡改, 整个令牌都会校验失败. 好比我们的身份证, 之所以能标识一个⼈的⾝份, 是因为他不能被篡改, 二不是因为内容加密.(任何人都可以看到身份证的信息, jwt 也是)

1.2JWT令牌的使用

1.2.1引入依赖
代码语言:javascript
代码运行次数:0
复制
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred-->
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
1.2.2使用Jar包中提供的API来完成JWT令牌的生成和校验

1.在Test中创建JWTUtilsTest类 如果需要从Spring容器中获取一些信息。则加上SpringBootTest注解。 而我们这里不用。

定义常量

代码语言:javascript
代码运行次数:0
复制
    //过期时间:设置为一小时后过期
    private final static long EXPIRATION_DATE = 60 * 1000;
    private final static String secretString = "sqmbcvcBPjfvJ4ilRLqbGmHeUaCEwdpv10jSRbCNtH4=";
    private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));//生成key

生成token令牌

代码语言:javascript
代码运行次数:0
复制
    @Test
    public void gentToken(){

        Map<String,Object> claim = new HashMap<>();
        claim.put("id",5);
        claim.put("name","王五");

        String token = Jwts.builder()
                .setClaims(claim) //设置头部和荷负载
                .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE))
                .signWith(key) //设置签名
                .compact();
        System.out.println(token);
    }

随机生成key(标签)

代码语言:javascript
代码运行次数:0
复制
    /**
     * 随机生成Key.(标签)
     */
    @Test
    public void genKey(){

        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String encode = Encoders.BASE64.encode(secretKey.getEncoded());
        System.out.println(encode);
    }

解析Token

代码语言:javascript
代码运行次数:0
复制
    /**
     * 解析Token
     */
    @Test
    public void parseToken(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi546L5LqUIiwiaWQiOjUsImV4cCI6MTczMTQ3MDYxMX0.s-6fv04cAt_8IY-BDScfzzqq-XtuEZ4THuqj_ekw824";
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims body = build.parseClaimsJws(token).getBody();
        System.out.println(body);

    }

二、登录接口的实现(使用JWT令牌技术)

2.1在utils包创建JWTUtils类

代码语言:javascript
代码运行次数:0
复制
@Slf4j
public class JWTUtils {

    //过期时间:设置为一小时后过期
    private final static long EXPIRATION_DATE = 60 * 60 * 1000;
    private final static String secretString = "sqmbcvcBPjfvJ4ilRLqbGmHeUaCEwdpv10jSRbCNtH4=";
    private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));//生成key
    
    /**
     * 生成token令牌
     */
    public static String gentToken(Map<String,Object> claim){
        return Jwts.builder()
                .setClaims(claim) //设置头部和荷负载
                .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE))
                .signWith(key) //设置签名
                .compact();
    }

    /**
     * 随机生成Key.(标签)
     * 由于我们已经生成了因此不需要这个代码了
     */
//    public void genKey(){
//        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
//        String encode = Encoders.BASE64.encode(secretKey.getEncoded());
//        System.out.println(encode);
//    }

    /**
     * 解析Token
     */
    public static Claims parseToken(String token){

        Claims body = null;
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        try {
            body = build.parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            log.info("token过期,校验失败,token:{}",token);
        } catch (Exception e) {
            log.info("token校验失败,token:{}",token);
        }
        return body;
    }

    public static boolean checkToken(String token){
        Claims body = parseToken(token);
        if(body == null){
            return false;
        }
        return true;
    }

2.2实现后端接口

代码语言:javascript
代码运行次数:0
复制
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public Result login(String userName, String password){
        //1.参数校验
        //2.对密码进行校验
        //3.如果校验成功,生成token
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return Result.fail("用户名或密码不能为空!");
        }
        UserInfo userInfo = userService.queryUserByName(userName);
        if(userInfo == null || userInfo.getId() <= 0){
            return Result.fail("用户不存在");
        }
        if(!password.equals(userInfo.getPassword())){
            return Result.fail("密码错误!");
        }
        //密码正确
        Map<String,Object> claim = new HashMap<>();
        claim.put("id",userInfo.getId());
        claim.put("name",userInfo.getUserName());
        return Result.success(JWTUtils.gentToken(claim));

    }

2.3实现前端接口

客户端可以把Token放在哪里呢? 1.Cookie(推荐,但实现较复杂) 2.本地存储(推荐,实现简单)(下面代码使用这个) 3.url中(一般不这样用)

使用

代码语言:javascript
代码运行次数:0
复制
localStorage.setItem("user_token",result.data)

存储token。登录后按f12。点击应用程序,找到本地存储。就能看到我们存储的token了

代码语言:javascript
代码运行次数:0
复制
        function login() {
            $.ajax({
                type: "post",
                url: "user/login",
                data: {
                    "userName": $("#username").val(),
                    "password": $("#password").val()
                },
                success:function(result){
                    if(result.code == 200 && result.code != null){
                        //存储Token
                        localStorage.setItem("user_token",result.data)
                        location.href = "blog_list.html"
                    }else if(result.errMsg == "用户名或密码不能为空!"){
                        alert("用户名或密码不能为空!");
                    }else if(result.errMsg == "用户不存在"){
                        alert("用户不存在");
                    }else if(result.errMsg == "密码错误!"){
                        alert("密码错误!");
                    }
                    //不为200如何处理.....
                    
                }
            });
        }

成功登录!

三、强制登录(拦截器)

1.客户端访问时,携带token(token通常放在Header中) 2.服务器获取token,验证token,如果token校验成功,放行。否则跳转到登录页面。

客户端返回token

在定义拦截器之前。我们需要从客户端获得token。若token存在且校验正确。那么放行。不然进行拦截

代码语言:javascript
代码运行次数:0
复制
$(document).ajaxSend(function(e,xhr,opt){
    var user_token = localStorage.getItem("user_token")
    xhr.setRequestHeader("user_token_header",user_token)
});
//放在common.js中,这时候所有引入common.js的页面都会执行这个代码。

//放在common.js中,这时候所有引入common.js的页面都会执行这个代码。 每当发起ajax请求。就会执行这个方法。ajaxSend 这样我们向后端发送token。将这个变量名命名为user_token_header。

登录状态失效(提示后跳转到登录状态)

放在common.js中,这时候所有引入common.js的页面都会执行这个代码。 每当发起ajax请求。如果请求发生错误。就会执行这个方法。ajaxError

代码语言:javascript
代码运行次数:0
复制
$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
    // 检查是否是未授权错误
    if (jqxhr.status === 401) {
        alert("登录已失效,请重新登录!");
        window.location.href = "/blog_login.html";
    } else {
        // 处理其他错误
        console.error("AJAX 请求出错,状态码:", jqxhr.status);
        alert("请求出错,请稍后再试!");
    }
});

3.1自定义拦截器

1.创建interceptor包。创建LoginInterceptor类,加上@Component注解 2.实现HandlerInterceptor接口, 3.重写preHandle方法{ //1.从handler中获取请求 //2.校验token //3.成功放行 }

代码语言:javascript
代码运行次数:0
复制
/**
 * 用户登录拦截器
 */
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.从handler中获取请求
        //2.校验token
        //3.成功放行
        String userToken = request.getHeader("user_token_header");
        log.info("获得token,token:{}",userToken);
        boolean result = JWTUtils.checkToken(userToken);
        if(result){
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

3.2注册拦截器配置

1.创建config包。创建WebConfig implements WebMvcConfigurer。 2.注册拦截器 3.放入拦截内容以及不拦截的内容

代码语言:javascript
代码运行次数:0
复制
/**
 * 注册拦截器并配置拦截路径
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/**/*.html",
                        "/blog-editormd/**",
                        "/css/**",
                        "/js/**",
                        "/pic/**",
                        "/user/login");
    }
}

1.注意对应关系,这两个名称可以一样。

我们完全可以写成一样的。

2.common.js的引用。必须放在jQuery.min.js的下面

ajaxSend是基于JQuery实现的。因此common.js的引用。必须放在

jQuery.min.js的下面。如果调整顺序。会导致运行不成功。

代码语言:javascript
代码运行次数:0
复制
    <script src="js/jquery.min.js"></script>
    <script src="js/common.js"></script>

四、获得当前用户信息接口

根据token,获得用户信息

后端代码

controller

代码语言:javascript
代码运行次数:0
复制
    @RequestMapping("getUserInfo")
    public UserInfo getUserInfo(HttpServletRequest request){
        //1.获取token,从token中获取Id
        //2.根据Id,获得用户信息
        String user_token = request.getHeader(Constant.USER_TOKEN_HEADER);
        Integer userId = JWTUtils.getUserIdFromToken(user_token);
        if(userId == null || userId<=0){
            return null;
        }
        return userService.queryUserById(userId);
    }

Service

代码语言:javascript
代码运行次数:0
复制
    public UserInfo queryUserById(Integer userId) {
        return userMapper.selectById(userId);
    }

Mapper

代码语言:javascript
代码运行次数:0
复制
    @Select("select *from user where id = #{userId} and delete_flag = 0")
    UserInfo selectById(Integer userId);

测试的时候。注意加上Header信息。也就是token。

我们发现password返回不太合适。因此可以进行处理。

代码语言:javascript
代码运行次数:0
复制
    @RequestMapping("getUserInfo")
    public UserInfo getUserInfo(HttpServletRequest request){
        //1.获取token,从token中获取Id
        //2.根据Id,获得用户信息
        String user_token = request.getHeader(Constant.USER_TOKEN_HEADER);
        Integer userId = JWTUtils.getUserIdFromToken(user_token);
        if(userId == null || userId<=0){
            return null;
        }
        UserInfo userInfo = userService.queryUserById(userId);
        userInfo.setPassword("");
        return userInfo;
    }

企业开发中并不会出现这种情况。

企业中。接口返回的实体类是单独定义的。并不是我们项目中使用的那个,

通过解耦的思想。我们返回的接口数据。并不是Userinfozhong

比如接口中返回用户信息。会重新定义一个比如UserInfoApi。

这个实体类。和UserInfo是对应的。UserInfoApi 是接口需要什么。设置什么

而UserInfo是与数据库对应的。

前端代码
代码语言:javascript
代码运行次数:0
复制
    <div class="container">
        <div class="left">
            <div class="card">
                <img src="pic/doge.jpg" alt="">
                <h3></h3>
                <a href="#">GitHub 地址</a>
代码语言:javascript
代码运行次数:0
复制
        getUserInfo();
        function getUserInfo(){
            $.ajax({
                type: "get",
                url: "/user/getUserInfo",
                success:function(result){
                    if(result.code == 200 && result.data!=null){
                        $(".left .card h3").text(result.data.userName);
                        $(".left .card a").attr("href",result.data.githubUrl);
                    }
                }
            });
        }

代码语言:javascript
代码运行次数:0
复制
 ".left .card a"

.card前面的空格一定要加上

成功显示

五、 获取作者信息接口

根据博客Id。获取作者Id 根据作者Id。获取作者信息。

后端代码

Controller

代码语言:javascript
代码运行次数:0
复制
    @RequestMapping("/getAuthorInfo")
    public UserInfo getAuthorInfo(Integer blogId){
        //1.根据博客Id,获取作者Id
        //2.根据作者Id,获取作者信息
        if(blogId != null && blogId< 1){
            return null;
        }
        UserInfo authorInfoByBlogId = userService.getAuthorInfoByBlogId(blogId);
        authorInfoByBlogId.setPassword("");
        return authorInfoByBlogId;
    }

Service

代码语言:javascript
代码运行次数:0
复制
    public UserInfo getAuthorInfoByBlogId(Integer blogId) {
        //1.根据博客Id,获取作者Id
        //2.根据作者Id,获取作者信息
        BlogInfo blogInfo = blogMapper.selectById(blogId);
        if(blogInfo ==null || blogInfo.getUserId()<1){
            return null;
        }
        return userMapper.selectById(blogInfo.getUserId());
    }

Mapper

代码语言:javascript
代码运行次数:0
复制
    @Select("select *from user where id = #{userId} and delete_flag = 0")
    UserInfo selectById(Integer userId);

使用Postman测试一下

前端代码
代码语言:javascript
代码运行次数:0
复制
    <div class="container">
        <div class="left">
            <div class="card">
                <img src="pic/doge.jpg" alt="">
                <h3></h3>
                <a href="#">GitHub 地址</a>
代码语言:javascript
代码运行次数:0
复制
        //显示博客作者信息
/*         var userUrl = "/user/getAuthorInfo" + location.search;
        getUserInfo(userUrl); */
        getUserInfo();
        function getUserInfo(){
            $.ajax({
                type: "get",
                url: "/user/getAuthorInfo"+location.search,
                success:function(result){
                    if(result.code == 200 && result.data!=null){
                        $(".left .card h3").text(result.data.userName);
                        $(".left .card a").attr("href",result.data.githubUrl);
                    }
                }
            });
        }

最终成功显示

代码整合

我们发现四、五这两个接口的前端代码几乎一样。

因此我们可以把这个代码放在common.js文件中。

代码语言:javascript
代码运行次数:0
复制
function getUserInfo(url){
    $.ajax({
        type: "get",
        url: url,
        success:function(result){
            if(result.code == 200 && result.data!=null){
                $(".left .card h3").text(result.data.userName);
                $(".left .card a").attr("href",result.data.githubUrl);
            }
        }
    });
}

接着在前端响应的接口处调用这个方法。传入参数就行了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PS:带删除线的方法
  • 前言
  • 一、JWT令牌(一种流行的公共令牌技术)
    • 1.1JWT令牌简介
    • 1.2JWT令牌的使用
      • 1.2.1引入依赖
      • 1.2.2使用Jar包中提供的API来完成JWT令牌的生成和校验
  • 二、登录接口的实现(使用JWT令牌技术)
    • 2.1在utils包创建JWTUtils类
    • 2.2实现后端接口
    • 2.3实现前端接口
  • 三、强制登录(拦截器)
    • 客户端返回token
    • 登录状态失效(提示后跳转到登录状态)
    • 3.1自定义拦截器
    • 3.2注册拦截器配置
  • 四、获得当前用户信息接口
    • 后端代码
    • 前端代码
  • 五、 获取作者信息接口
    • 后端代码
    • 前端代码
    • 代码整合
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档