前后端分离
一.创建前端页面
1.根据模版创建vue项目
2.配置vue
安装axios
安装element ui
安装 qs
配置main.js
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import axios from 'axios' // 后端项目的时候 http://localhost:8848 // axios设置一个默认的路径 // 创建实例时配置默认值 const instance = axios.create({ // 访问路径的时候假的一个基础的路径 baseURL: 'http://localhost:8848/' }); // 起一个名字注册到vue 里面 // Vue. prototype.名字 = axios Vue.prototype.$axios = instance; //响应拦截器 instance.interceptors.response.use( response=> { // 状态码 500 if(response.data.code==500){ // 跳转到 login 组件里面 // router.push({path:"/login"}); return; } // 数据 直接返回给axios return response.data.t; }, error=> { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 return Promise.reject(error); }); // 请求拦截器 instance.interceptors.request.use( config=> { // config 前端 访问后端的时候 参数 //config.headers['Authorization']="yyl" return config; }, error=> { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 return Promise.reject(error); });
3.创建页面
登录页面
<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> <script> export default { data() { var checkName = (rule, value, callback) => { if (!value) { return callback(new Error('名字不能为空')); }else{ callback(); } }; var validatePass = (rule, value, callback) => { if (value === '') { callback(new Error('请输入密码')); } else { callback(); } }; return { ruleForm: { username: '', password: '', }, rules: { password: [ { validator: validatePass, trigger: 'blur' } ], username: [ { validator: checkName, trigger: 'blur' } ] } }; }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { //alert('submit!'); this.$axios.post(`userlogin?uname=${this.ruleForm.username}&&pwd=${this.ruleForm.password}`) .then((d)=>{ sessionStorage.setItem("token",d.data.t[0]); sessionStorage.setItem("user",d.data.t[1]); this.$router.push("/main") }); } else { // console.log('error submit!!'); return false; } }); }, resetForm(formName) { this.$refs[formName].resetFields(); } } } </script> <style scoped> .login-form { width: 350px; margin: 160px auto; /* 上下间距160px,左右自动居中*/ background-color: rgb(255, 255, 255,0.8); /* 透明背景色 */ padding: 30px; border-radius: 20px; /* 圆角 */ } /* 背景 */ .login-container { position: absolute; width: 100%; height: 100%; background: url("../assets/timg.png"); } /* 标题 */ .login-title { color: #303133; text-align: center; } </style>
4.设置路由
引入qs
启动
npm run serve
访问页面
二.前端与后端Security结合
1.认证
登录认证成功
失败
2.登录跳转页面
1)创建main页面 配置路由
2)建立连接
登录访问
3.权限不允许
三.jwt
1.概念
什么是jwt
Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).该token被设计为紧凑且==安全==的,特别适用于==分布式站点的单点登录(SSO)场景==。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
官网: JSON Web Token Introduction - jwt.io
jwt的结构
jwt是用来生成凭证信息的 分为三部分 Header Payload Signature
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 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
2.使用jwt生成token
1)添加依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency>
2)修改配置文件
登陆成功
获取token 解码
第一部分和第二部分 并能没有加密 使用的是base64编码格式 通过网站 进行反编码就可以得出原来的数据
Base64 在线编码解码 | Base64 加密解密 - Base64.us
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJyZXNvdXJjZXMiOlsiUk9MRV9VU0VSIiwiU1oiLCJTWjIzIl0sInVzZXJuYW1lIjoienMifQ
.a4sbhLePMnH0AuYZz_OIOZPzxVosOe1u9hpv8ZHD87E
3.设置签发时间
获取token后解码
4.前端接收保存token
1)修改main.js
保存
5.解析token
1)创建解析文件
package com.example.filter; import cn.hutool.jwt.JWT; import cn.hutool.jwt.JWTUtil; import com.example.util.Result; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.stream.Collectors; @Component public class JwtFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 解析token /** * 1. 获取token * 有 解析 * 没有 返回一个json(401,“”,null) 没有认证 * 2. 有 * 2.1 校验token 真的还是假的 * 真 * 2.1.1 过 * 2.1.2 用户的信息 存放到安全框架的上下文的路径里面 * * 假 返回一个json数据 没有认证 */ String[] whitename = {"/userlogin"}; String token = request.getHeader("token"); // token 不为空 if(StringUtils.isNotBlank(token)){ // 有 解析 // boolean verify = JWTUtil.verify(token, "aaaaa".getBytes()); if(verify){ // 校验合格 // 获取 用户名字 和密码的信息 // token 的payload // map.put("username",authentication.getName()); // 认证成功之后 用户的名字 // // map.put("resources",allresoures); 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 requestURI = request.getRequestURI(); if(ArrayUtils.contains(whitename,requestURI)){ 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(); } } }
访问页面
SecurityConfiguration文件
package com.example.config; import cn.hutool.jwt.JWTPayload; import cn.hutool.jwt.JWTUtil; import com.example.util.Result; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.*; import java.util.stream.Collectors; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 配置 登录form 表单 // 路径的前面 必须加/ http.formLogin() // .loginPage("/login.html") // 登录的页面 localhost:8080/login.html .loginProcessingUrl("/userlogin") .successHandler((request, response, authentication) -> { System.out.println("********"+authentication); //资源 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); List<String> allresoures = authorities.stream().map(s->s.getAuthority()).collect(Collectors.toList()); System.out.println(allresoures); //认证成功 生成token Map map = new HashMap<>(); map.put("username",authentication.getName()); map.put("resources",allresoures); //设置token签发时间 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, "aaaaa".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();// 代表放行 "/login.html","/userlogin","/" // http.authorizeRequests().antMatchers("/test").hasRole("ADMIN"); http.authorizeRequests().anyRequest().authenticated(); // 权限不允许的时候 http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> { Result result = new Result(403,"权限不允许",null); printJsonData(response,result); }); // csrf 方便html 文件 能够通过 http.csrf().disable(); http.cors();//跨域 } @Resource private UserDetailsService userDetailsService; @Bean public PasswordEncoder getPassword(){ return new BCryptPasswordEncoder(); } // 自定义用户的信息 @Override protected 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"); // 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(); } } }