登录注册功能开发

本篇文章记录怎么实现一个简单的登陆注册功能。
讲解里的代码是不完全的,具体的代码我会放在文章最后

准备

要实现登录注册功能,首先我们就要有用户表来记录用户的数据。
我们的用户表大概就是这样的:
有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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泉绮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值