SpringBoot結(jié)合JWT實(shí)現(xiàn)用戶登錄、注冊(cè)、鑒權(quán)
用戶登錄、注冊(cè)及鑒權(quán)是我們基本所有系統(tǒng)必備的,也是很核心重要的一塊,這一塊的安全性等都比較重要,實(shí)現(xiàn)的方案其實(shí)也有幾種,從以前的
cookie+session的方案,到現(xiàn)在常用的jwt的方案,這篇文章就講講目前在公司中最常用的jwt方案如何實(shí)現(xiàn)。
一、用戶注冊(cè)與登錄
完成用戶注冊(cè)與登錄有個(gè)核心點(diǎn)就是密碼的加密與驗(yàn)證,我們目前比較常用的方案是密碼+鹽再采用MD5加密的方案,
鹽的方式一般可以在application.yml里面寫死,但安全性相對(duì)較差,還有就是通過(guò)UUID生成存到數(shù)據(jù)庫(kù)里,這里我們采用第二種安全性更高的方式。

sql如下:
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `salt` varchar(255) NOT NULL, `admin` int(1) DEFAULT '0', `age` int(3) NOT NULL, `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, `deleted` int(1) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
對(duì)應(yīng)的User實(shí)體類
domian.entity.User:
import com.baomidou.mybatisplus.annotation.FieldFill;
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 lombok.experimental.Accessors;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("user")
public class User {
@TableId
private Long id;
private String username;
private String password;
private String salt;
private Boolean admin;
private Integer age;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
private Integer deleted;
}這里我們使用了Mybatis Plus的邏輯刪除及自動(dòng)填充功能,不太清楚的可以看看我的文章SpringBoot 整合 Mybatis Plus 實(shí)現(xiàn)基本CRUD功能
接收用戶注冊(cè)信息的DTO
domain.dto.registryUserDto:
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.UUID;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class registryUserDto {
private String username;
private String password;
@JsonIgnore
private String salt = UUID.randomUUID().toString().replaceAll("-", "");
private Boolean admin;
private Integer age;
}@JsonIgnore為忽略前端的傳值,這里使用我們UUID生成的值。
用戶登錄的DTO
domain.dto.LoginUserDto:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUserDto {
private String username;
private String password;
}用戶注冊(cè)與登錄的controller:
controller.UserController:
import com.jk.domain.dto.registryUserDto;
import com.jk.domain.dto.LoginUserDto;
import com.jk.service.UserService;
import com.jk.domain.vo.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/registry")
public ResponseResult registryUser(@RequestBody registryUserDto registryUserDto) {
return userService.registryUser(registryUserDto);
}
@PostMapping("/login")
public ResponseResult login(@RequestBody LoginUserDto loginUserDto) {
return userService.login(loginUserDto);
}
}用戶注冊(cè)與登錄的service:
service.UserService:
import com.jk.domain.dto.registryUserDto;
import com.jk.domain.dto.LoginUserDto;
import com.jk.domain.vo.ResponseResult;
public interface UserService {
ResponseResult registryUser(registryUserDto registryUserDto);
ResponseResult login(LoginUserDto loginUserDto);
}用戶注冊(cè)與登錄的service實(shí)現(xiàn)類:
service.impl.UserServiceImpl:
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jk.domain.dto.registryUserDto;
import com.jk.domain.dto.LoginUserDto;
import com.jk.domain.entity.User;
import com.jk.enums.AppHttpCodeEnum;
import com.jk.mapper.UserMapper;
import com.jk.service.UserService;
import com.jk.domain.vo.ResponseResult;
import com.jk.utils.BeanCopyUtils;
import com.jk.utils.JwtUtils;
import com.jk.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.concurrent.TimeUnit;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult registryUser(registryUserDto registryUserDto) {
String password = registryUserDto.getPassword();
String salt = registryUserDto.getSalt();
String md5Password = DigestUtils.md5DigestAsHex((password + salt).getBytes());
registryUserDto.setPassword(md5Password);
User user = BeanCopyUtils.copyBean(registryUserDto, User.class);
userMapper.insert(user);
return ResponseResult.okResult();
}
@Override
public ResponseResult login(LoginUserDto loginUserDto) {
String username = loginUserDto.getUsername();
String password = loginUserDto.getPassword();
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(queryWrapper);
String md5Password = DigestUtils.md5DigestAsHex((password + user.getSalt()).getBytes());
if (!md5Password.equals(user.getPassword())) {
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR);
}
String token = JwtUtils.createToken(user.getId());
redisCache.setCacheObject("TOKEN_" + token, JSON.toJSONString(user), 1, TimeUnit.DAYS);
return ResponseResult.okResult(token);
}
}用戶注冊(cè)時(shí),我們把密碼+salt進(jìn)行MD5加密,然后入庫(kù),用戶登錄時(shí),根據(jù)username查出用戶,再把用戶傳入的密碼+salt進(jìn)行MD5加密與數(shù)據(jù)庫(kù)查出的用戶進(jìn)行密碼比較判斷是否驗(yàn)證通過(guò)。這里還有使用到一個(gè)JWT工具類,驗(yàn)證通過(guò)后使用JWT工具類生成token和用戶信息存到redis里面,這里需要引入下fastjson來(lái)對(duì)用戶信息字符串化存,然后返回前端token。
具體JWT使用如下:
- 首先引入
fastjson和jwt的依賴包
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.26</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
JWT工具類的封裝
utils.JwtUtils:
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtils {
private static final String jwtToken = "1234567890p[]l;'";
public static String createToken(Long userId) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
JwtBuilder jwtBuilder = Jwts.builder()
// 設(shè)置有效載荷
.setClaims(claims)
// 設(shè)置簽發(fā)時(shí)間
.setIssuedAt(new Date())
// 設(shè)置過(guò)期時(shí)間
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000))
// 采用HS256方式簽名,key就是用來(lái)簽名的秘鑰
.signWith(SignatureAlgorithm.HS256, jwtToken);
String token = jwtBuilder.compact();
return token;
}
public static Map<String, Object> checkToken(String token) {
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}到此我們已經(jīng)完成了用戶的注冊(cè)和登錄功能。但還有一個(gè)問(wèn)題就是用戶鑒權(quán),我們?cè)谡{(diào)用其他接口時(shí)如何判斷用戶是否已登錄。
二、用戶鑒權(quán)
用戶鑒權(quán)我們需要用到ThreadLocal來(lái)存儲(chǔ)用戶信息,我們首先創(chuàng)建這個(gè)工具類
utils.UserThreadLocal:
import com.jk.domain.entity.User;
public class UserThreadLocal {
private UserThreadLocal() {
}
private static final ThreadLocal<User> LOCAL = new ThreadLocal<>();
public static void put(User user) {
LOCAL.set(user);
}
public static User get() {
return LOCAL.get();
}
public static void remove() {
LOCAL.remove();
}
}還需要在service中實(shí)現(xiàn)驗(yàn)證token的邏輯
service.UserService:
User checkToken(String token);
service.impl.UserServiceImpl:
@Override
public User checkToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
Map<String, Object> map = JwtUtils.checkToken(token);
if (map == null) {
return null;
}
String userJson = redisCache.getCacheObject("TOKEN_" + token);
if (StringUtils.isEmpty(userJson)) {
return null;
}
User user = JSON.parseObject(userJson, User.class);
return user;
}使用攔截器實(shí)現(xiàn)token驗(yàn)證
handler.interceptor.LoginInterceptor:
import com.jk.domain.entity.User;
import com.jk.enums.AppHttpCodeEnum;
import com.jk.exception.SystemException;
import com.jk.service.UserService;
import com.jk.utils.UserThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
String token = request.getHeader("token");
log.info("===============request start===============");
log.info("request uri:{}", request.getRequestURI());
log.info("request method:{}", request.getMethod());
log.info("token:{}", token);
log.info("===============request end===============");
if (StringUtils.isEmpty(token)) {
throw new SystemException(AppHttpCodeEnum.NEED_LOGIN);
}
User user = userService.checkToken(token);
if (user == null) {
throw new SystemException(AppHttpCodeEnum.NEED_LOGIN);
}
UserThreadLocal.put(user);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}配置WebMvcConfigurer使用登錄攔截器
import com.jk.handler.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/web/**")
.addPathPatterns("/admin/**");
}
}會(huì)對(duì)/web及/admin的所有接口做登錄驗(yàn)證,這個(gè)大家根據(jù)自己項(xiàng)目需求調(diào)整。
到此這篇關(guān)于SpringBoot結(jié)合JWT實(shí)現(xiàn)用戶登錄、注冊(cè)、鑒權(quán)的文章就介紹到這了,更多相關(guān)SpringBoot JWT用戶登錄、注冊(cè)、鑒權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)讀取及生成Excel文件的方法
這篇文章主要介紹了Java實(shí)現(xiàn)讀取及生成Excel文件的方法,結(jié)合實(shí)例形式分析了java通過(guò)引入第三方j(luò)ar包poi-3.0.1-FINAL-20070705.jar實(shí)現(xiàn)針對(duì)Excel文件的讀取及生成功能,需要的朋友可以參考下2017-12-12
Java的優(yōu)先隊(duì)列PriorityQueue原理及實(shí)例分析
這篇文章主要介紹了Java的優(yōu)先隊(duì)列PriorityQueue原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java面向?qū)ο箨P(guān)鍵字extends繼承的深入講解
繼承就是使用已定義的類作為父類,新建一個(gè)類作為子類使用extends關(guān)鍵字繼承這個(gè)類,這樣就實(shí)現(xiàn)了繼承關(guān)系,這篇文章主要給大家介紹了關(guān)于Java面向?qū)ο箨P(guān)鍵字extends繼承的相關(guān)資料,需要的朋友可以參考下2021-08-08
java后臺(tái)如何接收get請(qǐng)求傳過(guò)來(lái)的數(shù)組
這篇文章主要介紹了java后臺(tái)如何接收get請(qǐng)求傳過(guò)來(lái)的數(shù)組問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Spring Boot 自動(dòng)配置的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot 自動(dòng)配置的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
SpringBoot 2 統(tǒng)一異常處理過(guò)程解析
這篇文章主要介紹了SpringBoot 2 統(tǒng)一異常處理過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09

