Java實(shí)現(xiàn)Token登錄驗(yàn)證的項(xiàng)目實(shí)踐
一、JWT是什么?
在介紹JWT之前,我們先來回顧一下利用token進(jìn)行用戶身份驗(yàn)證的流程:
1、客戶端使用用戶名和密碼請求登錄
2、服務(wù)端收到請求,驗(yàn)證用戶名和密碼
3、驗(yàn)證成功后,服務(wù)端會簽發(fā)一個token,再把這個token返回給客戶端
4、客戶端收到token后可以把它存儲起來,比如放到cookie中
5、客戶端每次向服務(wù)端請求資源時需要攜帶服務(wù)端簽發(fā)的token,可以在cookie或者h(yuǎn)eader中攜帶
6、服務(wù)端收到請求,然后去驗(yàn)證客戶端請求里面帶著的token,如果驗(yàn)證成功,就向客戶端返回請求數(shù)據(jù)
這種基于token的認(rèn)證方式相比傳統(tǒng)的session認(rèn)證方式更節(jié)約服務(wù)器資源,并且對移動端和分布式更加友好。其優(yōu)點(diǎn)如下:
支持跨域訪問:cookie是無法跨域的,而token由于沒有用到cookie(前提是將token放到請求頭中),所以跨域后不會存在信息丟失問題
無狀態(tài):token機(jī)制在服務(wù)端不需要存儲session信息,因?yàn)閠oken自身包含了所有登錄用戶的信息,所以可以減輕服務(wù)端壓力
更適用CDN:可以通過內(nèi)容分發(fā)網(wǎng)絡(luò)請求服務(wù)端的所有資料
更適用于移動端:當(dāng)客戶端是非瀏覽器平臺時,cookie是不被支持的,此時采用token認(rèn)證方式會簡單很多
無需考慮CSRF:由于不再依賴cookie,所以采用token認(rèn)證方式不會發(fā)生CSRF,所以也就無需考慮CSRF的防御
而JWT就是上述流程當(dāng)中token的一種具體實(shí)現(xiàn)方式,其全稱是JSON Web Token,官網(wǎng)地址:https://jwt.io/
通俗地說,JWT的本質(zhì)就是一個字符串,它是將用戶信息保存到一個Json字符串中,然后進(jìn)行編碼后得到一個JWT token,并且這個JWT token帶有簽名信息,接收后可以校驗(yàn)是否被篡改,所以可以用于在各方之間安全地將信息作為Json對象傳輸。JWT的認(rèn)證流程如下:
1、首先,前端通過Web表單將自己的用戶名和密碼發(fā)送到后端的接口,這個過程一般是一個POST請求。建議的方式是通過SSL加密的傳輸(HTTPS),從而避免敏感信息被嗅探
2、后端核對用戶名和密碼成功后,將包含用戶信息的數(shù)據(jù)作為JWT的Payload,將其與JWT Header分別進(jìn)行Base64編碼拼接后簽名,形成一個JWT Token,形成的JWT Token就是一個如同lll.zzz.xxx的字符串
3、后端將JWT Token字符串作為登錄成功的結(jié)果返回給前端。前端可以將返回的結(jié)果保存在瀏覽器中,退出登錄時刪除保存的JWT Token即可
4、前端在每次請求時將JWT Token放入HTTP請求頭中的Authorization屬性中(解決XSS和XSRF問題)
5、后端檢查前端傳過來的JWT Token,驗(yàn)證其有效性,比如檢查簽名是否正確、是否過期、token的接收方是否是自己等等
6、驗(yàn)證通過后,后端解析出JWT Token中包含的用戶信息,進(jìn)行其他邏輯操作(一般是根據(jù)用戶信息得到權(quán)限等),返回結(jié)果

最后:說白了,JWT:JSON Web Token,其實(shí)token就是一段字符串,由三部分組成:Header,Payload,Signature
二、使用步驟
1.項(xiàng)目結(jié)構(gòu)

2.相關(guān)依賴
<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.數(shù)據(jù)庫
這里進(jìn)行測試,所以用戶類只有用戶名密碼,自行創(chuàng)建

4.相關(guān)代碼
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:用來跳過驗(yàn)證的 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保留,
所以他們能在運(yùn)行時被JVM或其他使用反射機(jī)制的代碼所讀取和使用。*/
@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,"服務(wù)端異常");
public static CodeMsg PARAMETER_ISNULL = new CodeMsg(500101,"輸入?yún)?shù)為空");
// 業(yè)務(wù)異常
public static CodeMsg USER_NOT_EXSIST = new CodeMsg(500102,"用戶不存在");
public static CodeMsg ONLINE_USER_OVER = new CodeMsg(500103,"在線用戶數(shù)超出允許登錄的最大用戶限制。");
public static CodeMsg SESSION_NOT_EXSIST = new CodeMsg(500104,"不存在離線session數(shù)據(jù)");
public static CodeMsg NOT_FIND_DATA = new CodeMsg(500105,"查找不到對應(yīng)數(shù)據(jù)");
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:返回統(tǒng)一結(jié)果集
*/
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();
}
/**
* 成功時候的調(diào)用
* @return
*/
public static <T> Result<T> success(T data){
return new Result<T>(data);
}
/**
* 成功,不需要傳入?yún)?shù)
* @return
*/
@SuppressWarnings("unchecked")
public static <T> Result<T> success(){
return (Result<T>) success("");
}
/**
* 失敗時候的調(diào)用
* @return
*/
public static <T> Result<T> error(CodeMsg cm){
return new Result<T>(cm);
}
/**
* 失敗時候的調(diào)用,擴(kuò)展消息參數(shù)
* @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攔截器
*/
@Configuration
public 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注釋,有則跳過認(rèn)證
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//檢查有沒有需要用戶權(quán)限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 執(zhí)行認(rèn)證
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("用戶不存在,請重新登錄");
}
// 驗(yàn)證 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());
}
/**
* 登錄驗(yàn)證
* @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實(shí)現(xiàn)類:
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;
@Service
public 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:
*/
@Service
public 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> {
}三、測試結(jié)果
1、登錄驗(yàn)證

2、查詢用戶信息
這個方法加上了@UserLoginToken,所以要token才能查詢


3、不加上Token進(jìn)行測試就會出錯提示

出錯提示:

到此這篇關(guān)于Java實(shí)現(xiàn)Token登錄驗(yàn)證的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Java Token登錄驗(yàn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java利用TCP協(xié)議實(shí)現(xiàn)客戶端與服務(wù)器通信(附通信源碼)
這篇文章主要介紹了Java利用TCP協(xié)議實(shí)現(xiàn)客戶端與服務(wù)器通信(附通信源碼),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
SpringMVC生成的驗(yàn)證碼圖片不顯示問題及解決方法
這篇文章主要介紹了SpringMVC生成的驗(yàn)證碼圖片不顯示問題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
Spring?invokeBeanFactoryPostProcessors方法刨析源碼
invokeBeanFactoryPostProcessors該方法會實(shí)例化所有BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor的實(shí)例并且執(zhí)行postProcessBeanFactory與postProcessBeanDefinitionRegistry方法2023-01-01
spring boot 添加admin監(jiān)控的方法
這篇文章主要介紹了spring boot 添加admin監(jiān)控的相關(guān)知識,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-02-02
Spring Boot 對接深度求索接口實(shí)現(xiàn)知識問答功能
本文詳細(xì)介紹了如何使用 Spring Boot 對接深度求索接口,實(shí)現(xiàn)知識問答功能,通過整合深度求索 API,我們可以輕松地在 Spring Boot 項(xiàng)目中實(shí)現(xiàn)智能問答功能,2025-02-02

