您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java如何實現Token登錄驗證”,在日常操作中,相信很多人在Java如何實現Token登錄驗證問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java如何實現Token登錄驗證”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在介紹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
通俗地說,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
<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>
這里進行測試,所以用戶類只有用戶名密碼,自行創建
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攔截器 */ @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注釋,有則跳過認證 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; @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> { }
1、登錄驗證
2、查詢用戶信息
這個方法加上了@UserLoginToken,所以要token才能查詢
3、不加上Token進行測試就會出錯提示
出錯提示:
到此,關于“Java如何實現Token登錄驗證”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。