SpringBoot+JWT+Shiro

SpringBoot+JWT+Shiro

简介:本文讲解,如何用SpringBoot整合JWT与Shiro。

对于JWT和Shiro的讲解看这两篇文章,本文只讲解,最后的结合的代码。
使用shiro对数据库中的密码进行加密存储(java+springboot+shiro)
SpringBoot整合JWT

后端代码

项目结构

在这里插入图片描述

后端代码

pom.xml

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-crypto-hash</artifactId>
			<version>1.11.0</version>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.2</version>
		</dependency>
		<!--引入jwt-->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.4.0</version>
		</dependency>


		<!--引入mybatis-->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.3</version>
		</dependency>

		<!--引入lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.12</version>
		</dependency>

		<!--引入druid-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.19</version>
		</dependency>

		<!--引入mysql-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.32</version>
		</dependency>

	</dependencies>

Bean

User
@Data // 使用 Lombok 简化实体类代码
@AllArgsConstructor
@NoArgsConstructor
public class User {
    
    
    @TableId(type = IdType.AUTO) // 指定 ID 字段为自增主键
    private Long id;

    @TableField("username") // 指定该字段映射到数据库表中的 username 列
    private String username;

    @TableField("password") // 指定该字段映射到数据库表中的 password 列
    private String password;

    @TableField("salt") // 指定该字段映射到数据库表中的 password 列
    private byte[] salt;

    public User(String username, String password, byte[] salt) {
    
    
        this.username = username;
        this.password = password;
        this.salt = salt;
    }

    public User(String username, String password) {
    
    
        this.username = username;
        this.password = password;
    }
}


Dto

LoginDto

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {
    
    
    private String username;
    private String password;
}

Config

InterceptorConfig
/**
 * InterceptorConfig 是一个配置类,用于添加拦截器。
 * 在这个类中,我们可以配置需要拦截的接口路径以及排除不需要拦截的接口路径。
 * 在这个例子中,我们添加了JWTInterceptor拦截器来对请求进行token验证,
 * 并设置了"/user/test"接口需要进行验证,而"/user/login"接口则被排除在验证之外,即所有用户都放行登录接口。
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    
    

    /**
     * 添加拦截器配置
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/user/test", "/user/all")         // 对"/user/test"接口进行token验证
                .excludePathPatterns("/user/login", "/user/register");  // 所有用户都放行登录接口
    }
}

Result
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    
    

    private int code;
    private String message;
    private T data;

    public Result(T data) {
    
    
        this.code = 200;
        this.message = "success";
        this.data = data;
    }

    public Result(T data, boolean success, String message) {
    
    
        if (success) {
    
    
            this.code = 200;
            this.message = "success";
        } else {
    
    
            this.code = 500; // 自定义错误状态码(示例为500)
            this.message = message;
        }
        this.data = data;
    }

    public Result(int code, String message) {
    
    
        this.code = code;
        this.message = message;
        this.data = null;
    }

    public static <T> Result<T> success(String message) {
    
    
        return new Result<>(200, message);
    }
    /**
     * 返回执行失败的结果(默认状态码为500)
     *
     * @param message 提示信息
     * @return 失败的结果对象
     */
    public static <T> Result<T> fail(String message) {
    
    
        return new Result<>(500, message);
    }

    /**
     * 返回执行失败的结果(自定义状态码和提示信息)
     *
     * @param code    状态码
     * @param message 提示信息
     * @return 失败的结果对象
     */
    public static <T> Result<T> fail(int code, String message) {
    
    
        return new Result<>(code, message);
    }
}

Controller

UserController
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    
    
    @Autowired
    private IUserService userService;

    @PostMapping("/login")
    public Result<Map<String, Object>> login(@RequestBody User user) {
    
    
        // 打印用户名和密码
        System.out.println(user);

        // 创建结果对象
        Result<Map<String, Object>> result;

        try {
    
    
            // 调用userService的login方法进行用户认证
            User userDB = userService.login(user);

            // 获取用户ID和用户名,并将其放入payload
            Map<String, String> payload = new HashMap<>();
            payload.put("id", userDB.getId().toString());
            payload.put("name", userDB.getUsername());

            // 生成JWT的令牌
            String token = JWTUtils.getToken(payload);

            // 构造成功的结果对象
            result = new Result<>(200, "认证成功");
            result.setData(new HashMap<>());
            result.getData().put("token", token); // 响应token

        } catch (Exception e) {
    
    
            // 构造失败的结果对象
            result = Result.fail(500, e.getMessage());
        }

        return result;
    }

    @PostMapping("/register")
    public Result<Map<String, Object>> register(@RequestBody User user){
    
    
        System.out.println("注册");
        System.out.println(user);
        // 创建结果对象
        Result<Map<String, Object>> result;
        User user1 = userService.selectByUsername(user.getUsername());
        if (user1 != null){
    
    
            result = Result.fail(405, "用户已存在,请更换用户名");
        } else {
    
    
            boolean register = userService.register(user.getUsername(), user.getPassword());
            if (register){
    
    
                result = new Result(user);
            } else {
    
    
                result = Result.fail(500, "注册失败");
            }
        }
        return result;
    }

    @GetMapping("/all/{token}")
    Result<Map<String, Object>> getByAll(@PathVariable String token) {
    
    
        List<User> list = userService.list();
        Result<Map<String, Object>> result;
        System.out.println(token);
        try {
    
    
            Map<String, Object> map = new HashMap<>();

            // 处理自己的业务逻辑
            // 校验并解析token
            verifyToken(token);
            map.put("data", list);
            // 构造成功的结果对象
            result = new Result<>(200, "请求成功!");
            result.setData(map);

        } catch (Exception e) {
    
    
            // 构造失败的结果对象
            result = Result.fail(500, e.getMessage());
        }
        return result;
    }

    private void verifyToken(String token) throws Exception {
    
    

        // 校验并解析token
        DecodedJWT verify = JWTUtils.verify(token);

        // 可以在这里添加其他的校验逻辑
        // 打印解析出的用户id和用户名
        log.info("用户id: [{}]", verify.getClaim("id").asString());
        log.info("用户name: [{}]", verify.getClaim("name").asString());
    }



    @GetMapping("/test/{token}")
    public Result<Map<String, Object>> test(@PathVariable String token) {
    
    
        // 创建结果对象
        Result<Map<String, Object>> result;

        try {
    
    
            Map<String, Object> map = new HashMap<>();

            // 处理自己的业务逻辑

            // 从请求头中获取token
            // 校验并解析token
            DecodedJWT verify = JWTUtils.verify(token);

            // 打印解析出的用户id和用户名
            log.info("用户id: [{}]", verify.getClaim("id").asString());
            log.info("用户name: [{}]", verify.getClaim("name").asString());

            // 构造成功的结果对象
            result = new Result<>(200, "请求成功!");
            result.setData(map);

        } catch (Exception e) {
    
    
            // 构造失败的结果对象
            result = Result.fail(500, e.getMessage());
        }
        return result;
    }
}

Interceptor

JWTInterceptor
/**
 * JWTInterceptor是一个拦截器,用于验证请求头中的JWT令牌是否有效。
 * 当有请求进入时,该拦截器会首先从请求头中获取令牌,并尝试验证其有效性。
 * 如果令牌验证成功,则放行请求;否则,拦截请求并返回相应的错误信息。
 */
public class JWTInterceptor implements HandlerInterceptor {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        // 创建一个Map对象,用于存储响应信息
        Map<String, Object> map = new HashMap<>();

        // 从请求头中获取令牌
        String token = request.getHeader("token");

        try {
    
    
            JWTUtils.verify(token); // 验证令牌的有效性
            return true; // 放行请求
        } catch (SignatureVerificationException e) {
    
    
            e.printStackTrace();
            map.put("msg", "无效签名!");
        } catch (TokenExpiredException e) {
    
    
            e.printStackTrace();
            map.put("msg", "token过期!");
        } catch (AlgorithmMismatchException e) {
    
    
            e.printStackTrace();
            map.put("msg", "token算法不一致!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
            map.put("msg", "token无效!!");
        }

        map.put("state", false); // 设置状态为false

        // 将Map转化为JSON字符串(使用Jackson库)
        String json = new ObjectMapper().writeValueAsString(map);

        response.setContentType("application/json;charset=UTF-8"); // 设置响应的Content-Type
        response.getWriter().println(json); // 将JSON字符串写入响应中

        return false; // 不放行请求
    }
}

Mapper

UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    
    @Select("select * from user where username = #{username} and password = #{password}")
    User login(@Param("username") String username, @Param("password") String password);

    @Select("select * from user")
    List<User> list();
    @Select("select * from user where username = #{username}")
    User selectByUsername(@Param("username")String username);
}

Service

IUserService
public interface IUserService extends IService<User> {
    
    
    User login(LoginDto user);//登录接口
    User selectByUsername(String username); // 查询用户的名字
    boolean register(User user); // 注册接口
}


UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    
    @Autowired
    private UserMapper userMapper;
    @Override
    public User login(LoginDto user) {
    
    
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", user.getUsername());
        User users = getOne(wrapper);
        if (users == null) {
    
    
            // 如果用户不存在,则认为登录失败
            return null;
        }
        String hashedPassword = users.getPassword();
        byte[] salt = users.getSalt();

        // 对用户输入的密码进行加密处理,并将结果与数据库中的哈希值比较
        String hashedInputPassword = hash(user.getPassword(), salt);

        System.out.println(hashedPassword);
        System.out.println(salt);
        System.out.println(hashedInputPassword);

        if (hashedPassword.equals(hashedInputPassword)){
    
     // 如果密码一致
            return users;
        } else {
    
    
            return null;
        }
    }

    @Override
    public User selectByUsername(String username) {
    
    
        return userMapper.selectByUsername(username);
    }

    @Override
    public boolean register(User user) {
    
    

        // 生成盐值
        byte[] salt = new byte[16];
        new SecureRandom().nextBytes(salt);

        // 对密码进行加密处理
        String hashPassword = hash(user.getPassword(), salt);

        // 将用户名、盐值和哈希后的密码保护到数据库中
        User user1 = new User(user.getUsername(), hashPassword, salt);

        System.out.println(user1);
        boolean success = save(user1);
        System.out.println(userMapper.list());
        return success;
    }

    @Override
    public boolean saveBatch(Collection<User> entityList, int batchSize) {
    
    
        return super.saveBatch(entityList, batchSize);
    }

    private String hash(String password, byte[] salt) {
    
    
        int iterations = 10000;
        int keyLength = 256;

        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength);
        SecretKeyFactory skf;
        try {
    
    
            skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            byte[] hash = skf.generateSecret(spec).getEncoded();
            return Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
    
    
            throw new RuntimeException(e);
        }
    }

}

Utils

public class JWTUtils {
    
    


    private static final String  SING = "!Q@W3e4r%T^Y";

    /**
     * 生成token  header.payload.sing
     */
    public static String getToken(Map<String,String> map){
    
    

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认7天过期

        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k,v)->{
    
    
            builder.withClaim(k,v);
        });

        String token = builder.withExpiresAt(instance.getTime())//指定令牌过期时间
                .sign(Algorithm.HMAC256(SING));//sign
        return token;
    }

    /**
     * 验证token 合法性
     *
     */
    public static DecodedJWT verify(String token){
    
    
        return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }

//    /**
//     * 获取token信息方法
//     */
//    public static DecodedJWT getTokenInfo(String token){
    
    
//        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
//        return verify;
//    }
}

接口测试

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_51447496/article/details/132300726