Spring-Security前后端分离权限认证

前后端分离

一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还让你配置登出页,错误页。

但是现在前后端分离才是正道,前后端分离的话,那就需要将返回的页面换成Json格式交给前端处理了

SpringSecurity默认的是采用Session来判断请求的用户是否登录的,但是不方便分布式的扩展,虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态,不过现在分布式的还是无状态的Jwt比较主流。 所以怎么让SpringSecurity变成前后端分离,可以采用Jwt来做认证

什么是jwt

Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).该token被设计为紧凑且==安全==的,特别适用于==分布式站点的单点登录(SSO)场景==。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

官网: JSON Web Token Introduction - jwt.io

jwt的结构

. 分割   三部分 

Header

Header 部分是一个JSON对象,描述JWT的元数据,通常是下面的样子。

{"alg": "HS256","typ": "JWT"}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256 (写成 HS256) ;typ属性表示这个令牌(token)的类型(type), JWT令牌统一写为JWT。

最后,将上面的JSON对象使用Base64URL算法转成字符串。

Payload(载荷)

Payload 部分也是一个JSON对象,==用来存放实际需要传递的数据==。JWT规定了7个官方字段,供选用。

iss (issuer):签发人

exp (expiration time):过期时间

sub (subject):主题

aud (audience):受众

nbf (Not Before):生效时间

iat (lssued At):签发时间

jti (JWT ID):编号

除了官方字段,==你还可以在这个部分定义私有字段==,下面就是一个例子。

{"sub": "1234567890","name" : "John Doe",“userid”:2"admin": true}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把==秘密信息==放在这个部分。这个JSON 对象也要使用Base64URL 算法转成字符串。

Signature

Signature部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个==密钥(secret)==。这个密钥只有==服务器才知道==,不能泄露给用户。然后,使用Header里面指定的==签名算法(默认是 HMAC SHA256)==,按照下面的公式产生签名。

HMACSHA256(base64UrlEncode(header) + ".”"+base64UrlEncode(payload),secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

1.项目添加hutool依赖

        <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.18</version></dependency>

http://t.csdnimg.cn/TA0Xx基于文章中连接数据库的实例基础上进行的前后端分离设计

2.搭建好一个vue项目

所需的导入包

3.修改配置文件 main.js

全局导入引入

import Vue from 'vue'
import App from './App.vue'
import router from './router'Vue.config.productionTip = falseimport ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';Vue.use(ElementUI);
import axios from 'axios'
// 后端项目的时候  http://localhost:8080
// axios设置一个默认的路径
// 创建实例时配置默认值
const instance = axios.create({// 访问路径的时候假的一个基础的路径baseURL: 'http://localhost:8080/',// withCredentials: true
});

请求拦截器与响应拦截器

// 请求拦截器
//
instance.interceptors.request.use( config=> {// config 前端  访问后端的时候  参数// 如果sessionStorage里面于token   携带着token  过去if(sessionStorage.getItem("token")){// token的值  放到请求头里面let token = sessionStorage.getItem("token");config.headers['token']=token;}// config.headers['Authorization']="yyl"return config;
}, error=> {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error);
});// 添加响应拦截器
instance.interceptors.response.use( response=> {console.log(response)// 状态码  500if(response.data.code!=200){alert("chucuole")console.log(response.data);router.push({path:"/login"});return;}return response;
}, error=> {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error);
});Vue.prototype.$axios = instance;
// 引入组件

挂载点

new Vue({router,render: h => h(App)
}).$mount('#app')

4.搭建一个.vue页面,并在 router 目录下的 index.js 文件配置好路由

<template><div class="login-container"><el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="login-form"><el-form-item label="用户名" prop="username"><el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input></el-form-item><el-form-item label="确认密码" prop="password"><el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm('ruleForm')">提交</el-button><el-button @click="resetForm('ruleForm')">重置</el-button></el-form-item></el-form></div>
</template>

搭建的页面包含基本的登录表单,在新建一个页面用于成功的页面展示,如 图中跳转的main.vue

 methods: {submitForm(formName) {this.$refs[formName].validate((valid) => {if (valid) {alert('submit!');// 请求  userlogin   userlogin//post i请求  json 数据  后端接受的时候  @RequestBodythis.$axios.post("userlogin",qs.stringify(this.ruleForm)).then(r=>{// 获取token的值console.log(r.data.t);// 存起来sessionStorage.setItem("token",r.data.t)// 成功之后    跳转 /mainthis.$router.push("/main");//console.log(r.data);})} else {console.log('error submit!!');return false;}});},}
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'home',component: HomeView},{path: '/login',name: 'login',component: () => import(/* webpackChunkName: "about" */ '../views/login.vue')},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')},{path: '/main',name: 'main',component: () => import(/* webpackChunkName: "about" */ '../views/main.vue')},
]
// 针对ElementUI导航栏中重复导航报错问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {return originalPush.call(this, location).catch(err => err)
}

这里配置了导航重复导航的问题,我们在响应拦截器配置了code非200的跳转登录的情况,为了避免登录失败导致跳转登录页面,重复导航的问题

5.后端加入跨域的配置文件

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration
public class CrossConfig {@Beanpublic CorsFilter corsFilter() {final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();final CorsConfiguration corsConfiguration = new CorsConfiguration();//corsConfiguration.setAllowCredentials(true);  // 允许 携带cookie 的信息corsConfiguration.addAllowedHeader("*"); // 允许所有的头corsConfiguration.addAllowedOrigin("*");// 允许所有的请求源corsConfiguration.addAllowedMethod("*");  // 所欲的方法   get post delete putsource.registerCorsConfiguration("/**", corsConfiguration); // 所有的路径都允许跨域return new CorsFilter(source);}}

6.统一返回数据实体

@Data
@AllArgsConstructor //
@NoArgsConstructor //
public class Result<T> {/*** code编码*/private Integer code = 200;/*** 消息*/private String msg = "操作成功";/*** 具体的数据*/private T t;/*** 成功的静态方法*/public static <T> Result  success(T t){return new Result<>(200,"操作成功",t);}public static <T>  Result  <T>  fail(){return new Result<>(500,"操作失败",null);}public static <T>  Result  <T>  forbidden(){return new Result<>(403,"权限不允许",null);}
}

7.对实现了UserDetailsService接口的service层进行了修改

@Service
public class MyUserDetailService implements UserDetailsService {@Resourceprivate TabUserMapper userMapper;@Resourceprivate TabUserRoleMapper userRoleMapper;@Resourceprivate TabRoleMapper roleMapper;@Resourceprivate TabMenuMapper menuMapper;//    根据用户的名字 加载用户的信息@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        username 代表前端传递过来的名字
//        根据名字去数据库查询一下有没有这个用户的信息QueryWrapper queryWrapper = new QueryWrapper();queryWrapper.eq("username",username);TabUser tabUser = userMapper.selectOne(queryWrapper);if(tabUser != null) {
//            有值 查询用户对应的角色的idQueryWrapper queryWrapper1 = new QueryWrapper();queryWrapper1.eq("uid",tabUser.getId());List<TabUserRole> tabUserRoles = userRoleMapper.selectList(queryWrapper1);List<Integer> rids = tabUserRoles.stream().map(tabUserRole -> tabUserRole.getRid()).collect(Collectors.toList());
//            根据角色的id 查询rcodeList<TabRole> tabRoles = roleMapper.selectBatchIds(rids);
//            角色的修信息 角色管理 修改角色的名字List<SimpleGrantedAuthority> collect = tabRoles.stream().map(tabRole -> new SimpleGrantedAuthority("ROLE_" + tabRole.getRcode())).collect(Collectors.toList());
//            根据角色的id 查询菜单的mcodeList<TabMenu> menus = menuMapper.selectCodeByRids(rids);List<SimpleGrantedAuthority> resources = menus.stream().map(tabMenu -> new SimpleGrantedAuthority(tabMenu.getMcode())).collect(Collectors.toList());
//            将角色的所有信息,和资源信息合并在一起List<SimpleGrantedAuthority> allresource = Stream.concat(collect.stream(), resources.stream()).collect(Collectors.toList());return new User(username, tabUser.getPassword(), allresource);}return null;}
}

8.数据链路层,对前后端的认证进行判断与返回的JSON数据

@Component
public class JwtFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {/*解析token1.获取token -> 存在 -> 解析不存在返回 null 没有认证2.效验token真的还是假的 真-> 过 -> 用户的信息存放到安全框架的上下文路径里面假-> 返回一个Json 数据 没有认证* */String[] whitename = {"/userlogin"};String token = request.getHeader("token");
//        token存在if(StringUtils.isNotBlank(token)) {
//            存在 解析boolean verify = JWTUtil.verify(token, "hp".getBytes());if(verify) {
//                效验合格
//                获取用户的名字 和密码的信息JWT jwt = JWTUtil.parseToken(token);String username = (String) jwt.getPayload("username");List<String> resources = (List<String>) jwt.getPayload("resources");
//                资源的信息List<SimpleGrantedAuthority> collect = resources.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());
//                保存用户的信息UsernamePasswordAuthenticationToken usertoken = new UsernamePasswordAuthenticationToken(username, null, collect);
//                存起来用户的信息SecurityContextHolder.getContext().setAuthentication(usertoken);
//                放行filterChain.doFilter(request,response);}else  {Result result = new Result(401, "没有登录", null);printJsonData(response,result);}}else {
//              查看是否在白名单 如果在 就放行String requestURL = request.getRequestURI();if(ArrayUtils.contains(whitename,requestURL)) {filterChain.doFilter(request,response);}else {Result result = new Result(401, "没有登录", null);printJsonData(response,result);}}}public void printJsonData(HttpServletResponse response, Result result) {try {response.setContentType("application/json;charset=utf8"); //json格式 编码是中文ObjectMapper objectMapper = new ObjectMapper();String s = objectMapper.writeValueAsString(result);// 使用objectMapper将result转化为json字符串PrintWriter writer = response.getWriter();writer.print(s);writer.flush();writer.close();}catch (Exception e) {e.printStackTrace();}}
}

9.对config文件进行修改(前后端分离情况)

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate JwtFilter jwtFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {//    配置 登录form 表单
//    路劲前面必须加 /http.formLogin().loginProcessingUrl("/userlogin").successHandler((request, response, authentication) -> {System.out.println("authentication"+authentication);
//                    资源的信息Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();List<String> allresources = authorities.stream().map(s -> s.getAuthority()).collect(Collectors.toList());System.out.println("allresources"+allresources);
//                    认证成功
//                    生成tokenMap map =new HashMap<>();map.put("username",authentication.getName());  // 认证成功之后 用户的名字map.put("resources",allresources);
//                    资源的信息设置签发时间
//                    Calendar instance = Calendar.getInstance(); //获取当前的时间
//                    Date time = instance.getTime();过期的时间设置为2小时之后
//                    instance.add(Calendar.HOUR,2); //两个小时之后
//                    Date time1 = instance.getTime();
//                    map.put(JWTPayload.EXPIRES_AT,time1);
//                    map.put(JWTPayload.ISSUED_AT,time);
//                    map.put(JWTPayload.NOT_BEFORE,time);String token = JWTUtil.createToken(map, "hp".getBytes());System.out.println(token);Result result = new Result(200,"登录成功",token);printJsonData(response,result);}) //前后端分离的时候 认证成功 走的方法.failureHandler((request, response, exception) -> {Result result = new Result(500, "失败", null);printJsonData(response,result);}); //认证失败 走的方法http.authorizeRequests().antMatchers("/userlogin").permitAll(); //代表放行 "/userlogin"http.authorizeRequests().anyRequest().authenticated();
//        权限不允许的时候http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {Result result = new Result(403, "权限不允许", null);printJsonData(response,result);});http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
//         csrf 方便html文件 能够通过http.csrf().disable();http.cors();   // 可以跨域}@Resourceprivate UserDetailsService userDetailsService;@Beanpublic PasswordEncoder getPassword() {return new BCryptPasswordEncoder();}//    自定义用户的信息@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(getPassword());}public void printJsonData(HttpServletResponse response, Result result) {try {response.setContentType("application/json;charset=utf8");ObjectMapper objectMapper = new ObjectMapper();String s = objectMapper.writeValueAsString(result);PrintWriter writer = response.getWriter();writer.print(s);writer.flush();writer.close();}catch (Exception e) {e.printStackTrace();}}
}

10.配置完成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/138189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

@ConfigurationProperties使用

一直有个疑问,在使用ConfigurationProperties注解作用一个配置类时,如果该配置类继承了一个父类,那么父类的那些配置字段是否可以读取配置信息。 答案是可以的&#xff0c;前提是父类对应字段的set方法是public。 BaseProperties.java Getter Setter public class BasePropert…

React 递归手写流程图展示树形数据

需求 根据树的数据结构画出流程图展示&#xff0c;支持新增前一级、后一级、同级以及删除功能&#xff08;便于标记节点&#xff0c;把节点数据当作label展示出来了&#xff0c;实际业务中跟据情况处理&#xff09; 文件结构 初始数据 [{"ticketTemplateCode": &…

uniapp vue2 vuex 持久化

1.vuex的使用 一、uniapp中有自带vuex插件&#xff0c;直接引用即可 二、在项目中新建文件夹store,在main.js中导入 在根目录下新建文件夹store,在此目录下新建index.js文件 index.js import Vue from vueimport Vuex from vuexVue.use(Vuex)const store new Vuex.Store(…

代码随想录图论部分-695. 岛屿的最大面积|1020. 飞地的数量

695. 岛屿的最大面积 题目&#xff1a;给你一个大小为 m x n 的二进制矩阵 grid 。岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&#xff0…

electron 内部api capturePage实现webview截图

官方文档 .capturePage([rect]) rect Rectangle (可选) - 要捕获的页面区域。 返回 Promise - 完成后返回一个NativeImage 在 rect内捕获页面的快照。 省略 rect 将捕获整个可见页面。 async function cap(){ let image await webviewRef.value.capturePage() console.log(im…

Postman的环境变量和全局变量

近期在复习Postman的基础知识&#xff0c;在小破站上跟着百里老师系统复习了一遍&#xff0c;也做了一些笔记&#xff0c;希望可以给大家一点点启发。 多种环境&#xff1a;开发环境、测试环境、预发布环境、生产环境&#xff0c;可以用环境变量来解决。 今天的分享就到这里&a…

【论文阅读】Progressive Spatio-Temporal Prototype Matching for Text-Video Retrieval

资料链接 论文链接&#xff1a;https://openaccess.thecvf.com/content/ICCV2023/papers/Li_Progressive_Spatio-Temporal_Prototype_Matching_for_Text-Video_Retrieval_ICCV_2023_paper.pdf 代码链接&#xff1a;https://github.com/imccretrieval/prost 背景与动机 文章发…

LabVIEW在OPC中使用基金会现场总线

LabVIEW在OPC中使用基金会现场总线 本文讨论了如何使用开放的OPC&#xff08;用于过程控制的OLE&#xff09;接口访问基金会现场总线网络和设备。 NI-FBUS通信管理器随附了一个OPC数据访问服务器。 &#xff08;NI-FBUS Configurator自动包含NI-FBUS通信管理器。&#xff09…

Visual Studio2010保姆式安装教程(VS2010 旗舰版),以及如何运行第一个C语言程序,超详细

安装前请关闭杀毒软件&#xff0c;系统防火墙&#xff0c;断开网络连接 参考链接&#xff1a;请点击 下载链接&#xff1a; 通过百度网盘分享的文件&#xff1a;VS2010.zip 链接:https://pan.baidu.com/s/1yQUUCxMJP7FMaistFX94SQ 提取码:96ga 复制这段内容打开「百度网盘APP …

Linux下的调试工具——GDB

GDB 1.什么是GDB GDB 是由 GNU 软件系统社区提供的调试工具&#xff0c;同 GCC 配套组成了一套完整的开发环境&#xff0c;GDB 是 Linux 和许多 类Unix系统的标准开发环境。 一般来说&#xff0c;GDB 主要能够提供以下四个方面的帮助&#xff1a; 启动程序&#xff0c;可以按…

GF0-57CQD-002 测量参数:加速度、速度、位移–现场可配置

GF0-57CQD-002 测量参数:加速度、速度、位移–现场可配置 GF0-57CQD-002 是一款创新的双通道变送器&#xff0c;专为精确的振动测量而设计。它激励并读取来自加速度计的信号&#xff0c;并将整体振动值作为电流/电压信号传输。它测量加速度、速度和位移等不同参数的振动。配置…

模电学习路径--google镜像chatgpt

交流通路实质 列出电路方程1&#xff0c;方程1对时刻t做微分 所得方程1‘ 即为 交流通路 方程1对时刻t做微分&#xff1a;两个不同时刻的方程1相减&#xff0c;并 令两时刻差为 无穷小 微分 改成 差 模电学习路径&#xff1a; 理论 《电路原理》清华大学 于歆杰 朱桂萍 陆文…

【数据结构】二叉树的遍历递归算法详解

二叉树的遍历 &#x1f4ab;二叉树的结点结构定义&#x1f4ab;创建一个二叉树结点&#x1f4ab;在主函数中手动创建一颗二叉树&#x1f4ab;二叉树的前序遍历&#x1f4ab;调用栈递归——实现前序遍历&#x1f4ab;递归实现中序和后序遍历 &#x1f4ab;二叉树的结点结构定义 …

稳定扩散AI 纹理生成器

推荐基于稳定扩散(stable diffusion) AI 模型开发的自动纹理工具&#xff1a; DreamTexture.js自动纹理化开发包 - NSDT 什么是稳定扩散&#xff1f; 从技术上讲&#xff0c;Stable Diffusion 是一种用于机器学习的潜在扩散模型 &#xff08;LDM&#xff09;。这种类型的专用深…

【dbeaver】添加mysql高低版本选择驱动

添加mysql高低版本选择驱动 连接到数据库->全部->查询mysql MySQL 版本驱动 8.0 MySQL 5 版本驱动 5.7.x 其他需要就&#xff1a;https://downloads.mysql.com/archives/c-j/ 密码查看 项目设置密码&#xff1a; File -> Project security ->设置密码 It i…

Ubuntu 22.04 安装水星无线 USB 网卡

我的 USB 网卡是水星 Mercury 的&#xff0c; 在 Ubuntu 22.04 下面没有自动识别。 没有无线网卡的时候只能用有线接到路由器上&#xff0c;非常不方便。 寻思着把无线网卡驱动装好。折腾了几个小时装好了驱动。 1.检查网卡类型 & 安装驱动 使用 lsusb 看到的不一定是准确…

法治智能起航 | 拓世法宝AI智慧政务一体机重塑法治格局,开启智能司法新篇章

在科技的巨轮推动下&#xff0c;我们的社会正快速迈向一个以数据和智能为核心的新时代。在这个波澜壮阔的变革中&#xff0c;人工智能&#xff08;AI&#xff09;显得尤为突出&#xff0c;它不仅是科技进步的象征&#xff0c;更是未来发展的助力者。 2023年&#xff0c;最高人…

医学影像系统源码(MRI、CT三维重建)

一、MRI概述 核磁共振成像&#xff08;英语&#xff1a;Nuclear Magnetic Resonance Imaging&#xff0c;简称NMRI&#xff09;&#xff0c;又称自旋成像&#xff08;英语&#xff1a;spin imaging&#xff09;&#xff0c;也称磁共振成像&#xff08;Magnetic Resonance Imag…

Labview利用声卡捕获波形

一般的计算机上自带的声卡&#xff0c;均既有A/D功能&#xff0c;又有D/A功能&#xff0c;就是一款具备基本配置的数据采集卡&#xff0c;并且技术成熟&#xff0c;性能稳定。 后台如下&#xff1a;

【Word自定义配置,超简单,图文并茂】自定义Word中的默认配置,比如标题大小与颜色(参考科研作图配色),正文字体等

▚ 01 自定义样式Styles中的默认标题模板 &#x1f4e2;自定义标题的显示效果&#xff0c;如下图所示&#xff1a; 1.1 自定义标题的模板Normal.dotm 1.1.1 选择所需修改的标题 新建一个空白Word文档&#xff0c;依次选择菜单栏的开始Home&#xff0c;样式Styles&#xff0c;…