springboot,vue,shiro整合 关于登录认证功能
首先是session问题
传统session认证
http协议是一种无状态协议,即浏览器发送请求到服务器,服务器是不知道这个请求是哪个用户发来的。为了让服务器知道请求是哪个用户发来的,需要让用户提供用户名和密码来进行认证。当浏览器第一次访问服务器(假设是登录接口),服务器验证用户名和密码之后,服务器会生成一个sessionid(只有第一次会生成,其它会使用同一个sessionid),并将该session和用户信息关联起来,然后将sessionid返回给浏览器,浏览器收到sessionid保存到Cookie中,当用户第二次访问服务器是就会携带Cookie值,服务器获取到Cookie值,进而获取到sessionid,根据sessionid获取关联的用户信息。
由于前后的分离开发,传统的session方式不能用来做登录认证了,因为存在前端是部署在ngnix代理服务器中,springboot是部署tomcat服务器中,两个项目存在跨域问题,所以session不能共享,每次获取session地址不同,session对象就不同
解决思路
1、首次登录时,后端服务器判断用户账号密码正确之后, 生成 sessionId,设置过期时间保存到redis服务器中并返回给前端
@ApiOperation(value = "登入")
@PostMapping("/login")
public ResultVO loginPost(@RequestParam("userName")String userName, @RequestParam("userPass")String userPass){
//根据前端传递过来的name和passowrd生成shrio的UsernamePasswordToken
userPass = new Md5Hash(userPass,userName,3).toString();
UsernamePasswordToken token = new UsernamePasswordToken(userName, userPass);
//写shiro的认证逻辑
Subject subject = SecurityUtils.getSubject();
//因为可能出现异常,所以直接try catch
try {
//调用login方法,传入token
subject.login(token);
String sid = (String) subject.getSession().getId();
//如果登录没有出现异常的话,就可以通过getPrincinpal()获取登录用户
User user= (User)subject.getPrincipal();
//获取sessionId
String sessionId = (String) subject.getSession().getId();
//成功返回id和对象
LoginVo loginVo=new LoginVo();
loginVo.setUser(user);
loginVo.setSessionId(sessionId);
AppUser appUser=new AppUser();
BeanUtils.copyProperties(user,appUser);
return ResultVOUtil.success(loginVo);
} catch (AuthenticationException e) {
return ResultVOUtil.error(400,e.getMessage());
}
}
2、前端拿到后端返回的 sessionId, 存储在 localStroage/sessionStroage里
const user = {
state: {
id:window.sessionStorage.getItem('id'),
userId:null,
userName:null,
userNickname:null,
userIcon:null,
userNumber:null,
mail:null,
roleId:null
},
mutations: {
//将id保存到sessionStorage里,id表示登陆状态
SET_ID: (state, data) => {
state.id = data
window.sessionStorage.setItem('id', data)
},
//获取用户信息
SET_USER: (state, data) => {
// 把用户信息存起来
state.userId = data.userId
state.userName = data.userName
state.userNickname = data.userNickname
state.userIcon = data.userIcon
state.userNumber = data.userNumber
state.mail = data.mail
state.roleId = data.roleId
},
//登出
LOGOUT: (state) => {
// 登出的时候要清除token
state.id = null
state.userId = null
state.userName = null
state.userNickname = null
state.userIcon = null
state.userNumber = null
window.sessionStorage.removeItem('id')
}
},
actions: {
}
};
export default user;
getUser(name,pass){
login(name,pass).then(res => {
if(res.code==0){
this.$message({ message: "登录成功", type: 'success' })
this.$store.commit('SET_ID', res.data.sessionId)
this.$store.commit('SET_USER', res.data.user)
this.centerDialogVisible=false
}else{
this.$message({ message: res.msg, type: 'error' })
}
})
},
3、前端每次路由跳转到需要认证的页面, 判断 localStroage/sessionStroage有无 sessionId,没有则跳转到登录页
我的是局部路由的,大家根据不同需求写
beforeRouteEnter:(to,from,next)=>{
next(vm => {
vm.init()
if (vm.$store.state.user.id==null){
vm.$message.error('你还没有登录,请先登录');
next('/home');
}
})
4、封装拦截器,每次请求接口,在请求头里携带 sessionId
import axios from 'axios'
import { Notification } from 'element-ui';
export function request(config) {
const instance = axios.create({
baseURL: '/api',
tiemout: 5000
})
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
instance.interceptors.request.use(config => {
const id =window.sessionStorage.getItem('id')
// const id =this.$store.state.user.id
id && (config.headers.common['Authorization'] = 'beared'+id)
return config
}, err => {
console.log(err);
})
instance.interceptors.response.use(res => {
if (res.data.code == 401) {
Notification({
title: '温馨提示',
message: '您还没有登录,请登录后再进行相关操作',
position: 'bottom-right'
})
}
return res.data
}, err => {
console.log(err);
})
return instance(config)
}
5、后端统一拦截判断 请求头有无 sessionId ,没有或者 sessionId过期,返回401
6、前端得到 401 状态码,重定向到登录页面
登录认证总结
用到的技术有路由守卫,axiox封装请求响应拦截器,安全框架shiro,vuex和持久化插件(VuexPersistence)
关键点是session会话
把传统的session会话交给shiro管理,重写session校验机制,设置成根据请求头(sessionId)获取session信息
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 设置会话时间
*/
public CustomSessionManager() {
super();
setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48);
}
/**
* 请求头Authorization:sessionid
* 指定sessionid的获取方法
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头信息
String id= WebUtils.toHttp(request).getHeader("Authorization");
if (StringUtils.isEmpty(id)) {
//为空则创建新的sessionid
return super.getSessionId(request,response);
}else {
id=id.replaceAll("beared","");
//在哪里获取
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
//id是什么
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
//是否要验证
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
}
}
配置shiro配置redis存储session,这样的好处,根据sessionId把session保存在数据库中,既使重启服务器也不会丢失session
前端实现:安装vuex持久化插件VuexPersistence,把用户信息和sessionId保存到vuex里面
封装拦截器,每次请求接口,在请求头里携带 vuex取出的sessionId,后端根据路径判断是否有权限或是否认证
我的shiro配置
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* roles: 该资源必须得到角色权限才可以访问
*/
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
filters.put("roles",shiroRoleFilter());
filters.put("authc",shiroLoginFilter());
Map<String,String> filterMap = new LinkedHashMap<String,String>();
filterMap.put("/byuser/**","authc");
// filterMap.put("/byuser/home","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
@Bean(name="shiroLoginFilter")
public ShiroLoginFilter shiroLoginFilter() {
return new ShiroLoginFilter();
}
@Bean(name="shiroRoleFilter")
public ShiroRoleFilter shiroRoleFilter() {
return new ShiroRoleFilter();
}
@Bean(name="corsBasicFilter")
public CorsBasicFilter corsBasicFilter() {
return new CorsBasicFilter();
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("loginRealm")LoginRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
//将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());
//将自定义的redis缓存管理器注册到安全管理器中
// securityManager.setCacheManager(cacheManager());
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name="loginRealm")
public LoginRealm getRealm(){
return new LoginRealm();
}
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/**
* 1.redis的控制器,操作redis
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(7000);
return redisManager;
}
/**
* 2.sessionDao
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
/**
* 3.会话管理器
*/
public DefaultWebSessionManager sessionManager() {
//自定义子类
CustomSessionManager sessionManager = new CustomSessionManager();
// sessionManager.setSessionDAO(redisSessionDAO());
//禁用所有的Cookie 这样自己设置的session就不起作用了
// sessionManager.setSessionIdCookieEnabled(false);
// sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 4.缓存管理器
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
//开启对shior注解的支持
// @Bean
// public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
// AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
// advisor.setSecurityManager(securityManager);
// return advisor;
// }
// @Bean
// public ShiroDialect getShiroDialect(){
// return new ShiroDialect();
// }
}
springboot,vue,shiro整合 关于登录认证功能的更多相关文章
- Shiro+Mybatis实现登录认证、授权功能
Shiro+Mybatis实现登录认证.授权功能 一.实现登录认证功能 1.流程: 跟据用户提交表单的账号,经Mybatis框架在数据库中查出User对象: 如果User为空,则会抛出异常:Unkno ...
- SpringBoot与Shiro整合权限管理实战
SpringBoot与Shiro整合权限管理实战 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] *观看本文章需要有一定SpringBoot整合经验* Shiro框架简介 Apach ...
- springboot 与 shiro 整合 (简洁版)
前言: 网上有很多springboot 与 shiro 整合的资料,有些确实写得很好, 对学习shiro和springboot 都有很大的帮助. 有些朋友比较省事, 直接转发或者复制粘贴.但是没有经过 ...
- 基于SpringBoot搭建应用开发框架(二) —— 登录认证
零.前言 本文基于<基于SpringBoot搭建应用开发框架(一)——基础架构>,通过该文,熟悉了SpringBoot的用法,完成了应用框架底层的搭建. 在开始本文之前,底层这块已经有了很 ...
- 记录一下在SpringBoot中实现简单的登录认证
代码参考博客: https://blog.csdn.net/weixin_37891479/article/details/79527641 在做学校的课设的时候,发现了安全的问题,就不怀好意的用户有 ...
- 一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器
一.前言 最近花了一个月时间完成了一套基于Spring Boot+Vue+Shiro前后端分离的代码生成器,目前项目代码已基本完成 止步传统CRUD,进阶代码优化: 该项目可根据数据库字段动态生成 c ...
- Springboot + Vue + shiro 实现前后端分离、权限控制
本文总结自实习中对项目对重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelA ...
- SpringBoot与Shiro整合
一.数据库设计 这里主要涉及到五张表:用户表,角色表(用户所拥有的角色),权限表(角色所涉及到的权限),用户-角色表(用户和角色是多对多的),角色-权限表(角色和权限是多对多的).表结构建立的sq ...
- springboot vue简单整合
1.vue项目 (1)修改config/index.js (2)执行 npm run build 生成静态文件,在dist目录 2.springboot项目 (1)在src/main/resource ...
随机推荐
- as(android studio)的初次使用
链接:https://blog.csdn.net/qq_28808627/article/details/50058805
- SDNU_ACM_ICPC_2020_Winter_Practice_2nd
A - [The__Flash]的矩阵 给你一个m×n的整数矩阵,在上面找一个x×y的子矩阵,使子矩阵中所有元素的和最大. Input输入数据的第一行为一个正整数T,表示有T组测试数据.每一组测试数据 ...
- 学习笔记(24)- plato-训练中文模型
先处理中文语料.参考上篇笔记 1. 准备model_definition_file文件 官方文档给了例子, plato/example/config/ludwig/metalWOZ_seq2seq_l ...
- java用JSONObject生成json
Json在前后台传输中,是使用最多的一种数据类型.json生成的方法有很多,自己只是很皮毛的知道点,用的时候,难免会蒙.现在整理下 第一种: import net.sf.json.JSONArray; ...
- HDU 4825 Xor Sum(字典树)
嗯... 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4825 这道题更明确的说是一道01字典树,如果ch[u][id^1]有值,那么就向下继续查找/ ...
- JS的起源和发展
JS概述 JS主要由三部分组成 ECMAScript BOM DOM 对于ECMAScript的理解 这是JS这个大厦的地基和骨架,是核心的部分 BOM:提供与浏览器进行交互的方法和接口 DOM;提供 ...
- 一个基础的问题 多个$(function(){})里面的函数 为什么在下一个$(function(){})里没法执行。
先看下例子 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <tit ...
- 1、MyBatis框架底层初涉
1.拜年 哈哈,现在是过年了,祝大家新年好. 本来大过年的是不打算碰电脑的,(抢票除外,三疯同学现在还没抢到票,然后突然又延长假期了).现在疫情严重,被堵家里不能出去了.不能为国家做贡献,但是起码不能 ...
- win10 下安装 tesseract + tesserocr
首先参考博文一贴:https://blog.csdn.net/u014179267/article/details/80908790 1.那么安装这两个模块是为了爬虫的时候识别验证码用的,但是安装的过 ...
- POJ-1087 A Plug for UNIX (网络流)
思路 电器数1 ~ 100,附带100种接口,注意题目:You notice that some of the devices use plugs for which there is no rece ...