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…

for循环、while循环和do while循环有什么不同

for循环、while循环和do while循环是三种常见的循环语句&#xff0c;它们的主要不同点在于循环条件的判断时机和执行顺序。具体如下&#xff1a; for循环 for循环适用于已知循环次数的循环结构&#xff0c;它的语法格式为&#xff1a; for (初始化表达式; 循环条件; 更新表达…

深度学习之pytorch第一课

学习使用pytorch&#xff0c;然后进行简单的线性模型的训练与保存 学习代码如下&#xff1a; import numpy as np import torch import torch.nn as nn x_value [i for i in range(11)] x_train np.array(x_value,dtypenp.float32) print(x_train.shape) x_train x_train.r…

nodejs 异步架构

nodejs的核心之一就是非阻塞的异步IO&#xff0c;于是想知道它是怎么实现的&#xff0c;挖了下nodejs源码&#xff0c;找到些答案&#xff0c;在此跟大家分享下。首先&#xff0c;我用了一段js代码test-fs-read.js做测试&#xff0c;代码如下&#xff1a; var path require(pa…

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(…

core-site.xml,yarn-site.xml,hdfs-site.xml,mapred-site.xml配置

core-site.xml <?xml version"1.0" encoding"UTF-8"?> <?xml-stylesheet type"text/xsl" href"configuration.xsl"?> <!--Licensed under the Apache License, Version 2.0 (the "License");you may no…

微服务如何做负载均衡?

笔者在参与联通某子公司时&#xff0c;遇到了这样一个问题。感觉比较实际&#xff0c;特来记录一波。 先看腾讯混元的解答&#xff1a; 微服务架构中&#xff0c;负载均衡是必不可少的。在微服务中&#xff0c;负载均衡可以通过以下几种方式来实现&#xff1a; 1. DNS轮询&am…

【objectarx.net】读写扩展字典

在objectarx.net中操作扩展字典

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

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

flutter实现上拉加载下拉刷新

效果如下&#xff1a; flutter实现上拉加载下拉刷新 使用到的库是easy_refresh 安装请查看官网 接口用的是提供的接口https://reqres.in/ 请求接口用到的库是dio 下面主要是介绍如何使用easy_refresh实现上拉加载数据&#xff0c;详细学习其它例子请查看easy_refresh main…

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 背景与动机 文章发…

【微软技术栈】C#.NET 依赖项注入

本文内容 多个构造函数发现规则使用扩展方法注册服务组框架提供的服务服务生存期服务注册方法作用域验证范围场景 .NET 支持依赖关系注入 (DI) 软件设计模式&#xff0c;这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。 .NET 中的依赖关系注入是框架的内置部分&#…

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 …

C++ 数组学习资料

C 数组学习资料 目录 什么是数组&#xff1f;声明和初始化数组访问数组元素多维数组数组和指针常见的数组操作数组的限制和注意事项 什么是数组&#xff1f; 在 C 中&#xff0c;数组是一种用于存储相同类型元素的数据结构。它是一个固定大小的连续内存块&#xff0c;每个元…

Linux下的调试工具——GDB

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