解决shiro中前后端session共享问题
添加shiro的配置类。
DefaultWebSessionManager 获取请求头中JSESSION ID的值
通过RedisSessionDao 从Redis中查询该值对应的key 如果存在则认为当前用户登录
@Bean
public DefaultWebSecurityManager sexcurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm());
securityManager.setCacheManager(cacheManager());
// 设置session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
// 设置缓存管理器
@Bean
public CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager());
return cacheManager;
}
@Bean
public SessionManager sessionManager() {
/*DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();*/
MyWebSessionManager sessionManager = new MyWebSessionManager();
// SessionDao用于操作session对象,再容器中把对象session进行crud
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
@Bean
public SessionDAO sessionDAO() {
// 该类会把对象session进行crud操作
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
解决前端不支持cookie的效果
原因: 默认DefaultWebSessionManager它只接受Cookie中存储的JsessionId. 查询发现再redis中不存在对应的key.
解决原理:
客户发送请求时,再请求头中携带sessionId, 然后重写DefaultWebSessionManager中getSessionId()的方法。
把sessionId放入请求头
@RestController
/**
* @RestController是Spring4.0之后新增的注解。
* 相当于@Controller+@ResponseBody合在一起的作用。
*/
@Api(tags ="登录接口类")
public class LoginController {
@RequestMapping("/login")
@ResponseBody
public Result login(@RequestBody LoginVo loginVo){
Subject subject = SecurityUtils.getSubject();
// UsernamePasswordToken 用户名密码令牌
/**
* 具体的身份验证逻辑和配置可能因应用程序和使用的身份验证框架
*/
UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(), loginVo.getPasword());
try{
subject.login(token);
return new Result(200,"登录成功",subject.getSession().getId());
} catch (Exception e){
e.printStackTrace();
return new Result(500,"登录失败",null);
}
}
}
添加前端登录方法
<template>
<div>
<el-form label-width="80px" :model="loginForm">
<el-form-item label="账号:">
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码:">
<el-input v-model="loginForm.pasword"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login">创建</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "Login",
// 定义数据
data(){
return{
loginForm:{},
}
},
methods:{
login(){
this.$http.post("http://localhost:8080/login",this.loginForm).then(result=>{
if(result.data.code==200){
this.$message.success("登录成功");
sessionStorage.setItem("token",result.data.data);
this.$router.push("/product")
/*console.log("对对对");*/
}else{
this.$message.error("账号或密码错误");
}
})
}
}
}
</script>
<style scoped>
</style>
添加main.js文件
// 创建vue对象
// 设置axios的请求拦截器
axios.interceptors.request.use(config=>{
// 从localStorage 中 获取token值
var item = sessionStorage.getItem("token");
if (item){
config.headers.token=item;
}
return config;
})
重写DefaultWebSessionManager的方法
public class MyWebSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头中名称为token的内容
String id = WebUtils.toHttp(request).getHeader("token");
if (!StringUtils.isEmpty(id)) { //如果存在该token
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//从cookie中获取sessionId.
return super.getSessionId(request, response);
}
}
}
修改shiro配置类
@Bean
public SessionManager sessionManager() {
/*DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();*/
MyWebSessionManager sessionManager = new MyWebSessionManager();
// SessionDao用于操作session对象,再容器中把对象session进行crud
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
shiroFilter过滤器
运行发现跨域请求,会发送两个请求:第一个OPTIONS请求,第二个请求是真实的请求。
OPTIONS请求:先头部队。
所以要对OPTIONS请求都要放行。
package com.aaa.filter;
import com.aaa.vo.Result;
import com.alibaba.fastjson.JSON;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
/**
* @ClassName: LoginFilter
* @Description: TODO
* @date: 2023/7/11 16:33
* @author: zzj
* @Version: 1.0
*/
public class LoginFilter extends FormAuthenticationFilter {
//未登录访问资源时,会触发该方法。---默认内容是重定向到登录页面---重写改为返回json数据
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8"); //解决响应数据的中文乱码问题
PrintWriter writer = response.getWriter();
Result result=new Result(401,"请先登录2",null);
//吧java对象转换为json字符串---JSON
String jsonString = JSON.toJSONString(result);
writer.print(jsonString);
writer.flush();
writer.close();
return false;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,Object mappedValue){
HttpServletRequest requestRequest = (HttpServletRequest)request;
// 获取请求方式
String method = requestRequest.getMethod();
if ("OPTIONS".equals(method)){
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
}
如何设置前端前置路由守卫
在main.js文件中设置前端前置路由
每个守卫方法接收三个参数:
-
to: Route
: 即将要进入的目标 路由对象 -
from: Route
: 当前导航正要离开的路由 -
next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。 -
router.beforeEach((to,from,next) =>{ var path = to.path; if (path == "/login"){ return next(); } // 判断是否登录过 var token = sessionStorage.getItem("token"); if (token){ return next(); } return next("/login"); })
如何防止恶意重复登录
获取当前登录用户的信息,账号退出
后端退出
后端获取用户信息
前端
<template>
<div>
这是商品的页面
<el-button type="primary" @click="logout">退出</el-button>
<el-button @click="info">获取用户信息</el-button>
<span v-text="userinfo.username"></span>
</div>
</template>
<script>
export default {
name: "Product",
data(){
return{
userinfo:{},
}
},
created(){
},
methods:{
info(){
this.$http.get("http://localhost:8080/user/info").then(result=>{
this.userinfo=result.data.data;
})
},
logout(){
this.$http.post("http://localhost:8080/logout").then(result=>{
console.log(result)
if (result.data.code==200){
this.$message.success("退出成功");
// 清空sessionStorage
sessionStorage.clear();
this.$router.push("/login")
}
})
}
}
}
</script>
<style scoped>
</style>