本篇文章记录怎么实现一个简单的登陆注册功能。
讲解里的代码是不完全的,具体的代码我会放在文章最后
文章目录
- 准备
- 为什么要有登录:
- 简述注册功能:
- 简述登录功能
- 完全代码:
- 数据实体部分:
- Users类:
- UsersLoginDTO类:
- RegisterDTO类:
- 拦截器部分:
- application.yml文件对应内容:
- JwtProperties类:
- JwtUtil工具类
- JwtTokenAdminInterceptor拦截器类:
- WebMvcConfiguration 配置类:
- 登录注册部分
- UserController类
- UserService接口
- UserServiceImpl实现类
- UserMapper类:
准备
要实现登录注册功能,首先我们就要有用户表来记录用户的数据。
我们的用户表大概就是这样的:
有id,用户名,密码,姓名,邮箱,权限等级这些字段。
为什么要有登录:
不知道大家是否想过,为什么要进行登录呢?
其实,在我们的项目开发中,我们要对各种请求进行拦截加以限制,这样才能保证我们项目的安全性,不然随随便便什么人就可以把我们项目的数据给拿走了。
在项目的配置中,我们会写拦截器用于拦截请求,将合法合规的请求放行。
项目配置:
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor tokenAdminInterceptor;
protected void addInterceptors(InterceptorRegistry registry){
log.info("开始注册自定义拦截器");
registry.addInterceptor(tokenAdminInterceptor)
.addPathPatterns("/api/**")//加入拦截
.excludePathPatterns("/api/users/login")//不拦截的请求
.excludePathPatterns("/api/users/register")
.excludePathPatterns("/api/category/list")
.excludePathPatterns("/api/users/sendEmail/**");
log.info("自定义拦截器注册完成");
}
}
这里的JwtTokenAdminIntercepor
则是拦截器,在这里面实现拦截与放行的功能。
JwtTokenAdminIntercepor
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
System.out.println("开始校验jwt");
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.ID).toString());
Integer role = (Integer) claims.get(JwtClaimsConstant.ROLE);
log.info("当前id:{}", empId);
log.info("当前角色:{}", role);
BaseContext.setCurrentId(empId);
BaseContext.setCurrentRole(role);//设置当前角色
//3、通过,放行
return true;
} catch (Exception ex) {
log.error("jwt校验失败:{}", ex.getMessage());
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
简述注册功能:
这个功能就很简单,不过是发送请求往表中加入用户数据罢了。
这是我们UserController类中的注册,我们这里使用的邮箱进行验证,关于邮箱验证的知识,可以查看文章:邮箱验证
@PostMapping("/register")
@ApiOperation("用户注册")
public Result register(@RequestBody RegisterDTO registerDTO){
log.info("用户注册:{}", registerDTO);
String code = registerDTO.getCode();
log.info("前端输入的验证码{}", code);
String eml = registerDTO.getEmail();
log.info("前端的对象为{},邮箱=》{}",registerDTO,eml);
String s = redisTemplate.opsForValue().get(eml);
log.info("从redis中获取code->{}",s);
if(s==null){
throw new RuntimeException(MessageConstant.CODE_EXPIRED);
}
if (Objects.equals(s, code)) {
log.info("验证码正确{}", code);
userService.register(registerDTO);
return Result.success(MessageConstant.Register_SUCCESS);
}else{
throw new RuntimeException(MessageConstant.CODE_EXPIRED);
}
}
注册功能实现类部分:
public void register(RegisterDTO registerDTO) {
String userName = registerDTO.getUsername();
String password = registerDTO.getPassword();
if(userName == null || password == null){//用户名密码不能为空
throw new AccountNotFoundException(MessageConstant.ACCOUNT_IS_NULL);
}
Users user = userMapper.getByUserName(userName);
if(user!= null){//查看用户名是否已存在
throw new AccountNotFoundException(MessageConstant.ACCOUNT_ALREADY_EXIST);
}
Users users = Users.builder()
.username(userName)
.password(DigestUtils.md5DigestAsHex(password.getBytes()))
.name(registerDTO.getName())
.email(registerDTO.getEmail())
.role(StatusConstant.NORMAL)
.build();
userMapper.insert(users);//向用户表中插入数据
}
大概就这样,用户注册功能就可以被实现。
简述登录功能
登录功能就是重头戏了,用户登录后再发送请求,我们就要验证这个请求的发送者是不是我们的用户,这个就比较重要了。
其实,登录功能的逻辑就是:1.验证登录对应的用户数据是否在我们的数据库表中。2.在数据库中,则生成相关的JWT令牌,方便后续用户发送请求验证。
UserController部分:
@PostMapping("/login")//请求方法
@ApiOperation("用户登录")//取名
public Result<UsersLoginVO> login(@RequestBody UsersLoginDTO userLoginDTO){
Users user = userService.login(userLoginDTO);
// 开始生成token
Map<String,Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.ID,user.getId());
claims.put(JwtClaimsConstant.ROLE,user.getRole());
// token令牌
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
UsersLoginVO usersLoginVO = UsersLoginVO.builder()
.id(user.getId())
.username(user.getUsername())
.token(token)
.role(user.getRole())
.email(user.getEmail())
.name(user.getName())
.build();
return Result.success(usersLoginVO);
}
service实现类部分:
@Override
public Users login(UsersLoginDTO userLoginDTO) {
String userName = userLoginDTO.getUsername();
String password = userLoginDTO.getPassword();
Users users = userMapper.getByUserName(userName);
if (users == null) {// 用户不存在
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
if(StatusConstant.DIS.equals(users.getRole())){// 账户被锁定
throw new AccountNotFoundException(MessageConstant.ACCOUNT_LOCKED);
}
password = DigestUtils.md5DigestAsHex(password.getBytes());
if(!password.equals(users.getPassword())){ // 密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
return users;
}
完全代码:
按以下顺序创建复制粘贴即可
数据实体部分:
Users类:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Users implements Serializable {
private Long id;
private String username;
private String password;
private String name;
private String email;
private Integer role;
}
UsersLoginDTO类:
@Data
public class UsersLoginDTO {
private String username;
private String password;
}
RegisterDTO类:
@Data
public class RegisterDTO {
private String username;
private String password;
private String name;
private String email;
private String code;
}
拦截器部分:
application.yml文件对应内容:
codehome:
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间
admin-ttl: 604800000
# 设置前端传递过来的令牌名称
admin-token-name: token
JwtProperties类:
@Component
@ConfigurationProperties(prefix = "codehome.jwt")
@Data
public class JwtProperties {
/**
* 管理端员工生成jwt令牌相关配置
*/
private String adminSecretKey;
private long adminTtl;
private String adminTokenName;
/**
* 用户端微信用户生成jwt令牌相关配置
*/
private String userSecretKey;
private long userTtl;
private String userTokenName;
}
JwtUtil工具类
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
JwtTokenAdminInterceptor拦截器类:
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
System.out.println("开始校验jwt");
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.ID).toString());
Integer role = (Integer) claims.get(JwtClaimsConstant.ROLE);
log.info("当前id:{}", empId);
log.info("当前角色:{}", role);
BaseContext.setCurrentId(empId);
BaseContext.setCurrentRole(role);//设置当前角色
//3、通过,放行
return true;
} catch (Exception ex) {
log.error("jwt校验失败:{}", ex.getMessage());
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
WebMvcConfiguration 配置类:
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor tokenAdminInterceptor;
protected void addInterceptors(InterceptorRegistry registry){
log.info("开始注册自定义拦截器");
registry.addInterceptor(tokenAdminInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/users/login")
.excludePathPatterns("/api/users/register")
.excludePathPatterns("/api/category/list")
.excludePathPatterns("/api/users/sendEmail/**");
log.info("自定义拦截器注册完成");
}
/**
* 通过knife4j生成接口文档
* @return
*/
@Bean
public Docket docket() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("代码芝士后端接口文档")
.version("1.0")
.description("代码芝士后端接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.codehome.server.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
/**
* 设置静态资源映射
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 消息转换器
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){
log.info("开始扩展消息转换器");
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(new JacksonObjectMapper());
converters.add(0,converter);
}
}
登录注册部分
UserController类
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户模块")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("/login")//请求方法
@ApiOperation("用户登录")//取名
public Result<UsersLoginVO> login(@RequestBody UsersLoginDTO userLoginDTO){
Users user = userService.login(userLoginDTO);
// 开始生成token
Map<String,Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.ID,user.getId());
claims.put(JwtClaimsConstant.ROLE,user.getRole());
// token令牌
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
UsersLoginVO usersLoginVO = UsersLoginVO.builder()
.id(user.getId())
.username(user.getUsername())
.token(token)
.role(user.getRole())
.email(user.getEmail())
.name(user.getName())
.build();
return Result.success(usersLoginVO);
}
@PostMapping("/register")
@ApiOperation("用户注册")
public Result register(@RequestBody RegisterDTO registerDTO){
userService.register(registerDTO);
return Result.success(MessageConstant.Register_SUCCESS);
}
}
UserService接口
public interface UserService {
Users login(UsersLoginDTO userLoginDTO);
void register(RegisterDTO registerDTO);
}
UserServiceImpl实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public Users login(UsersLoginDTO userLoginDTO) {
String userName = userLoginDTO.getUsername();
String password = userLoginDTO.getPassword();
Users users = userMapper.getByUserName(userName);
if (users == null) {// 用户不存在
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
if(StatusConstant.DIS.equals(users.getRole())){// 账户被锁定
throw new AccountNotFoundException(MessageConstant.ACCOUNT_LOCKED);
}
password = DigestUtils.md5DigestAsHex(password.getBytes());
if(!password.equals(users.getPassword())){ // 密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
return users;
}
@Override
public void register(RegisterDTO registerDTO) {
String userName = registerDTO.getUsername();
String password = registerDTO.getPassword();
if(userName == null || password == null){
throw new AccountNotFoundException(MessageConstant.ACCOUNT_IS_NULL);
}
Users user = userMapper.getByUserName(userName);
if(user!= null){
throw new AccountNotFoundException(MessageConstant.ACCOUNT_ALREADY_EXIST);
}
Users users = Users.builder()
.username(userName)
.password(DigestUtils.md5DigestAsHex(password.getBytes()))
.name(registerDTO.getName())
.email(registerDTO.getEmail())
.role(StatusConstant.NORMAL)
.build();
userMapper.insert(users);
}
UserMapper类:
@Mapper
public interface UserMapper {
@Select("select * from users where username = #{userName}")
Users getByUserName(String userName);
@Insert("insert into users (username, password, name, email, role) values (#{username}, #{password}, #{name}, #{email}, #{role})")
void insert(Users users);
}