当前位置:首页 » 《随便一记》 » 正文

Java实现Token登录验证(基于JWT的token认证实现)

13 人参与  2023年03月23日 18:57  分类 : 《随便一记》  评论

点击全文阅读


文章目录

一、JWT是什么?二、使用步骤 1.项目结构2.相关依赖3.数据库4.相关代码 三、测试结果

一、JWT是什么?

在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:

1、客户端使用用户名和密码请求登录

2、服务端收到请求,验证用户名和密码

3、验证成功后,服务端会签发一个token,再把这个token返回给客户端

4、客户端收到token后可以把它存储起来,比如放到cookie中

5、客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带

6、服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下

支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
更适用CDN:可以通过内容分发网络请求服务端的所有资料
更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

1、首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探

2、后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
3、后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可

4、前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)

5、后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等

6、验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果
在这里插入图片描述

最后:说白了,JWT:JSON Web Token,其实token就是一段字符串,由三部分组成:Header,Payload,Signature

二、使用步骤

1.项目结构

在这里插入图片描述

2.相关依赖

    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.22</version>        </dependency>        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>            <version>5.7.21</version>        </dependency>        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>mybatis-plus-boot-starter</artifactId>            <version>3.4.3.2</version>        </dependency>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>1.2.79</version>        </dependency>        <dependency>            <groupId>commons-beanutils</groupId>            <artifactId>commons-beanutils</artifactId>            <version>1.9.4</version>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.22</version>        </dependency>        <dependency>            <groupId>commons-io</groupId>            <artifactId>commons-io</artifactId>            <version>2.11.0</version>        </dependency>        <dependency>            <groupId>org.apache.poi</groupId>            <artifactId>poi-ooxml</artifactId>            <version>4.1.2</version>        </dependency>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.13.1</version>            <scope>test</scope>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <version>2.3.8.RELEASE</version>        </dependency>        <dependency>            <groupId>com.auth0</groupId>            <artifactId>java-jwt</artifactId>            <version>3.18.3</version>        </dependency>        <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-lang3</artifactId>        </dependency>    </dependencies>

3.数据库

这里进行测试,所以用户类只有用户名密码,自行创建
在这里插入图片描述

4.相关代码

1、annotation包
PassToken:

package com.geesun.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:26 * @description:用来跳过验证的 PassToken */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface PassToken {    boolean required() default true;}

UserLoginToken:

package com.geesun.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:26 * @description:用于登录后才能操作的token *//*RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。*/@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface UserLoginToken {    boolean required() default true;}

2、common包
CodeMsg:

package com.geesun.common;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:26 * @description:返回提示 */public class CodeMsg {    private int retCode;    private String message;    // 按照模块定义CodeMsg    // 通用异常    public static CodeMsg SUCCESS = new CodeMsg(0,"success");    public static CodeMsg SERVER_EXCEPTION = new CodeMsg(500100,"服务端异常");    public static CodeMsg PARAMETER_ISNULL = new CodeMsg(500101,"输入参数为空");    // 业务异常    public static CodeMsg USER_NOT_EXSIST = new CodeMsg(500102,"用户不存在");    public static CodeMsg ONLINE_USER_OVER = new CodeMsg(500103,"在线用户数超出允许登录的最大用户限制。");    public static CodeMsg SESSION_NOT_EXSIST =  new CodeMsg(500104,"不存在离线session数据");    public static CodeMsg NOT_FIND_DATA = new CodeMsg(500105,"查找不到对应数据");    public static CodeMsg USER_OR_PASS_ERROR = new CodeMsg(500102,"账号或者密码错误,请重试!");    private CodeMsg(int retCode, String message) {        this.retCode = retCode;        this.message = message;    }    public int getRetCode() {        return retCode;    }    public String getMessage() {        return message;    }    public void setMessage(String message) {        this.message = message;    }}

Result:

package com.geesun.common;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:26 * @description:返回统一结果集 */public class Result<T> {    private String message;    private int retCode;    private T data;    private Result(T data) {        this.retCode = 200;        this.message = "成功";        this.data = data;    }    private Result(CodeMsg cm) {        if(cm == null){            return;        }        this.retCode = cm.getRetCode();        this.message = cm.getMessage();    }    /**     * 成功时候的调用     * @return     */    public static <T> Result<T> success(T data){        return new Result<T>(data);    }    /**     * 成功,不需要传入参数     * @return     */    @SuppressWarnings("unchecked")    public static <T> Result<T> success(){        return (Result<T>) success("");    }    /**     * 失败时候的调用     * @return     */    public static <T> Result<T> error(CodeMsg cm){        return new Result<T>(cm);    }    /**     * 失败时候的调用,扩展消息参数     * @param cm     * @param msg     * @return     */    public static <T> Result<T> error(CodeMsg cm,String msg){        cm.setMessage(cm.getMessage()+"--"+msg);        return new Result<T>(cm);    }    public T getData() {        return data;    }    public String getMessage() {        return message;    }    public int getRetCode() {        return retCode;    }}

3、config包
InterceptorConfig:

package com.geesun.config;import com.geesun.Interceptor.AuthenticationInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.format.FormatterRegistry;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.validation.MessageCodesResolver;import org.springframework.validation.Validator;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.method.support.HandlerMethodReturnValueHandler;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.config.annotation.*;import java.util.List;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:25 * @description:新建Token拦截器 */@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(authenticationInterceptor())                .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录    }    @Bean    public AuthenticationInterceptor authenticationInterceptor() {        return new AuthenticationInterceptor();    }    @Override    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {        // TODO Auto-generated method stub    }    @Override    public void addCorsMappings(CorsRegistry arg0) {        // TODO Auto-generated method stub    }    @Override    public void addFormatters(FormatterRegistry arg0) {        // TODO Auto-generated method stub    }    @Override    public void addResourceHandlers(ResourceHandlerRegistry arg0) {        // TODO Auto-generated method stub    }    @Override    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {        // TODO Auto-generated method stub    }    @Override    public void addViewControllers(ViewControllerRegistry arg0) {        // TODO Auto-generated method stub    }    @Override    public void configureAsyncSupport(AsyncSupportConfigurer arg0) {        // TODO Auto-generated method stub    }    @Override    public void configureContentNegotiation(ContentNegotiationConfigurer arg0) {        // TODO Auto-generated method stub    }    @Override    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer arg0) {        // TODO Auto-generated method stub    }    @Override    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {        // TODO Auto-generated method stub    }    @Override    public void configureMessageConverters(List<HttpMessageConverter<?>> arg0) {        // TODO Auto-generated method stub    }    @Override    public void configurePathMatch(PathMatchConfigurer arg0) {        // TODO Auto-generated method stub    }    @Override    public void configureViewResolvers(ViewResolverRegistry arg0) {        // TODO Auto-generated method stub    }    @Override    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {        // TODO Auto-generated method stub    }    @Override    public void extendMessageConverters(List<HttpMessageConverter<?>> arg0) {        // TODO Auto-generated method stub    }    @Override    public MessageCodesResolver getMessageCodesResolver() {        // TODO Auto-generated method stub        return null;    }    @Override    public Validator getValidator() {        // TODO Auto-generated method stub        return null;    }}

4、Interceptor包
AuthenticationInterceptor:

package com.geesun.Interceptor;import com.auth0.jwt.JWT;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.exceptions.JWTDecodeException;import com.auth0.jwt.exceptions.JWTVerificationException;import com.geesun.annotation.PassToken;import com.geesun.annotation.UserLoginToken;import com.geesun.pojo.User;import com.geesun.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:24 * @description:拦截器 */public class AuthenticationInterceptor implements HandlerInterceptor {    @Autowired    UserService userService;    @Override    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {        String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token        // 如果不是映射到方法直接通过        if(!(object instanceof HandlerMethod)){            return true;        }        HandlerMethod handlerMethod=(HandlerMethod)object;        Method method=handlerMethod.getMethod();        //检查是否有passtoken注释,有则跳过认证        if (method.isAnnotationPresent(PassToken.class)) {            PassToken passToken = method.getAnnotation(PassToken.class);            if (passToken.required()) {                return true;            }        }        //检查有没有需要用户权限的注解        if (method.isAnnotationPresent(UserLoginToken.class)) {            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);            if (userLoginToken.required()) {                // 执行认证                if (token == null) {                    throw new RuntimeException("无token,请重新登录");                }                // 获取 token 中的 user id                String userId;                try {                    userId = JWT.decode(token).getAudience().get(0);                } catch (JWTDecodeException j) {                    throw new RuntimeException("401");                }                User user = userService.findUserById(userId);                if (user == null) {                    throw new RuntimeException("用户不存在,请重新登录");                }                // 验证 token                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();                try {                    jwtVerifier.verify(token);                } catch (JWTVerificationException e) {                    throw new RuntimeException("401");                }                return true;            }        }        return true;    }    @Override    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {    }    @Override    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}

5、utils包
TokenUtil:

package com.geesun.utils;import com.auth0.jwt.JWT;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:24 * @description: */public class TokenUtil {    public static String getTokenUserId() {        String token = getRequest().getHeader("token");// 从 http 请求头中取出 token        String userId = JWT.decode(token).getAudience().get(0);        return userId;    }    /**     * 获取request     * @return     */    public static HttpServletRequest getRequest() {        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder                .getRequestAttributes();        return requestAttributes == null ? null : requestAttributes.getRequest();    }}

6、pojo包
User:

package com.geesun.pojo;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;@Data@AllArgsConstructor@NoArgsConstructor@TableName(value = "`user`")public class User implements Serializable {    @TableId(value = "id", type = IdType.NONE)    private String id;    @TableField(value = "username")    private String username;    @TableField(value = "password")    private String password;    private static final long serialVersionUID = 1L;}

7、controller包
UserController:

package com.geesun.controller;import cn.hutool.json.JSONObject;import com.geesun.annotation.UserLoginToken;import com.geesun.common.CodeMsg;import com.geesun.common.Result;import com.geesun.pojo.User;import com.geesun.service.UserService;import com.geesun.service.impl.TokenService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;/** * @author :Mr.ZJW * @date :Created 2022/2/26 10:47 * @description: */@RestController@RequestMapping("/user")public class UserController {    @Autowired    private UserService userService;    @Autowired    private TokenService tokenService;    /**     * 查询用户信息     * @return     */    @UserLoginToken    @GetMapping("/list")    public Result<Object> list(){        return Result.success(userService.list());    }    /**     * 登录验证     * @param user     * @param response     * @return     */    @RequestMapping(value = "/login" ,method = RequestMethod.GET)    public Result<Object> login(User user, HttpServletResponse response) {        JSONObject jsonObject = new JSONObject();        //获取用户账号密码        User userForBase = new User();        userForBase.setId(userService.findByUsername(user).getId());        userForBase.setUsername(userService.findByUsername(user).getUsername());        userForBase.setPassword(userService.findByUsername(user).getPassword());        //判断账号或密码是否正确        if (!userForBase.getPassword().equals(user.getPassword())) {            return Result.error(CodeMsg.USER_OR_PASS_ERROR);        } else {            String token = tokenService.getToken(userForBase);            jsonObject.put("token", token);            Cookie cookie = new Cookie("token", token);            cookie.setPath("/");            response.addCookie(cookie);            return Result.success(jsonObject);        }    }    }

8、service包
UserService接口:

package com.geesun.service;import com.baomidou.mybatisplus.extension.service.IService;import com.geesun.pojo.User;public interface UserService extends IService<User> {    int deleteByIds(Long[] ids);    int addUser(User user);    User findByUsername(User user);    User findUserById(String userId);}

UserServiceImpl实现类:

package com.geesun.service.impl;import cn.hutool.core.util.ArrayUtil;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.geesun.mapper.UserMapper;import com.geesun.pojo.User;import com.geesun.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.Arrays;@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {    @Autowired    private UserMapper userMapper;        /**     * 判断用户名     * @param user     * @return     */    public User findByUsername(User user){        return userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername,user.getUsername()));    }    public User findUserById(String userId) {        return userMapper.selectById(userId);    }}

TokenService:

package com.geesun.service.impl;import com.auth0.jwt.JWT;import com.auth0.jwt.algorithms.Algorithm;import com.geesun.pojo.User;import org.springframework.stereotype.Service;import java.util.Date;/** * @author :Mr.ZJW * @date :Created 2022/2/28 10:20 * @description: */@Servicepublic class TokenService {    public String getToken(User user) {        Date start = new Date();        long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间        Date end = new Date(currentTime);        String token = "";        token = JWT.create().withAudience(user.getId()).withIssuedAt(start).withExpiresAt(end)                .sign(Algorithm.HMAC256(user.getPassword()));        return token;    }}

9、mapper包

package com.geesun.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.geesun.pojo.User;public interface UserMapper extends BaseMapper<User> {}

三、测试结果

1、登录验证
在这里插入图片描述
2、查询用户信息
这个方法加上了@UserLoginToken,所以要token才能查询
在这里插入图片描述

在这里插入图片描述
3、不加上Token进行测试就会出错提示
在这里插入图片描述
出错提示:
在这里插入图片描述
在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/56363.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1