简单注册案例(用户权限验证)

1.注册流程

 2.数据库表的设计

分析:

假设一个用户只有一个角色,但一个角色可以有多个用户,则用户与角色之间是多对一的关系

一个角色可以有多个权限,一个权限可以有多个角色,角色与权限之间是多对多的关系

综上:可以创建四张表,用户表、角色表、权限表以及角色和权限之间关系的中间表

 

  

 3.创建视图

查询用户的权限需要进行四表连接,因此做一个视图表便于查询,(可以把四表连接查询的select语句放入视图创建工具中,会自动生成好创建视图的语句)

 4.创建maven项目,在pom文件中导入需要的包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wnhz.auth</groupId>
    <artifactId>springboot-privs-token</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.druid.version>1.2.18</project.druid.version>
        <project.mybatis.plus.version>3.5.3.1</project.mybatis.plus.version>
        <project.knife4j.version>4.1.0</project.knife4j.version>
    </properties>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.14</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${project.druid.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${project.mybatis.plus.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>${project.knife4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.22.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.otter</groupId>
            <artifactId>canal.client</artifactId>
            <version>1.1.0</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>
    </dependencies>

</project>

5.配置yml文件

server:
  port: 10002

spring:
  mvc:
    format:
      date: yyyy-MM-dd
  jackson:
    date-format: yyyy-MM-dd
  application:
    name: privs-token
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.21.129:3306/bookdata?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123
  redis:
    database: 0
    host: 192.168.21.129
    port: 6379

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

knife4j:
  enable: true

#项目日志
logging:
  level:
    com.wnhz.auth: debug

pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql






6.创建实体类

package com.wnhz.auth.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user_privs_view")
public class UserPrivs {
    @TableField("user_username")
    private String username;

    @TableField("user_password")
    private String password;

    @TableField("privs_name")
    private String privsname;
}

7.实现dao层接口

package com.wnhz.auth.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wnhz.auth.entity.UserPrivs;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface IUserPrivsDao extends BaseMapper<UserPrivs> {
}

8.创建dto的结果返回类

package com.wnhz.auth.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;
import java.util.Date;

@ApiModel("DTO返回数据")
@Getter
@ToString
@Setter
public class HttpResp<T> implements Serializable {

    private ResultCode resultCode;
    @ApiModelProperty("测试时间")
    @JsonFormat(timezone = "GMT+8")
    private Date time;
    @ApiModelProperty("测试结果")
    private T results;

    private HttpResp(){}

    public static <T> HttpResp <T> result(ResultCode resultCode,Date time,T results){
        HttpResp httpResp = new HttpResp();
        httpResp.setResultCode(resultCode);
        httpResp.setTime(time);
        httpResp.setResults(results);
        return httpResp;
    }
}
package com.wnhz.auth.dto;

import com.fasterxml.jackson.annotation.JsonFormat;

/**
 * 绑定信息和对应的代码
 */
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ResultCode{
    BOOK_TYPE_SUCCESS(20010,"图书类型操作成功"),
    PUBLISHER_SUCCESS(20100,"出版社操作成功"),
    BOOK_SUCCESS(21000,"图书操作成功"),
    USER_REGISTER_ERROR(10010,"注册失败"),
    USER_REGISTER_SUCCESS(10001,"注册成功"),
    GOODS_REDISSION_SUCCESS(00001,"REDISSION测试"),
    USER_LOGIN_SUCCESS(30001,"用户登录成功"),
    USER_NOT_LOGIN_ERROR(41000,"用户没有登录"),
    USER_QUERY_SUCCESS(30003,"用户查询成功"),
    USER_ADD_SUCCESS(10001,"用户添加成功"),
    USER_ADD_ERROR(10002,"用户添加失败"),
    USER_CODE_CREATE_SUCCESS(00002,"创建验证码成功"),
    USER_CODE_SUCCESS(0003,"验证成功"),
    USER_CODE_ERROR(0004,"验证失败"),
    USER_CODE_NULL(0003,"验证码为空"),
    USER_LOGIN_TOKEN_EXPIRED_ERROR(42000,"token异常"),
    USER_PRIVILEGES_ERROR(45002,"权限异常")


    ;

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }


    private int code;
    private String msg;

    ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

9.创建util层工具类

JwtUtil是对创建token和解析token的一个工具类
package com.wnhz.auth.util;

import cn.hutool.crypto.digest.DigestUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtil {

    private final static String key= DigestUtil.bcrypt("apple");
    private static Jws<Claims> jws;
    /**
     * 创建token字符串
     * @param username 用户名
     * @param privs 权限名称
     * @param expiredTime 过期时间
     * @return
     */
    public static String createToken(String username,String privs,long expiredTime){
        String token = Jwts.builder().setHeaderParam("alg","SHA256")
                .setHeaderParam("type","JWT")
                .claim("username",username)
                .claim("privs",privs)
                .setExpiration(new Date(System.currentTimeMillis()+expiredTime))
                .signWith(SignatureAlgorithm.HS256,key.getBytes())
                .compact();
        return token;
    }

    /**
     * 解析令牌
     * @param token
     */
    public static void parasToken(String token){
        jws =  Jwts.parser().setSigningKey(key.getBytes())
                .parseClaimsJws(token);
    }

    /**
     * 取出claim中的值
     * @param token
     * @param key---->username/privs
     * @return
     */
    public static String getClaim(String token,String key){
        parasToken(token);
        return (String) jws.getBody().get(key);
    }
}
PrivsCheck自己定义一个注解
package com.wnhz.auth.util;

import java.lang.annotation.*;


/**
 * 自己定义一个注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PrivsCheck {
    String value() default "";
}

10.实现service层

service的接口

package com.wnhz.auth.service;

import com.wnhz.auth.entity.UserPrivs;

import java.util.List;

public interface IUserService {

    List<UserPrivs> login(String username,String password);

    List<String> findAllUsernames();
}

实现servece接口类

package com.wnhz.auth.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wnhz.auth.dao.IUserPrivsDao;
import com.wnhz.auth.entity.UserPrivs;
import com.wnhz.auth.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.dc.pr.PRError;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private IUserPrivsDao userPrivsDao;

    @Override
    public List<UserPrivs> login(String username, String password) {
        QueryWrapper<UserPrivs> wrapper = new QueryWrapper<>();
        wrapper.eq("user_username",username)
                .eq("user_password",password);
        return userPrivsDao.selectList(wrapper);
    }

    @Override
    public List<String> findAllUsernames() {
        List<String> usernames = new ArrayList<>();
        userPrivsDao.selectList(null).forEach(
                u->{usernames.add(u.getUsername());}
        );
        return usernames;
    }
}

11.实现controller层

package com.wnhz.auth.controller;


import com.wnhz.auth.dto.HttpResp;
import com.wnhz.auth.dto.ResultCode;
import com.wnhz.auth.entity.UserPrivs;
import com.wnhz.auth.service.IUserService;
import com.wnhz.auth.util.JwtUtil;
import com.wnhz.auth.util.PrivsCheck;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Api(tags = "用户Api接口")
@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private IUserService ius;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @ApiOperation(value = "findAllUsernames",tags = "查询所有用户名接口")
    @GetMapping("/findAllUsernames")
    @PrivsCheck("findAllUsernames") //自己定义的注解,value值为权限名称,如何和数据库里的权限名称一致,说明有这个权限,数据库没有,则无权限
    public HttpResp findAllUsernames(){
        List<String> allUsernames = ius.findAllUsernames();
        return HttpResp.result(ResultCode.USER_QUERY_SUCCESS,new Date(),allUsernames);
    }

    @ApiOperation(value = "login",tags = "用户登录接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username",value = "用户名",required = true),
            @ApiImplicitParam(name = "password",value = "密码",required = true)
    })
    @GetMapping("/login")
    public HttpResp login(String username, String password, HttpServletResponse response){
        List<UserPrivs> users = ius.login(username,password);
        //字符穿拼接,获得登录用户的权限名称,用逗号隔开
        StringBuilder sb = new StringBuilder();
        users.forEach(u->sb.append(u.getPrivsname()+","));
        sb.deleteCharAt(sb.length()-1);//去除最后一个多余的逗号

        //创建token令牌
        String token = JwtUtil.createToken(username,sb.toString(),1000*60);
        stringRedisTemplate.opsForValue().set(username,token,3, TimeUnit.MINUTES);

        response.addHeader("token",token);


        return HttpResp.result(ResultCode.USER_LOGIN_SUCCESS,new Date(),username);
    }
}

12.实现exception层(异常)

package com.wnhz.auth.exception;

/**
 * 权限异常
 */
public class AccesRefusedException extends RuntimeException{
    public AccesRefusedException(String message){
        super(message);
    }
}
package com.wnhz.auth.exception;

/**
 * token的异常
 */
public class UserLoginExpiredException extends  RuntimeException{

    public UserLoginExpiredException(String message) {
        super(message);
    }
}
package com.wnhz.auth.exception;

/**
 * 用户未登录异常
 */
public class UserNotLoginException extends RuntimeException {
    public UserNotLoginException(String message){
        super(message);
    }
}

异常实现

package com.wnhz.auth.exception.handler;

import com.wnhz.auth.dto.HttpResp;
import com.wnhz.auth.dto.ResultCode;
import com.wnhz.auth.exception.AccesRefusedException;
import com.wnhz.auth.exception.UserLoginExpiredException;
import com.wnhz.auth.exception.UserNotLoginException;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Date;

@Slf4j
@RestControllerAdvice
public class UserAutnExceptionHandler {


    /**
     * 用户没有登录异常
     */
    @ExceptionHandler(UserNotLoginException.class)
    public HttpResp<String> userNotLogin(UserNotLoginException e){
        return HttpResp.result(ResultCode.USER_NOT_LOGIN_ERROR,new Date(),e.getMessage());
    }

    /**
     * token过期
     * @param e
     * @return
     */
    @ExceptionHandler(UserLoginExpiredException.class)
    public HttpResp<String> userLoginExpired(UserLoginExpiredException e){
        return HttpResp.result(ResultCode.USER_LOGIN_TOKEN_EXPIRED_ERROR,new Date(),e.getMessage());
    }

    /**
     * token篡改
     * @param e
     * @return
     */
    @ExceptionHandler(SignatureException.class)
    public HttpResp<String> signatureException(SignatureException e){
        return HttpResp.result(ResultCode.USER_LOGIN_TOKEN_EXPIRED_ERROR,new Date(),e.getMessage());
    }

    /**
     * 权限异常
     * @param e
     * @return
     */
    @ExceptionHandler(AccesRefusedException.class)
    public HttpResp<String> accesRefusedException(AccesRefusedException e){
        return HttpResp.result(ResultCode.USER_PRIVILEGES_ERROR,new Date(),e.getMessage());
    }
}

13.实现interceptor层(拦截)

package com.wnhz.auth.interceptor;

import cn.hutool.jwt.JWTUtil;
import com.wnhz.auth.dto.ResultCode;
import com.wnhz.auth.exception.AccesRefusedException;
import com.wnhz.auth.exception.UserNotLoginException;
import com.wnhz.auth.util.JwtUtil;
import com.wnhz.auth.util.PrivsCheck;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 定义一个拦截器,获取token
 * 1.token不存在,返回USER_NOT_LOGIN_ERROR,表示用户没有登录
 * 2.token存在,正常情况利用handler和method获取所有方法,利用自定义的注解获得权限内容,与该用户的权限privs对比判断是否权限足够
 * 如果有此权限则正常登录,没有则返回USER_PRIVILEGES_ERROR,表示权限异常
 * 3.token存在,但过期了,如果redis里token任未过期,则重新创建一个token续期,利用JWTUtil.parseToken(token).getPayload().getClaim
 * 任可以获得token的数据
 * 如果redis里token也过期了,则抛出异常
 */
@Component
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        response.setContentType("text/html;charset=utf-8");
        if (Objects.isNull(token)) {
            throw new UserNotLoginException(ResultCode.USER_NOT_LOGIN_ERROR.getMsg());
        }
        try {//正常情况
            String username = JwtUtil.getClaim(token, "username");
            String privs = JwtUtil.getClaim(token, "privs");
            //判断是否能够范围当前的方法,java去反射
            log.debug("用户权限:{}" , privs);
            log.debug("handler: {}", handler.getClass());

            HandlerMethod m = (HandlerMethod) handler;
            Method method = m.getMethod();

            log.debug("method: {}",method);//获得所有方法

            PrivsCheck annotation = method.getAnnotation(PrivsCheck.class); //拿到方法上的注解,获取注解中的value值
            System.out.println("------>" + annotation.value());

            //contains:字符串匹配,如果privs中的字符串包含了annotation.value()的值,则返回true
            if (privs.contains(annotation.value())) {//有此权限
                return true;
            } else {
               throw new AccesRefusedException(ResultCode.USER_PRIVILEGES_ERROR.getMsg());
            }
        }
        catch (ExpiredJwtException e) {  //token过期
            //续期
            System.out.println(JWTUtil.parseToken(token).getPayload().getClaim("username"));
            String username = (String) JWTUtil.parseToken(token).getPayload().getClaim("username");
            String tprivs = (String) JWTUtil.parseToken(token).getPayload().getClaim("privs");
            if (stringRedisTemplate.opsForValue().get(username)!=null){//redis的token没有过期
                String nToken = JwtUtil.createToken(username, tprivs, 1000 * 30);
                stringRedisTemplate.opsForValue().set(username, nToken, 3, TimeUnit.MINUTES);
                response.setHeader("token", nToken);
                return true;
            }
            else {
                throw new UserNotLoginException("用户登录凭证失效");
            }
        }

    }
}

14.实现config层(调用拦截)

package com.wnhz.auth.config;

import com.wnhz.auth.interceptor.AuthInterceptor;
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 AuthConfig implements WebMvcConfigurer {
    @Autowired
    private AuthInterceptor authInterceptor;

    /**
     * 调用自己定义的拦截,拦截住api下的所有路径,除了user/login未拦截
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/user/login");
    }
}

15.运行类

package com.wnhz.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AuthApp {
    public static void main(String[] args) {
        SpringApplication.run(AuthApp.class);
    }
}

16.代码结构图

代码的一些解释请看代码中的注解

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值