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.rhkb.cn/news/189524.html

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

相关文章

【python后端】- 初识Django框架

Django入门 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0c;大家一起学习成长&#xff01; 文章目录 Django入门…

[工业自动化-16]:西门子S7-15xxx编程 - 软件编程 - 西门子仿真软件PLCSIM

目录 前言&#xff1a; 一、PLCSIM仿真软件 1.1 PLCSIM仿真软件基础版&#xff08;内嵌&#xff09; 1.2 PLCSIM仿真软件与PLCSIM仿真软件高级版的区别&#xff1f; 1.3 PLCSIM使用 前言&#xff1a; PLC集成开发环境是运行在Host主机上&#xff0c;Host主机与PLC可以通过…

基于springboot实现桥牌计分管理系统项目【项目源码】计算机毕业设计

基于springboot实现桥牌计分管理系统演示 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#…

[HXPCTF 2021]includer‘s revenge

文章目录 方法一前置知识Nginx 在后端 Fastcgi 响应过大产生临时文件竞争包含绕过include_once限制 解题过程 方法二前置知识Base64 Filter 宽松解析iconv filter 解题过程 方法一 NginxFastCGI临时文件 前置知识 Nginx 在后端 Fastcgi 响应过大产生临时文件 www-data用户在n…

python-jupyter实现OpenAi语音对话聊天

1.安装jupyter 这里使用的是jupyter工具&#xff0c;安装时需要再cmd执行如下命令&#xff0c;由于直接执行pip install jupyter会很慢&#xff0c;咱们直接使用国内源 pip install --user jupyter -i http://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.t…

20231112_DNS详解

DNS是实现域名与IP地址的映射。 1.映射图2.DNS查找顺序图3.DNS分类和地址4.如何清除缓存 1.映射图 图片来源于http://egonlin.com/。林海峰老师课件 2.DNS查找顺序图 3.DNS分类和地址 4.如何清除缓存

Halcon WPF 开发学习笔记(4):Halcon 锚点坐标打印

文章目录 专栏前言锚点二次开发添加回调函数辅助Model类 下集预告 专栏 Halcon开发 博客专栏 WPF/HALCON机器视觉合集 前言 Halcon控件C#开发是我们必须掌握的&#xff0c;因为只是单纯的引用脚本灵活性过低&#xff0c;我们要拥有Halcon辅助开发的能力 锚点开发是我们常用的…

rv1126-rv1109-添加分区,定制固件,开机挂载功能

===================================================================== 修改分区: 这里是分区的txt文件选择; 这里是分区的划分,我这里回车了,方便看 FIRMWARE_VER: 8.1 MACHINE_MODEL: RV1126 MACHINE_ID: 007 MANUFACTURER: RV1126 MAGIC: 0x5041524B ATAG: 0x00200…

PCA(主成分分析)数据降维技术代码详解

引言 随着大数据时代的到来&#xff0c;我们经常会面临处理高维数据的问题。高维数据不仅增加了计算复杂度&#xff0c;还可能引发“维度灾难”。为了解决这一问题&#xff0c;我们需要对数据进行降维处理&#xff0c;即在不损失太多信息的前提下&#xff0c;将数据从高维空间…

7.运算符

目录 一.算数运算符 1、算术运算符 2、比较运算符 1、等号()用来判断数字、字符串和表达式是否相等。 2、安全等于运算符(<>) 3、不等于运算符(<>或者!) 4、小于或等于运算符(<) 5、小于运算符(<) 6、IS NULL(IS NULL)&#xff0c;IS NOT NULL 运算…

计算机网络(一)

一、什么是计算机网络、计算机协议&#xff1f; 计算机网络就是由计算机作为收发端&#xff0c;不同计算机相互连接的网络&#xff0c;包括互联网&#xff08;Internet&#xff09;&#xff0c;公司或者家用网络&#xff08;intranet&#xff09;等等&#xff1b;其中Internet…

敏捷开发是什么?敏捷开发流程是怎么样的?

1. 什么是敏捷开发&#xff1f; 敏捷开发是一种迭代、增量式的软件开发方法&#xff0c;旨在通过灵活、协作和快速响应变化的方式&#xff0c;提高开发团队的效率和产品的质量。相较于传统的瀑布式开发模型&#xff0c;敏捷开发更加注重用户需求的响应和团队协作&#xff0…

3 任务3 使用趋动云部署自己的stable-diffusion

使用趋动云部署自己的stable-diffusion 1 创建项目&#xff1a;2 初始化开发环境实例3 部署模型4 模型测试 1 创建项目&#xff1a; 1.进入趋动云用户工作台&#xff0c;选择&#xff1a;当前空间&#xff0c;请确保当前所在空间是注册时系统自动生成的空间。 a.非系统自动生成…

Linux学习第40天:Linux SPI 驱动实验(一):乾坤大挪移

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 主从工作方式完成数据交换&#xff0c;形象的说就是武侠中的乾坤大挪移。 本章实验的最终目的就是驱动 I.MX6UALPHA 开发板上的 ICM-20608 这个 SPI 接口的六轴传…

3D渲染原理及朴素JavaScript实现【不使用WebGL】

在网页中显示图像和其他平面形状非常容易。 然而&#xff0c;当涉及到显示 3D 形状时&#xff0c;事情就变得不那么容易了&#xff0c;因为 3D 几何比 2D 几何更复杂。 为此&#xff0c;你可以使用专用技术和库&#xff0c;例如 WebGL 和 Three.js。 但是&#xff0c;如果你只…

菜单栏管理软件 Bartender 3 mac中文版功能介绍

​Bartender 3 mac是一款菜单栏管理软件&#xff0c;该软件可以将指定的程序图标隐藏起来&#xff0c;需要时呼出即可。 Bartender 3 mac功能介绍 Bartender 3完全支持macOS Sierra和High Sierra。 更新了macOS High Sierra的用户界面 酒吧现在显示在菜单栏中&#xff0c;使其…

Linux系统上搭建高可用Kafka集群(使用自带的zookeeper)

本次在CentOS7.6上搭建Kafka集群 Apache Kafka 是一个高吞吐量的分布式消息系统&#xff0c;被广泛应用于大规模数据处理和实时数据管道中。本文将介绍在CentOS操作系统上搭建Kafka集群的过程&#xff0c;以便于构建可靠的消息处理平台。 文件分享&#xff08;KafkaUI、kafka…

Leetcode421. 数组中两个数的最大异或值

Every day a Leetcode 题目来源&#xff1a;421. 数组中两个数的最大异或值 解法1&#xff1a;贪心 位运算 初始化答案 ans 0。从最高位 high_bit 开始枚举 i&#xff0c;也就是 max⁡(nums) 的二进制长度减一。设 newAns ans 2i&#xff0c;看能否从数组 nums 中选两个…

servlet 的XML Schema从哪边获取

servlet 6.0的规范定义&#xff1a; https://jakarta.ee/specifications/servlet/6.0/ 其中包含的三个XML Schema&#xff1a;web-app_6_0.xsd、web-common_6_0.xsd、web-fragment_6_0.xsd。但这个页面没有给出下载的链接地址。 正好我本机有Tomcat 10.1.15版本的源码&#…

头歌答案--爬虫实战

目录 urllib 爬虫 第1关&#xff1a;urllib基础 任务描述 第2关&#xff1a;urllib进阶 任务描述 requests 爬虫 第1关&#xff1a;requests 基础 任务描述 第2关&#xff1a;requests 进阶 任务描述 网页数据解析 第1关&#xff1a;XPath解析网页 任务描述 第…