用户登录、注册及鉴权是我们基本所有系统必备的,也是很核心重要的一块,这一块的安全性等都比较重要,实现的方案其实也有几种,从以前的
cookie
+session
的方案,到现在常用的jwt
的方案,这篇文章就讲讲目前在公司中最常用的jwt
方案如何实现。
一、用户注册与登录
完成用户注册与登录有个核心点就是密码的加密与验证,我们目前比较常用的方案是密码+盐
再采用MD5加密
的方案,
盐的方式一般可以在application.yml
里面写死,但安全性相对较差,还有就是通过UUID
生成存到数据库里,这里我们采用第二种安全性更高的方式。
sql
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
:
1 2 3 4 5 6 7 8 9 10 | import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class LoginUserDto { private String username; private String password; } |
用户注册与登录的controller
:
controller.UserController
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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
:
1 2 3 4 5 6 7 | 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 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
的依赖包
1 | com.alibabafastjson2. 0 .26io.jsonwebtokenjjwt0. 9.1 |
-
JWT工具类
的封装
utils.JwtUtils
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 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 checkToken(String token) { try { Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token); return (Map) parse.getBody(); } catch (Exception e) { e.printStackTrace(); return null ; } } } |
到此我们已经完成了用户的注册和登录功能。但还有一个问题就是用户鉴权,我们在调用其他接口时如何判断用户是否已登录。
二、用户鉴权
用户鉴权我们需要用到ThreadLocal
来存储用户信息,我们首先创建这个工具类
utils.UserThreadLocal
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import com.jk.domain.entity.User; public class UserThreadLocal { private UserThreadLocal() { } private static final ThreadLocal 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
:
1 | User checkToken(String token); |
service.impl.UserServiceImpl
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Override public User checkToken(String token) { if (StringUtils.isEmpty(token)) { return null ; } Map 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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
使用登录拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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实现用户登录、注册、鉴权的文章就介绍到这了,更多相关SpringBoot JWT用户登录、注册、鉴权内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!