SpringBoot+JWT+Shiro

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 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;
//    }
}

接口测试

相关文章
|
29天前
|
JSON 安全 算法
|
1月前
|
安全 Java 数据库
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
这篇文章是关于Apache Shiro权限管理框架的详细学习指南,涵盖了Shiro的基本概念、认证与授权流程,并通过Spring Boot测试模块演示了Shiro在单应用环境下的使用,包括与IniRealm、JdbcRealm的集成以及自定义Realm的实现。
44 3
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
|
29天前
|
存储 安全 Java
|
1月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
1月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
30 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
19天前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
32 0
|
3月前
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
233 0
|
6月前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
209 0
|
4月前
|
JSON 安全 Java
使用Spring Boot和JWT实现用户认证
使用Spring Boot和JWT实现用户认证
|
1月前
|
存储 JSON 算法
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
文章介绍了JWT令牌的基础教程,包括其应用场景、组成部分、生成和校验方法,并在Springboot中使用JWT技术体系完成拦截器的实现。
78 0
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)

热门文章

最新文章