Spring boot整合shiro问题400或者302
这个问题给我整了一周才解决
记录时间:
年 | 月 | 日 | 时间 | 星期 |
---|---|---|---|---|
2024 | 8 | 1 | 15:30 | 星期日 |
这里使用的是前后端分离
spring boot
vite vue
疑惑一:为什么400状态了呢?
个人认为首先和重定向没有关系,根前端发送请求格式有关系
接下来看代码,搞了一周才发现不是我shiro的问题是我自己的问题,在研究过程中我发现虽然知道在shiro重定向于重定向之前有关系,但是我依然找不出问题,查阅了很多的帖子/博客/csdn/腾讯云社区等等都没有找到满意的答案,后来我自己又狠下心仔仔细细的检查了一遍,通过对通义千问的依赖和帮助,最后发现了不对的地方,这是我的代码。
后端项目代码
Maven依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hejiu45</groupId>
<artifactId>spring-shiro-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-shiro-1</name>
<description>spring-shiro-1</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<classifier>jakarta</classifier>
<version>1.13.0</version>
<!-- 排除仍使用了javax.servlet的依赖 -->
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入适配jakarta的依赖包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<classifier>jakarta</classifier>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<classifier>jakarta</classifier>
<version>1.13.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- <dependency>-->
<!-- <groupId>javax.servlet</groupId>-->
<!-- <artifactId>javax.servlet-api</artifactId>-->
<!-- <version>4.0.1</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
ShiroConfig
package com.hejiu45.springshiro1.config;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* shiro 配置类
*/
@Configuration
public class ShiroConfig {
// shiro filter 工厂
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// CustomShiroFilterFactoryBean factoryBean = new CustomShiroFilterFactoryBean();
// 给拦截器设置安全管理器
factoryBean.setSecurityManager(securityManager);
Map<String, String> filterMap = new HashMap<>();
// 系统公共资源
filterMap.put("/test/scuer", "anon");
filterMap.put("/test/scuer1", "anon");
filterMap.put("/test/demo1", "anon");
filterMap.put("/login", "anon");
// 系统受限资源
filterMap.put("/**", "authc"); // authc是受限资源拦截 anon
filterMap.put("/test/demo", "authc");
// 自定义登录页
factoryBean.setLoginUrl("/login");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
// 安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 给安全管理器一个realm
securityManager.setRealm(realm);
return securityManager;
}
// 自定义realm
@Bean
public Realm getRealm(){
ShiroRealm shiroRealm1 = new ShiroRealm();
return shiroRealm1;
}
}
ShiroRealm
package com.hejiu45.springshiro1.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class ShiroRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
TestController
package com.hejiu45.springshiro1.controller;
import com.hejiu45.springshiro1.entity.Test;
import com.hejiu45.springshiro1.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("demo")
public Result demo() {
log.error("----------------------->demo<-----------------------");
return new Result(false, 400, "被拦截了");
}
// @PostMapping("scuer")
// public Result scuer(@RequestBody Test test) {
// log.error("----------------------->scuer<-----------------------");
// log.error("----------uname---------->[{}]<-----------------------", test.getUname());
// log.error("----------upasswd---------->[{}]<-----------------------", test.getUpasswd());
// return new Result(true, 200, "成功进入");
// }
@PostMapping("scuer")
public Result scuer(@RequestParam String uname, @RequestParam String upasswd) {
log.error("----------------------->scuer<-----------------------");
log.error("----------uname---------->[{}]<-----------------------", uname);
log.error("----------upasswd---------->[{}]<-----------------------", upasswd);
return new Result(true, 200, "成功进入");
}
@GetMapping("demo1")
public Result demo1(@RequestParam String uname, @RequestParam String upasswd) {
log.error("----------------------->demo<-----------------------");
log.error("----------uname---------->[{}]<-----------------------", uname);
log.error("----------upasswd---------->[{}]<-----------------------", upasswd);
return new Result(false, 400, "被拦截了");
}
@PostMapping("scuer1")
public Result scuer1() {
log.error("----------------------->scuer<-----------------------");
return new Result(true, 200, "成功进入");
}
}
Entity
package com.hejiu45.springshiro1.entity;
import lombok.Data;
/**
* 没有链接数据库
* 只是为了给@RequestBody 提供类
*/
@Data
public class Test {
private String uname;
private String upasswd;
}
Utils
package com.hejiu45.springshiro1.utils;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Response 数据格式类
*/
@Data
@NoArgsConstructor
public class Result <T> {
private Boolean flag;
private Integer code;
private String mssege;
private T result;
public Result(Boolean flag, Integer code, String mssege, T result) {
this.flag = flag;
this.code = code;
this.mssege = mssege;
this.result = result;
}
public Result(Boolean flag, Integer code, String mssege) {
this.flag = flag;
this.code = code;
this.mssege = mssege;
}
}
前端项目代码
前端项目我使用的是js于vite脚手架于axios和postman做的测试,接下来看前端的代码
Vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 8081,
open: '/',
// 本地代理
proxy: {
// '/api': 'http://localhost:45',
// 带选项写法:http://localhost:5173/api/bar -> http://jsonplaceholder.typicode.com/bar
'/api': {
target: 'http://localhost:45',
// changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
// atcors: false
},
},
// 路径别名设置
resolve: {
alias: {
'@': resolve(__dirname, './src'), // 将 @ 符号指向项目的 src 目录
},
},
})
Package.json
{
"name": "vue-spring-shiro",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.7.2",
"vue": "^3.4.21"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0"
}
}
Components Vue
<template>
<div>
<p>登录页面</p>
<div>
账号:
<input type="text" v-model="uname">
密码:
<input type="text" v-model="upasswd">
</div>
<div>
<button @click="getLogin1">登录1</button>
<button @click="getLogin2">登录2</button>
<button @click="getLogin3">登录3</button>
<button @click="getLogin4">登录4</button>
</div>
</div>
</template>
<script setup>
import axios from 'axios';
import { ref } from 'vue'
let uname = ref()
let upasswd = ref()
function getLogin1() {
let url = "http://localhost:8081/api/test/scuer"
axios.get(url, {
params: {
uname: uname.value,
upasswd: upasswd.value
}
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
}
function getLogin2() {
let url = "http://localhost:8081/api/test/scuer"
axios.post(url, {
// params: {
uname: uname.value,
upasswd: upasswd.value
// }
},
{
headers: {
// 'Content-Type': 'application/json'
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
}
function getLogin3() {
let url = "http://localhost:8081/api/test/scuer1"
axios.post(url)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
}
function getLogin4() {
let url = "http://localhost:8081/api/test/demo1"
axios.get(url, {
params: {
uname: uname.value,
upasswd: upasswd.value
}
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
}
</script>
<style scoped>
</style>
现在来说400的问题
我遇到的400问题是,我发现在@PostMapping 与 @RequestParam 与 @RequestBody这几个注解有关系,首先在我测试的4种情况:
请求方式 | 状态码 | 使用注解 | 有无参数 | 参数 |
---|---|---|---|---|
GET | 200 | @GetMapping | 无参 | |
GET | 200 | @GetMapping与@RequestParam 与 @RequestBody | 有参数 | uname/upasswd |
POST | 200 | @PostMapping | 无参 | |
POST | 400 | @PostMapping与@RequestParam 与 @RequestBody | 有参数 | uname/upasswd |
这就是测试结果,我也发现为什么post有参会400,就是因为前端发送的请求头中的content-type格式不同,就像我的vue文件中的写的第二个方法一样:
function getLogin2() {
let url = "http://localhost:8081/api/test/scuer"
axios.post(url, {
// params: {
uname: uname.value,
upasswd: upasswd.value
// }
},
{
headers: {
// 'Content-Type': 'application/json'
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
}
当我带有headers: {
// ‘Content-Type’: ‘application/json’
‘Content-Type’: ‘application/x-www-form-urlencoded’
}时我的请求就是200,当我不带时就是400.
在然后如果使用的注解是@RequestBody你带还是不带他都200
如果使用的注解是@RequestParam你不带他都400
其次就是不可以在使用注解@RequestParam时前端使用这种方式发送参数:
function getLogin2() {
let url = "http://localhost:8081/api/test/scuer"
axios.post(url, {
params: {
uname: uname.value,
upasswd: upasswd.value
}
},
{
headers: {
// 'Content-Type': 'application/json'
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
}
最终的问题是因为@RequestBody与@RequestParam的不同:AI回答
问题分析
-
使用
@RequestParam
:- 当你在前端使用
axios.post()
方法发送请求时,如果没有明确指定请求体的内容类型(例如Content-Type: application/x-www-form-urlencoded
),默认情况下,axios
会将数据序列化为JSON格式。 - 如果你的后端控制器使用
@RequestParam
来获取参数,它会在URL查询字符串中查找这些参数。在这种情况下,你发送的JSON数据不会被正确解析,因此会导致400 Bad Request错误。
- 当你在前端使用
-
使用
@RequestBody
:-
当你使用
@RequestBody
注解时,Spring会尝试将请求体中的JSON数据反序列化为Java对象。 -
如果前端发送的数据格式与后端控制器中的
@RequestBody
对象匹配,则请求会被正确处理,并且返回200 OK状态码。
-
疑惑二:为什么302状态了呢?
第一可能是跨域引起的,第二可能是你前端请求地址写错了,第三仔细检查,实在不行就配置后端解决跨域问题