阅读 263

SpringBoot+Shiro+JWT+Redis+Mybatis-plus 前后端分离实战项目

SpringBoot+Shiro+JWT+Redis+Mybatis-plus 前后端分离实战项目

文章目录

前言

JWT学习总结

什么是JWT?

JWT的结构?

JWT整合SpringBoot的依赖

JWT核心代码配置

JWTUtil

JWT拦截器

全局拦截器配置

登陆成功的时候生成JWT token 返回给前端

前端如何利用 JWT token

项目源码(CodeChina平台)

踩过的坑

项目运行

总结

前言

这篇博客是在我上篇发的 SpringBoot+Shiro+Redis+Mybatis-plus 实战项目 之上添加了JWT认证和前后端分离,所以这篇博客重点是贴出 JWT 学习总结的代码,希望可以帮助到大家!



JWT学习总结

什么是JWT?

JWT 全称就是 JSON WEB TOKEN,可以看作是一个获得请求资格的令牌,我们有了这个令牌,才可以访问到网站的大部分功能(接口)。


JWT的结构?

JWT 分成三段


header

header 里面主要是放 加密的算法名和类型

payload(负载)

payload 主要是放一些我们想传递给 token 中保存的用户部分信息字段,比如 用户名 用户ID等

sign(核心安全信息)

sign 是JWT 的核心安全信息,它其实就是一个字符串,在公司中这个字符串一定不能被暴露出去。

以 . 号连接,有点像 IP 地址的格式

例如: header.payload.sign




JWT整合SpringBoot的依赖

 <dependency>

     <groupId>com.auth0</groupId>

     <artifactId>java-jwt</artifactId>

     <version>3.15.0</version>

 </dependency>


1

2

3

4

5

6

JWT核心代码配置

JWTUtil

package com.jmu.shiro_demo.utils;


import com.auth0.jwt.JWT;

import com.auth0.jwt.JWTCreator;

import com.auth0.jwt.algorithms.Algorithm;

import com.auth0.jwt.interfaces.DecodedJWT;


import java.security.Signature;

import java.util.Calendar;

import java.util.Map;


public class JWTutil {


    private static final String SIGN = "jiachengren"; //JWT 签名

    private static final int DEFAULT_JWT_EXPIRE_DAYS = 7; //默认JWT过期天数


    public static String getToken(Map<String,String> map) {

        JWTCreator.Builder builder = JWT.create();

        Calendar instance = Calendar.getInstance();

        instance.add(Calendar.DATE,DEFAULT_JWT_EXPIRE_DAYS);

        //1.header 默认

        //2.payload 遍历 map

       map.forEach((k,v) -> {

            builder.withClaim(k,v);

        });

        //3.设置过期时间和签名后生成 token

        String token = builder

                .withExpiresAt(instance.getTime())

                .sign(Algorithm.HMAC256(SIGN));

        return token;

    }


    public static DecodedJWT verify(String token){

        return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);

    }


}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

JWT拦截器

package com.jmu.shiro_demo.intercepetor;


import com.auth0.jwt.exceptions.AlgorithmMismatchException;

import com.auth0.jwt.exceptions.SignatureVerificationException;

import com.auth0.jwt.exceptions.TokenExpiredException;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.jmu.shiro_demo.utils.JWTutil;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.util.HashMap;

import java.util.Map;


public class JWTIntercepetor implements HandlerInterceptor {


    private final String TOKEN_NAME = "token";


    //JWT拦截器,所有请求都被这个拦截器拦截,校验header中的token,token校验通过再放行

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.token一般存放在 header 中,所以从 request.getHeader()中获取 token

        String token = request.getHeader(TOKEN_NAME);

        Map<String,Object> map = new HashMap<String, Object>();

        try {

            JWTutil.verify(token);

            map.put("state",true);

            return true;

        } catch (AlgorithmMismatchException e) {

            map.put("msg","JWT算法不匹配!");

        } catch (SignatureVerificationException e) {

            map.put("msg","JWT签名不匹配!");

        } catch (TokenExpiredException e) {

            map.put("msg","token(用户信息)已经过期,请重新登录!");

        } catch (Exception e) {

            map.put("msg","token无效!");

        }

        map.put("state",false);

        response.setContentType("application/json");

        String msg = new ObjectMapper().writeValueAsString(map);

        response.getWriter().println(msg);

        return false;

    }


}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

全局拦截器配置

package com.jmu.shiro_demo.config;


import com.jmu.shiro_demo.intercepetor.JWTIntercepetor;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration

public class IntercepetorConfig implements WebMvcConfigurer {


    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new JWTIntercepetor())

                .addPathPatterns("/**") //拦截所有请求

                .excludePathPatterns("/logout","/js/**","/index","/getAuthCode","/login","/toLogin","/user/**"); // 放行 登陆请求 下面的所有请求

    }


}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

登陆成功的时候生成JWT token 返回给前端

package com.jmu.shiro_demo.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import com.jmu.shiro_demo.entity.Permission;

import com.jmu.shiro_demo.entity.Role;

import com.jmu.shiro_demo.entity.User;

import com.jmu.shiro_demo.mapper.UserMapper;

import com.jmu.shiro_demo.service.RoleService;

import com.jmu.shiro_demo.service.UserService;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.jmu.shiro_demo.utils.JWTutil;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authc.IncorrectCredentialsException;

import org.apache.shiro.authc.UnknownAccountException;

import org.apache.shiro.authc.UsernamePasswordToken;

import org.apache.shiro.subject.Subject;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.ui.Model;


import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import java.util.HashMap;

import java.util.LinkedList;

import java.util.List;

import java.util.Map;


/**

 * <p>

 *  服务实现类

 * </p>

 *

 * @author ${author}

 * @since 2021-04-20

 */

@Service

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


    @Autowired

    private RoleService roleService;


    @Override

    public String login(String username, String password, String code, HttpSession session, HttpServletResponse response, Model model) {

        //在校验登陆之前先检验验证码的正确性

        String realAuthCode = (String) session.getAttribute("code");

        if(!realAuthCode.equalsIgnoreCase(code)) {

            model.addAttribute("msg","验证码错误!");

            return "login";

        }

        //1.获取 Subject

        Subject subject = SecurityUtils.getSubject();

        //2.封装 token

        UsernamePasswordToken shiroToken = new UsernamePasswordToken(username, password);

        //3.调用 subject.login(token) 方法

        try {

            subject.login(shiroToken);

            //登录成功之后返回 token 给客户端

            Map<String,String> map = new HashMap<String, String>();

            map.put("username",username);

            String token = JWTutil.getToken(map);  //登陆成功,生成JWT token

            response.setHeader("token",token); // 将 token 设置在 header 里面

            model.addAttribute("token",token);

        }catch (UnknownAccountException e1) {

            //用户名不存在

            model.addAttribute("msg","用户名不存在!");

            return "/login";

        }catch (IncorrectCredentialsException e2) {

            model.addAttribute("msg","密码错误!");

            return "/login";

        }

        return "/index";

    }


    @Override

    public User getUserByUserName(String username) {

        QueryWrapper<User> wrapper = new QueryWrapper<User>();

        wrapper.eq("username",username);

        return this.baseMapper.selectOne(wrapper);

    }


    @Override

    public List<Permission> getUserPermissionsByUserId(Integer userId) {

        List<Role> roles = getUserRoleByUserId(userId);

        List<Permission> permissions = new LinkedList<Permission>();

        roles.stream().forEach(role -> {

            permissions.addAll(roleService.getRolePermissionsByRoleId(role.getRoleid()));

        });

        return permissions;

    }


    @Override

    public List<Role> getUserRoleByUserId(Integer userId) {

        return this.baseMapper.getUserRoleByUserId(userId);

    }


    @Override

    public String logout() {

        SecurityUtils.getSubject().logout();

        return "redirect:/index";

    }


}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

前端如何利用 JWT token

这是 前后端分离的登陆界面, 登陆成功后在 success 里面进行保存 token 和跳转页面


<!DOCTYPE html>

<html xmlns:th="https://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>Login</title>

    <script th:src="@{/js/jquery-3.6.0.slim.min.js}"></script>

</head>

<body>

<h3 style="color: red" th:text="${msg}"/>

<div>

    账号: <input type="text" name="username" id="username"> <br/>

    密码: <input type="password" name="password" id="password"> <br/>

    验证码: <input type="text" name="code" id="code"><img id="changeImg" src="/getAuthCode" alt=""><br/>

    <button id="loginBtn">登陆</button>

</div>

<script>


    $(function () {

        //点击更换验证码   拼接随机数

        $("#changeImg").click(function(){

            $("#changeImg").attr('src',"/getAuthCode?d="+Math.random());

        });

        $('#loginBtn').click(function () {

            let username = $('#username').val()

            let password = $('#password').val()

            let code = $('#code').val()

            $.ajax({

                url:"/user/login",

                data:{"username":username,"password":password,"code":code},

                method:"POST",

                success: function (data) {

                    window.localStorage.setItem("token",data.token) //将token设置在页面的 localStorage 中

                    window.location = data.url //跳转页面

                    console.log(data)

                },

                error: function () {

                    alert("error!")

                }

            })

        })

    })

</script>

</body>

</html>


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

项目源码(CodeChina平台)

shiro_demo


踩过的坑

前端报错 $.ajax is not function

解决:将压缩版本的 jquery 更换成 非压缩的

jquery获取不到 文本框中的值

解决:使用 $(’#id’).val() 函数

js被拦截

解决:所有的拦截器的时候放行 /js/**

项目运行

这里主要演示前后端分离下JWT的效果,shiro 和 redis 的效果在上篇实战博客中有发

演示说明,登陆的时候会保存一个 jwt token 到 客户端(浏览器)的localstorage 中,然后每次发起一个后台请求的时候,在 请求头(header)中就会携带这个 token, 后台如果校验这个 token 通过,就响应请求,在这里我的响应请求的方式,就是简单的成功跳转页面。




小彩蛋: 写这篇博客文章的时候,博主是一边听赵雷(一个低调优秀的民谣歌手)的《鼓楼》这首民谣,真的好听!推荐大家也去听(不是广告????)哈哈哈哈????????!


总结

JWT 是一种用于减缓认证处理业务过程中的繁琐步骤,提高效率的技术,使用起来也不会很难,但是需要好好理解为什么要用 JWT,为什么会出现 token 令牌机制,最后留个问题给大家思考:JWTtoken需要保存在数据库中吗?有啥想法,都可以在下面评论!希望学到的童鞋可以给博主个三连!????????


坚持分享,坚持原创,喜欢博主的靓仔靓女们可以看看博主的首页博客!

您的点赞与收藏是我分享博客的最大赞赏!

博主博客地址: https://blog.csdn.net/weixin_43967679

————————————————

版权声明:本文为CSDN博主「jiachengren」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_43967679/article/details/116107119


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐