SpringBoot+JWT+Shiro
简介:本文讲解,如何用SpringBoot整合JWT与Shiro。
对于JWT和Shiro的讲解看这两篇文章,本文只讲解,最后的结合的代码。
使用shiro对数据库中的密码进行加密存储(java+springboot+shiro)
后端代码
项目结构
后端代码
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; // } }
接口测试