用户登录、注册及认证是咱们根本一切体系必备的,也是很核心重要的一块,这一块的安全性等都比较重要,完结的计划其实也有几种,从以前的
cookie
+session
的计划,到现在常用的jwt
的计划,这篇文章就讲讲现在在公司中最常用的jwt
计划怎么完结。
一、用户注册与登录
完结用户注册与登录有个核心点便是暗码的加密与验证,咱们现在比较常用的计划是暗码+盐
再选用MD5加密
的计划,
盐的方法一般能够在application.yml
里面写死,但安全性相对较差,还有便是经过UUID
生成存到数据库里,这儿咱们选用第二种安全性更高的方法。
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;
对应的User
实体类
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
的逻辑删除及自动填充功用,不太清楚的能够看看我的文章SpringBoot 整合 Mybatis Plus 完结根本CRUD功用
接纳用户注册信息的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;
}
用户注册与登录的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);
}
}
用户注册与登录的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);
}
用户注册与登录的service完结类
:
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);
}
}
用户注册时,咱们把暗码+salt
进行MD5加密
,然后入库,用户登录时,根据username
查出用户,再把用户传入的暗码+salt
进行MD5加密
与数据库查出的用户进行暗码比较判别是否验证经过。这儿还有运用到一个JWT东西类
,验证经往后运用JWT东西类
生成token
和用户信息存到redis
里面,这儿需要引进下fastjson
来对用户信息字符串化存,然后回来前端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()
// 设置有效载荷
.setClaims(claims)
// 设置签发时刻
.setIssuedAt(new Date())
// 设置过期时刻
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000))
// 选用HS256方法签名,key便是用来签名的秘钥
.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;
}
}
}
到此咱们已经完结了用户的注册和登录功用。但还有一个问题便是登录认证,咱们在调用其他接口时怎么判别用户是否已登录。
二、登录认证
登录认证咱们需要用到ThreadLocal
来存储用户信息,咱们首要创立这个东西类
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
中完结验证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;
}
运用拦截器完结token验证
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/**");
}
}
会对/web
及/admin
的一切接口做登录验证,这个我们根据自己项目需求调整。
关于公司中常用的 SpringBoot 项目 + JWT完结用户登录、注册、认证功用便是这样,里面一切运用到的技能和计划在之前的文章中也都有介绍,假如我们有任何疑问也能够评论区留言。