目录
2.2 重写DefaultWebSessionManager的方法
1.如何解决session共享问题
默认session存储再各自服务的内存中,可以让session统一存储再redis中。
疯狂的蛋糕的依赖。---提供了redis存储session的类。
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.3.1</version>
</dependency>
(1)修改shiro的配置类。
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(myRealm());
securityManager.setCacheManager(cacheManager());
//设置session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(setSessionDao());
return sessionManager;
}
@Bean
public SessionDAO setSessionDao(){
//该类会对session进行插入到操作
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
@Bean
public RedisManager redisManager(){
RedisManager manager = new RedisManager();
manager.setHost("192.168.214.129:6379");
manager.setDatabase(1);
return manager;
}
解决思路:
DefaulWrbSessionManager获取请求头中JSESSION的值,通过RedisSessionDao从redis中查询该值对应的key,如果存在则认为当前用户登录。
2. 解决前端不支持cookie的效果
当登陆成功,点击相应的操作会出现跨域问题。
原因: 默认DefaultWebSessionManager它只接受Cookie中存储的JsessionId. 查询发现再redis中不存在对应的key.
如何解决:
客户发送请求时,再请求头中携带sessionId, 然后重写DefaultWebSessionManager中getSessionId()的方法。
思考:1. 如何把sessionId放入请求头。
2. 重写getSessionId方法如何获取请求头的sessionID。
2.1 如何把sessionId放入请求头。
(1)修改登录的接口
@Controller
//@CrossOrigin
public class LoginController {
@PostMapping("/login")
@ResponseBody
public Result login(@RequestBody LoginVo loginVo){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword());
try {
subject.login(token);
//subject.getSession().getId()希望带当前会话id
return new Result(200,"登陆成功",subject.getSession().getId());
}catch (Exception e){
e.printStackTrace();
return new Result(500,"账号或密码错误",null);
}
}
}
注意:在登陆的时候会出现序列化报错!!!
解决方法:在实体类实现Serializable接口
@Data
public class User implements Serializable {
@TableId
private Integer id;
private String username;
private String userpwd;
private String sex;
private String address;
private String salt;
}
(2)修改前端登录方法
(3)修改main.js文件
//设置axios的请求拦截器
axios.interceptors.request.use(config=>{
//从localStorage中获取token的值
var item = localStorage.getItem("token");
if (item){
config.headers.token=item;
}
return config;
})
2.2 重写DefaultWebSessionManager的方法
(1)自定义MyWebSessionManager配置类
public class MyWebSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
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);
}
}
}
(2)修改shiro配置类
(3)修改shiroFilter过滤器
我们发现跨域请求,会发送两个请求:第一个OPTIONS请求,第二个请求是真实的请求。
OPTIONS请求:先头部队。
所以我们对OPTIONS请求都要放行。
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest request1 = (HttpServletRequest) request;
//获取请求方式
String method = request1.getMethod();
if ("OPTIONS".equals(method)){
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
3.设置前端前置路由守卫
//设置前置路由守卫
router.beforeEach((to,from,next)=>{
//to:到哪去 from:从哪来 next:下一站
let path = to.path;//获取路由的路径
if (path == "/login"){
return next();
}
//判断是否定登陆过
let token = sessionStorage.getItem("token");
if (token){
return next();
}
return next("/login");
})
4.如何防止恶意重复登录
5.退出登录接口
(1)编辑退出接口
@PostMapping("/logout")
public Result logout(){
Subject subject = SecurityUtils.getSubject();
//redis清空
subject.logout();
return new Result(200,"退出成功",null);
}
(2)编辑前端退出按钮
<el-button type="danger" @click="logout">退出登录</el-button>
logout(){
this.$http.post("http://localhost:8080/logout").then(result=>{
if (result.data.code == 200){
this.$message.success("退出成功");
//清空sessionStorage
sessionStorage.clear();
this.$router.push("/login")
}else {
this.$message.error("退出失败");
}
})
6.获取当前登录用户的信息
(1)编辑信息接口
@GetMapping("/info")
public Result info(){
Subject subject = SecurityUtils.getSubject();
Object principal = subject.getPrincipal();
return new Result(200,"查询成功",principal);
}
(2)编辑查看信息按钮
info(){
this.$http.get("http://localhost:8080/info").then(result=>{
this.userinfo = result.data.data;
console.log(result.data.data.username)
})
}
<el-button type="info" @click="info()">获取用户信息</el-button>
7.设定登录设备的个数
(1)引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
(2)配置 application.properties文件
#配置redis spring.redis.host=192.168.214.129
(3)编写后端登录接口
@Autowired
private StringRedisTemplate redisTemplate;
@PostMapping("/login")
public Result login(@RequestBody LoginVo loginVo){
Subject subject = SecurityUtils.getSubject();
//判断当前用户是否登陆过
if(subject.isAuthenticated()){
return new Result(200,"登陆成功",subject.getSession().getId());
}
UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword());
try {
String key = "shiro:user:"+loginVo.getUsername();
System.out.println(key+"===========================");
ValueOperations<String, String> forValue = redisTemplate.opsForValue();
int count = 0;
String s = forValue.get(key);
System.out.println(s+"------------------------------");
if (s !=null){
if (Integer.parseInt(s)>=1){
return new Result(400,"同时在线设备不能超过2台",subject.getSession().getId());
}else {
count++;
}
}else {
count = 0;
}
forValue.set(key,count+"");
subject.login(token);
//subject.getSession().getId()希望带当前会话id
return new Result(200,"登陆成功",subject.getSession().getId());
}catch (Exception e){
e.printStackTrace();
return new Result(500,"账号或密码错误",null);
}
}