JWT(Token令牌)整合 SpringBoot

 

前言:

  JWT(JSON Web Token)可以被称为令牌(token)。JWT是一种在网络应用中广泛使用的令牌格式,用于在用户和服务器之间传递安全可靠的信息。JWT通常包含了用户的身份信息和一些其他的元数据,被用作身份验证和授权。因此,人们经常将JWT简称为令牌(token)。

 

代码整合:

  

  1. 导入依赖

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

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

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

        <!-- javax.xml.bind.DatatypeConverter 在Java9 版本后加 ->-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

        <!-- 简化实体类 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>

        <!-- StringUtils等工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

        <!--hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.0.0</version>
        </dependency>

    </dependencies>

  

  2. 代码

  1) pojo层

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {

    @TableId(type = IdType.AUTO)
    private Integer id;
    @TableField("username")
    private String username;
    @TableField("password")
    private String password;
    @TableField("age")
    private String age;
    @TableField("nick")
    private String nick;
    @TableField("icon")
    private String icon;

}

  2) mapper/dao层

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

  3) model层

public class Result<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 成功标志
     */
    private boolean success = true;

    /**
     * 返回处理消息
     */
    private String message = "操作成功!";

    /**
     * 返回代码
     */
    private Integer code = 0;
    
    /**
     * 返回数据对象 data
     */
    private T result;

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * 时间戳
     */
    private long timestamp = System.currentTimeMillis();

    public Result() {
        
    }
    
    public Result<T> success(String message) {
        this.message = message;
        this.code = 200;
        this.success = true;
        return this;
    }
    
    
    public static Result<Object> ok() {
        Result<Object> r = new Result<Object>();
        r.setSuccess(true);
        r.setCode(200);
        r.setMessage("成功");
        return r;
    }
    
    public static Result<Object> ok(String msg) {
        Result<Object> r = new Result<Object>();
        r.setSuccess(true);
        r.setCode(200);
        r.setMessage(msg);
        return r;
    }
    
    public static Result<Object> ok(Object data) {
        Result<Object> r = new Result<Object>();
        r.setSuccess(true);
        r.setCode(200);
        r.setResult(data);
        return r;
    }
    
    public static Result<Object> error(String msg) {
        return error(500, msg);
    }
    
    public static Result<Object> error(int code, String msg) {
        Result<Object> r = new Result<Object>();
        r.setCode(code);
        r.setMessage(msg);
        r.setSuccess(false);
        return r;
    }

    public Result<T> error500(String message) {
        this.message = message;
        this.code = 500;
        this.success = false;
        return this;
    }
    /**
     * 无权限访问返回结果
     */
    public static Result<Object> noauth(String msg) {
        return error(555, msg);
    }
}

  4) service/impl层

public interface IUserService {

    // 注册
    Result register(User user);

    // 登录
    Result login(String username, String password);

}
@Service
@Transactional  // 在Service层使用,以确保Service层方法中的数据库操作在一个事务内执行
public class UserServiceImpl implements IUserService {

    @Resource
    private UserMapper userMapper;

    /**
     * 注册
     * @param user
     * @return      // N:java_bei P:bei2024..
     */
    @Override
    public Result register(User user) {

        // 判断用户名格式与是否被注册
        String username = user.getUsername();
        // 判断用户名是否被注册
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        if (userMapper.selectOne(queryWrapper) != null) {
            return Result.error("此用户名已被注册.");
        }

        // 判断用户名格式
        if (!RegexUtil.checkUsername(username)) {
            return Result.error("用户名格式有误.");
        }


        // 判断密码格式
        String userPassword = user.getPassword();
        if (!RegexUtil.checkPassword(userPassword)) {
            return Result.error("密码格式有误.");
        }

        // 密码加密
        user.setPassword(MD5Util.MD5Encode(user.getPassword(),"utf-8"));
        int insertUser = userMapper.insert(user);

        return insertUser > 0 ? Result.ok("注册成功.") : Result.error("注册失败.");
    }

    /**
     * 登录
     * @param username
     * @param password
     * @return
     */
    @Override
    public Result login(String username, String password) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        queryWrapper.eq("password", MD5Util.MD5Encode(password,"utf-8"));
        User userMatch = userMapper.selectOne(queryWrapper);
        // 加上JWT(token)
        userMatch.setPassword(null);
        if (userMatch != null) {
            String jsonString = JSON.toJSONString(userMatch);
            String token = JwtUtil.createJWT(userMatch.getId().toString(), jsonString, 7 * 24 * 2600 * 1000L);
            return Result.ok(token);
        }
        return Result.error("用户名或密码有误.");
    }
}

  5) controller层

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private IUserService iUserService;

    /**
     * 注册
     * @param user
     * @return
     */
    @PostMapping("/register")
    public Result register(User user) {
        return iUserService.register(user);
    }

    /**
     * 登录
     * @param username
     * @param password
     * @return
     */
    @PostMapping("/login")
    public Result login(String username, String password) {
        return iUserService.login(username, password);
    }

    /**
     * 登录后给予权限跳转主页
     * @param username
     * @param password
     * @return
     */
    @PostMapping("/home")
    public Result home( String username,String password){
        return Result.ok("首页");
    }
}

 

  3. 工具类

  utils包:

  JWT工具类:

/**
 * 创建和解析JWT,可以在用户登录时生成JWT,并在需要验证用户身份时解析JWT来获取用户信息。
 */
public class JwtUtil {
    // token 有效期
    public static final Long JWT_TTL = 3600000L;    // 60 * 60 * 1000  一个小时

    // Jwt令牌信息
    public static final String JWT_KEY = "mysql20240224";

    // 创建token方法
    public static String createJWT(String id, String subject, Long ttlMillis) {     // id, 信息, 过期时间
        // 指定算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 当前系统时间
        long nowMillis = System.currentTimeMillis();
        //令牌签发时间
        Date now = new Date(nowMillis);
        // 如果令牌有效期为null,则默认设置有效期1小时
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }
        // 令牌过期时间设置
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);

        // 生成秘钥
        SecretKey secretKey = generalKey();

        // 封装Jwt令牌信息
        JwtBuilder builder = Jwts.builder()
                .setId(id)                    // 唯一的ID
                .setSubject(subject)          // 主题  可以是JSON数据
                .setIssuer("admin")          // 签发者
                .setIssuedAt(now)             // 签发时间
                .signWith(signatureAlgorithm, secretKey) // 签名算法以及密匙
                .setExpiration(expDate);      // 设置过期时间
        // builder.addClaims(map); 可以放一些额外信息;
        return builder.compact();
    }

    /**
     * 生成加密 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes()); // 转为字节码
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    /**
     * 解析令牌数据
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

}

  MD5加密:

/**
 * @description: MD5加密工具类
 */
public class MD5Util {

    // 将字节数组转换为对应的十六进制字符串。
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    // 将单个字节转换为对应的两个十六进制字符
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0) {
            n += 256;
        }
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }


    // 接收一个字符串和字符集名称作为参数,对输入的字符串进行MD5加密,并返回加密后的结果。
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname)) {
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            } else {
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
            }
        } catch (Exception exception) {

        }
        return resultString;
    }

    // 这个数组用于将字节转换为十六进制字符。
    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};


    public static void main(String[] args) {
        System.out.println(MD5Util.MD5Encode("123", "UTF-8"));
    }
}

  正则表达式:

public class RegexUtil {

        // 用户名的正则表达式:4至8位,只能为单独的英文或中文(中文的话为2至4位),英文可以加下划线
        private static final String USERNAME_REGEX = "^([a-zA-Z_]{4,8}|[\\u4E00-\\u9FA5]{2,4})$";

        // 密码的正则表达式:6至12位,包含字母、数字、特殊字符
        private static final String PASSWORD_REGEX = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$%^&*()_+\\-={}\\[\\]:;\"'<>,.?/\\\\]).{6,12}$";

        // 检查用户名是否匹配正则表达式
        public static boolean checkUsername(String username) {
            Pattern pattern = Pattern.compile(USERNAME_REGEX);
            Matcher matcher = pattern.matcher(username);
            return matcher.matches();
        }

        // 检查密码是否匹配正则表达式
        public static boolean checkPassword(String password) {
            Pattern pattern = Pattern.compile(PASSWORD_REGEX);
            Matcher matcher = pattern.matcher(password);
            return matcher.matches();
        }

        public static void main(String[] args) {
            // 测试用户名
            String[] usernames = {"abcd", "abcd123", "abcdABCD", "abcd中文", "abcd12中文", "java@zhanmusi", "Hello,World", "张三", "二零二四"};
            for (String username : usernames) {
                System.out.println(username + " is valid: " + checkUsername(username));
            }

            // 测试密码
            String[] passwords = {"abc123", "abc@123", "abc123ABC", "abcABC@123", "abc"};
            for (String password : passwords) {
                System.out.println(password + " is valid: " + checkPassword(password));
        }
    }
}

 

  4. 拦截器配置

  config包:

/**
 * 配置类主要用于配置静态资源的访问路径以及注册拦截器,其中拦截器可以用于实现登录验证、权限控制等功能。
 * 在这里,拦截器 loginInterceptor 可能用于检查用户的登录状态,对需要登录的请求进行拦截和处理。
 */

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    private static final List<String> EXCLUDE_PATH = Arrays.asList("/user/login","/user/register", "/login.html","/register.html");

    @Autowired
    LoginInterceptor loginInterceptor;

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**");
    }

    // 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//拦截的路径
                .excludePathPatterns(EXCLUDE_PATH);
    }

}

  拦截器:

  interceptor包

/**
 * 拦截器:拦截器主要用于实现对请求的权限控制,检查用户是否已经登录(携带有效的 token),如果没有登录则拒绝访问,并返回相应的提示信息。
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    // 带 token --> 进; 不带 --> 禁
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        if(StringUtils.isNoneBlank(token)){
            Claims claims = JwtUtil.parseJWT(token);
            if(claims!=null){
                return true;
            }
        }
        HashMap<String, Object> map = new HashMap<>();
        map.put("msg", "禁止访问");
        map.put("status", 403);
        response.setContentType("application/json;charset=UTF-8");
        String s = new ObjectMapper().writeValueAsString(map);
        response.getWriter().println(s);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

  

  4. 页面及配置

  

   application.properties:

#???
server.port=8080

#????
spring.application.name=Java_jwt

#???
# server.servlet.context-path=/dispatch-nlp

# Redis?????????0?
#?????????0
#spring.redis.database=0
#????????????8
#spring.redis.jedis.pool.max-active=600
#??????????????????????
#spring.redis.jedis.pool.max-wait=10000
#?????????????8
#spring.redis.jedis.pool.max-idle=100
#??????(??)
#spring.redis.timeout=10000

#????????
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.tomcat.uri-encoding=UTF-8

#-----------------------------Mysql????
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
#mybatis.config-location=classpath:mybatis-config.xml
mybatis-plus.type-aliases-package=com.bei.model
mybatis-plus.mapper-locations=classpath:mapper/*.xml


#-----------------------------Mysql
spring.datasource.url=jdbc:mysql://localhost:3306/mysql20240224?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=ll163.cn

  register(注册):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册2-3</title>
</head>
<body>
    <form action="/user/register" method="post"><br>
        账户:<input type="text" name="username"/><br>
        密码:<input type="password" name="password"><br>
        年龄:<input type="text" name="age"><br>
        昵称:<input type="text" name="nick"><br>
        <input type="submit" value="注册"><br>
    </form>
</body>
</html>

  login(登录):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录1-1</title>
</head>
<body>
<form action="/user/login" method="post"><br>
    账户:<input type="text" name="username"/><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

 

  浏览器访问登录:

  

  Postman模拟token:

  

 

  流程:登录时等成JWT(token),返回给前端,前端拿到后放到请求头,前端的每次请求都可以带token到请求头;判断是否带上token,并解析(带-进,不带-禁);每次访问都由拦截器拦截。

 

posted @ 2024-02-24 19:22  学Java的`Bei  阅读(260)  评论(0编辑  收藏  举报