简单登录功能(一)token的使用

目录

1.token介绍

2.前端代码

3.后端代码


1.token介绍

前端使用的layui,通过ajax异步提交表单来实现登录,刚好学习巩固一下ajax,后端主要学习一下token的使用。

token是什么?

百度百科:Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

为什么使用token?

主要减轻服务器压力,用户登录生成一次token后,发送一份给客户端存放在localStorage或cookie中,请求时放在请求头携带。

服务端token存放在哪?

session:使用起来简单,分布式情况存在同步问题

redic:系统复杂性就要提高了 ,设置时间自动销毁,可以实现单点登录

不存储:jwt验证,项目小建议使用,无法实现自动销毁

token验证流程

  1.  客户端使用用户名跟密码请求登录;
  2. 通过ajax向后端发送请求;
  3. 服务端收到请求,去验证用户名与密码;
  4. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端;
  5. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里;
  6. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Toke(不管是你自己写的)它都要通过ajax向后端发送请求,这时服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

2.前端代码

(1)首先是验证码功能,验证码功能可以看的这篇文章学习https://blog.csdn.net/qing_er_/article/details/125913325?spm=1001.2014.3001.5502w​​​​​​​w

验证码代码

var code; //在全局定义验证码
function createCode() {
            code = "";
            var random = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
                'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; //随机数
            for (var i = 0; i < 4; i++) { //循环操作
                var index = Math.floor(Math.random() * 36); //取得随机数的索引(0~35)
                code += random[index]; //根据索引取得随机数加到code上
            }
            $("#code").val(code) //把code值赋给验证码
        }

 需要注意的就是验证码刷新的地方,加载页面,点击验证码,登录失败都要刷新验证码。

(2)表单异步提交,这里之前每次点击都会刷新页面,可以通过onSubmit='return false'把提交禁掉,直接使用点击事件异步提交,在表单内数据很多的情况下,下面ajax提交的数据可以直接序列化表单数据提交,$("form").serialize(),还有之前直接把ajax换成post或get不知道为啥会报415错误。提交表单数据后,后端生成一个token字符串,具体要存入token哪些信息自己根据实际情况决定,返回的token可以存在localstorage和cookie中,这里存放在localstorage中,每次请求携带token。

<!--引入form模块-->
        layui.use(['form', 'layer', 'jquery'], function () {

            $ = layui.jquery;
            var form = layui.form,
                layer = layui.layer;

            // 表单提交
            $("#submit").click(function () {
                can_sumbit = true;//重新点击后需要重新赋值,否则一直为false
                // 表单校验
                if ($("#username").val() == "") {
                    layer.msg("用户名不能为空");
                    can_sumbit = false;
                } else if (can_sumbit && $("#password").val() == "") {
                    layer.msg("密码不能为空");
                    can_sumbit = false;
                } else if (can_sumbit && $('#ctl00_txtcode').val() == "") {
                    layer.msg("请填写验证码");
                    can_sumbit = false;
                } else if (can_sumbit && $("#ctl00_txtcode").val().toUpperCase() != $("#code").val().toString()) {//将输入验证码转大写,实际验证码的类型转为字符串类型
                    createCode();//刷新验证码
                    layer.msg("验证码不正确");
                    can_sumbit = false;
                }
            //异步提交表单数据,分两个请求,第一个获取后端传入的token并存入本地,第二请求头携带token登录,第二没有直接登录感觉也是可以的
            if (can_sumbit) {
                var data = {
                    username: $("#username").val(),
                    password: $("#password").val()
                };
                $.ajax({
                    url: "/login",
                    data: JSON.stringify(data),
                    contentType: 'application/json',
                    type: "post",
                    success: function (result) {
                        if (result.ok == false) {
                            createCode();//刷新验证码
                            layer.msg(result.message);
                        } else if (result.ok == true) {
                            localStorage.setItem("token", result.data.token);
                            $.ajax({
                                url: "/getInfo",
                                type: "get",
                                headers: {"token": localStorage.getItem("token")},
                                success: function (result) {
                                    if (result.ok == true){
                                        location.href = "../index.html"
                                    }

                                }
                            })
                        }

                    }
                })
            } else {
                return false;
            }
        });
    });

(3)用户名框失去焦点进行异步验证用户名存在性

//异步请求是否存在该用户名
    $("#username").blur(function () {
        console.log($("#username").val())
        $.ajax({
            url: "/loginVerify",
            data: {"username": $("#username").val()},
            type: "get",
            success: function (result) {

                    $("#zz1").text(result.message);
            }
        })
    })
    });

3.后端代码

后端使用springboot+mybatis-plus +mysql

主要来学习token的使用,使用jwt生成token,配置token的生效时间等,后续会整合把token字符串和过期时间存入redic

(1)pojo,使用了lombok

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
}

(2)mybatis-plus配置类config,主要扫描作用,分页插件用不上

@Configuration
@MapperScan("com.xj.mapper")
public class MybatisConfig {
        /**
         * 分页插件
         */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            // paginationInterceptor.setLimit(你的最大单页限制数量,默认 500 条,小于 0 如 -1 不受限制);
            return paginationInterceptor;
        }


    }

(3)mapper的UserMapper

public interface UserMapper extends BaseMapper<User> {
}

(4)service的UserService接口和serviceImpl的UserServiceImpl

public interface UserService extends IService<User> {
    User login(User user);

    User getUserByUsername(String username);

    User getUserById(Long userId);
}
@Service("userService")
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public User login(User user) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", user.getUsername());
        wrapper.eq("password", user.getPassword());
        System.out.println(baseMapper.selectOne(wrapper).getUsername());
        return baseMapper.selectOne(wrapper);
    }

    @Override
    public User getUserByUsername(String username) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        return baseMapper.selectOne(wrapper);
    }

    @Override
    public User getUserById(Long userId) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("id",userId);
        return baseMapper.selectOne(wrapper);
    }
}

(5)controller的UserService

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody User user, HttpServletRequest request){
        try {
            User loginUser = userService.login(user);
            Map<String, Object> map = new HashMap<>();
            if (loginUser != null) {
                String token = JwtHelper.createToken(loginUser.getId().longValue());
                map.put("token", token);
                return Result.ok(map);
            } else {
                throw new RuntimeException("用户名或密码错误");
            }
        } catch (RuntimeException e) {
            return Result.fail().message("用户名或密码错误");
        }
    }
    @GetMapping("/getInfo")
    public Result getInfo(@RequestHeader String token){
        boolean expiration = JwtHelper.isExpiration(token);//验证token是否过期
        if (expiration) {
            Result.build(null, ResultCodeEnum.CODE_ERROR);
        }
        Long userId = JwtHelper.getUserId(token);
        Map<String, Object> map = new HashMap<>();
        User user = userService.getUserById(userId);
        map.put("user", user);
        return Result.ok(user);
    }
    @GetMapping("/loginVerify")
    public Result test(String username){
        User user = userService.getUserByUsername(username);
        if(user != null){
            return Result.ok().message("√");
        }else {
            return Result.fail().message("用户不存在");
        }
    }
}

(5)工具类until,Result用来封装返回数据状态码等,JwtHelper生成token和解析token的工具类

Result工具类

    @Data
    @ApiModel(value = "全局统一返回结果")
    public class Result<T> {

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private T data;

    public Result() {
    }

    // 返回数据
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<T>();
        if (data != null)
            result.setData(data);
        return result;
    }

    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }

    public static <T> Result<T> ok() {
        return Result.ok(null);
    }

    /**
     * 操作成功
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> Result<T> ok(T data) {
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }

    public static <T> Result<T> fail() {
        return Result.fail(null);
    }

    /**
     * 操作失败
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> Result<T> fail(T data) {
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.FAIL);
    }

    public Result<T> message(String msg) {
        this.setMessage(msg);
        return this;
    }

    public Result<T> code(Integer code) {
        this.setCode(code);
        return this;
    }

    public boolean isOk() {
        if (this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
            return true;
        }
        return false;
    }
}

jwthelper工具类 

/**
 * token口令生成工具 JwtHelper
 */
public class JwtHelper {
    private static long tokenExpiration = 24*60*60*1000;
    private static String tokenSignKey = "123456";

    //生成token字符串
    public static String createToken(Long userId) {
        String token = Jwts.builder()

                .setSubject("YYGH-USER")

                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))

                .claim("userId", userId)
//                .claim("userName", userName)

                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    //从token字符串获取userid
    public static Long getUserId(String token) {
        if(StringUtils.isEmpty(token)) return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }

    //从token字符串获取userType
    public static Integer getUserType(String token) {
        if(StringUtils.isEmpty(token)) return null;
        Jws<Claims> claimsJws
                = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (Integer)(claims.get("userType"));
    }

    //从token字符串获取userName
    public static String getUserName(String token) {
        if(StringUtils.isEmpty(token)) return "";
        Jws<Claims> claimsJws
                = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("userName");
    }

    //判断token是否有效
    public static boolean isExpiration(String token){
        try {
            boolean isExpire = Jwts.parser()
                    .setSigningKey(tokenSignKey)
                    .parseClaimsJws(token)
                    .getBody()
                    .getExpiration().before(new Date());
            //没有过期,有效,返回false
            return isExpire;
        }catch(Exception e) {
            //过期出现异常,返回true
            return true;
        }
    }


    /**
     * 刷新Token
     * @param token
     * @return
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = Jwts.parser()
                    .setSigningKey(tokenSignKey)
                    .parseClaimsJws(token)
                    .getBody();
            refreshedToken = JwtHelper.createToken(getUserId(token));
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
     }

}

(6)application.yml配置

spring:
  #解决SpringBoot2.6.0与swagger冲突问题【原因是在springboot2.6.0中将SpringMVC 默认路径匹配策略从AntPathMatcher 更改为PathPatternParser,导致出错,解决办法是切换回原先的AntPathMatcher】
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  #配置数据源
  datasource:
    #配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    #配置数据库连接属性
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/study_xj?characterEncoding=utf-8&serverTimezone=GMT%2B8&userSSL=false
    username: root
    password: 334460977
    #url: jdbc:mysql://r2czkq1vewxat78mnyg60oisurj5h4dp.mysql.qingcloud.link:3306/ssg_zhxy_db?characterEncoding=utf-8&serverTimezone=GMT%2B8&userSSL=false
    #username: shangguigu
    #password: shangguigu@QY123
    #mybatis-plus内置连接池
    hikari:
      connection-test-query: SELECT 1
      connection-timeout: 60000
      idle-timeout: 500000
      max-lifetime: 540000
      maximum-pool-size: 12
      minimum-idle: 10
      pool-name: GuliHikariPool
  thymeleaf:
    #模板的模式,支持 HTML, XML TEXT JAVASCRIPT
    mode: HTML5
    #编码 可不用配置
    encoding: UTF-8
    #开发配置为false,避免修改模板还要重启服务器
    cache: false
    #配置模板路径,默认是templates,可以不用配置
    prefix: classpath:/static/
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  servlet:
    #设置文件上传上限
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
mybatis-plus:
  configuration:
    #添加日志支持
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:/mapper/**/*.xml

全部代码地址:登录学习: 学习登录功能

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值