1. 前端准备工作--都在全局main.js页面中设置的
1.1. 创建Vue工程后,并导入element ui和axios,添加连接后端项目的路径,把axios挂载到Vue
1.2. 前置路由守卫(所有路由跳转前核实一下身份)
//前置路由守卫--所有的路由跳转都先经过这里
// to:即将要访问的路径 from:从哪里来 next:放行函数
//解决登录之后关闭浏览器之后,还可以不用登录就能进入登录后的页面
router.beforeEach((to, from, next) => {
//如果是 登录页面 或者 token为真 就放行 其他情况强制跳转到登录页面
const token = sessionStorage.getItem('token');
if (to.path==="/login" || token){
return next();
}
return next("/login");
})
1.3. 请求过滤器(所有请求前都需携带令牌给后端)
//请求拦截器--每次请求前都会经过的
//这里的意义就是需要每次向后端发送请求都要携带者token令牌
axios.interceptors.request.use(function (config) {
let token = sessionStorage.getItem('token');
if (token){
config.headers.token = token;
}
return config;
})
1.4. 响应拦截器(后端响应过来的json数据,根据code状态码判断成功/失败,并返回json数据给对应的请求,这里请求处只需要写对应的操作即可)
//设置响应拦截器
axios.interceptors.response.use(function (response) {
if (response.data.code===200){
Vue.prototype.$message.success(response.data.msg)
return response;
}else {
Vue.prototype.$message.error(response.data.msg);
return response;
}
})
1.5. 页面布局路由导入跳转和请求
<template>
<div class="home">
<h1>主页页面</h1>
<el-button type="success" @click="add()">增加</el-button>
<el-button type="danger" @click="del()">删除</el-button>
<el-button type="info" @click="update()">修改</el-button>
<el-button type="primary" @click="sel()">查询</el-button>
<el-button type="warning" @click="exp()">导出</el-button>
<el-button icon="el-icon-switch-button" @click="outLogin()">退出</el-button>
</div>
</template>
<script>
export default {
name: 'home',
data(){
return{
}
},
methods:{
//退出
outLogin(){
this.$axios.post("/logout").then((res) => {
sessionStorage.removeItem("token")
this.$router.push("/login")
})
},
//添加用户
add(){
this.$axios.get("/user/insert").then(res=>{
})
},
//删除用户
del(){
this.$axios.get("/user/delete").then(res=>{
})
},
//修改用户
update(){
this.$axios.get("/user/update").then(res=>{
})
},
//查询
sel(){
this.$axios.get("/user/select").then(res=>{
})
},
//导出
exp(){
this.$axios.get("/user/export").then(res=>{
})
}
},
}
</script>
<style>
.home{
margin: auto;
text-align: center;
}
</style>
2. 后端准备工作
2.1. JWT工具类,客户端与服务器通信,都要带上jwt,放在http请求的头信息中
2.1.1. 引入jar
<!--引入jwt的依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
2.1.2. 创建jwt的工具类
①创建令牌---②校验令牌---③根据token获取自定义的信息
package com.wjy.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Verification;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtil {
private static final String SECRET = "wjyGyh";
//通过jwt创建token令牌
public static String createToken(Map<String,Object> map){
Map<String, Object> head = new HashMap<>();
head.put("alg", "HS256");
head.put("typ", "JWT");
Date date = new Date();//当前时间--发布时间
Calendar instance = Calendar.getInstance();//获取当前时间
instance.set(Calendar.SECOND,7200);//当前时间的基础上添加2个小时
Date time = instance.getTime();
return JWT.create()
.withHeader(head)//头部信息
.withIssuedAt(date)//发布日期
.withExpiresAt(time)//过期时间
.withClaim("userinfo",map)//存放用户信息--设置个人信息
.sign(Algorithm.HMAC256(SECRET));//签名
}
//校验token
public static boolean verify(String token){
Verification require = JWT.require(Algorithm.HMAC256(SECRET));
try {
require.build().verify(token);
return true;
} catch (JWTVerificationException e) {
System.out.println("token错误,校验失败");
return false;
}
}
//根据token获取自定义的信息
public static Map<String,Object> getTokenInfo(String token,String key){
return JWT.require(Algorithm.HMAC256(SECRET))
.build()
.verify(token)
.getClaim(key)
.asMap();
}
}
2.2. 加密、认证、授权、权限管理
(包含了对密码加密、用户认证授权、登录前后的权限设置==》开启允许跨域
由于都是无返回值类型的,所有使用到了fastjson)
package com.wjy.config;
import com.alibaba.fastjson2.JSON;
import com.wjy.filter.LoginFilter;
import com.wjy.service.MyUserDetailService;
import com.wjy.util.JWTUtil;
import com.wjy.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Configuration
//如果springboot的版本2.7.0以上有其他的写法
public class MySercurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private StringRedisTemplate redisTemplate;
//密码编码器--会自动加密
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailService myUserDetailService;
//用户认证和授权
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService);
}
@Autowired
private LoginFilter loginFilter;
//登录前后的权限设置
@Override
protected void configure(HttpSecurity http) throws Exception {
//把自定义的过滤器放在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);
//登录页面
http.formLogin()
//登录页面
.loginPage("/login.html")
//登录成功后跳转的页面
.loginProcessingUrl("/login")
//登录成功后跳转的页面--必须都是Post请求
//.successForwardUrl("/success")
.successHandler(successHandler())
//登录失败后跳转的页面
//.failureForwardUrl("/error")
.failureHandler(failureHandler())
//上面的页面请求无需认证--无需权限认证特权
.permitAll();
//退出登录--通过登录时候token令牌存入redis中,这里从头中拿到token,如何删除redis中的token
http.logout(item->{
item.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
String token = httpServletRequest.getHeader("token");
//如果token验证成功
//删除redis中的token
redisTemplate.delete("login"+token);
//返回json数据
R r = new R(200, "退出成功", null);
String jsonString = JSON.toJSONString(r);
writer.println(jsonString);
writer.flush();
writer.close();
});
});
//权限不足跳转的页面
// http.exceptionHandling().accessDeniedPage("/error.html");
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
//如果使用的登录页面不是自己的--禁止跨越伪造请求的过滤器
http.csrf().disable();
//security登录允许跨域
http.cors();
//其他所有请求都需要认证
http.authorizeRequests()
.anyRequest()
.authenticated();
}
//登录成功后给前端返回的json数据--并存到redis中
private AuthenticationSuccessHandler successHandler() {
return (httpServletRequest, httpServletResponse, authentication)->{
//设置响应的编码
httpServletResponse.setContentType("application/json;charset=utf-8");
//获取输出对象
PrintWriter writer = httpServletResponse.getWriter();
//封装map数据
Map<String, Object> map = new HashMap<>();
//①存放用户名
map.put("username", authentication.getName());
//获取权限
List<String> collect = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList());
//②存放权限码
map.put("permissions", collect);
//1.生成token
String token = JWTUtil.createToken(map);
//存放在redis中
redisTemplate.opsForValue().set("login"+token,"");
//2.放入token,返回json数据
R r = new R(200, "登录成功", token);
//转为json数据
String jsonString = JSON.toJSONString(r);
//给前端判断的依据
writer.println(jsonString);
//刷新流
writer.flush();
//关闭流
writer.close();
};
}
//登录失败后给前端返回的json数据
private AuthenticationFailureHandler failureHandler() {
return (httpServletRequest, httpServletResponse, e)->{
//设置响应的编码--->获取输出对象
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
R r = new R(500, "登录失败", e.getMessage());
//转为json数据-->给前端判断的依据-->刷新流-->关闭流
String jsonString = JSON.toJSONString(r);
writer.println(jsonString);
writer.flush();
writer.close();
};
}
//权限不足后给前端返回的json数据
private AccessDeniedHandler accessDeniedHandler() {
return (httpServletRequest, httpServletResponse, e)->{
//设置响应的编码--->获取输出对象
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
//创建R对象 并转换成json数据给前端 判断作为的依据-->刷新流-->关闭流
R r = new R(403, "权限不足", e.getMessage());
String jsonString = JSON.toJSONString(r);
writer.println(jsonString);
writer.flush();
writer.close();
};
}
}
未登录--自定义顾虑器
package com.wjy.filter;
import com.alibaba.fastjson.JSON;
import com.wjy.util.JWTUtil;
import com.wjy.vo.R;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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.Map;
import java.util.stream.Collectors;
@Component//交于spring容器管理
public class LoginFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setContentType("application/json;charset=utf-8");
//获取请求路径
String path = request.getRequestURI();
//获取请求方式
String method = request.getMethod();
//判断请求路径是否为登录路径--是的话放行
if ("/login".equals(path) && "POST".equals(method)){
filterChain.doFilter(request,response);
return;
}
//1.获取token令牌--从请求头中获取
String token = request.getHeader("token");
//2.判断token是否为空
//①token为空
if (StringUtils.isEmpty(token)){
R r = new R(500, "未登录", null);
PrintWriter writer = response.getWriter();
String jsonString = JSON.toJSONString(r);
writer.write(jsonString);
writer.flush();
writer.close();
return;
}
//②token不为空或者redis中不存在token--验证token失败的情况(token不正确)
if (!JWTUtil.verify(token) || !redisTemplate.hasKey("login"+token)){
R r = new R(500, "token失效,登录失败", null);
PrintWriter writer = response.getWriter();
String jsonString = JSON.toJSONString(r);
writer.write(jsonString);
writer.flush();
writer.close();
return;
}
//③token不为空--验证token成功--获取token中的信息
//获取token中的信息
SecurityContext context = SecurityContextHolder.getContext();
//--从JWT的工具类中获取
Map<String, Object> userinfo = JWTUtil.getTokenInfo(token, "userinfo");
//获取用户名
Object username = userinfo.get("username");
//获取权限码-->并转成需要的集合格式类型
List<String> permissions = (List<String>) userinfo.get("permissions");
List<SimpleGrantedAuthority> collect = permissions.stream()
.map(item -> new SimpleGrantedAuthority(item))
.collect(Collectors.toList());
//创建token
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, collect);
//将token放入上下文
context.setAuthentication(authenticationToken);
//④验证token成功--放行
filterChain.doFilter(request,response);
}
}
2.3. 跨域配置类
package com.wjy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//跨域配置--允许跨域全局--自己定义的
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//*全部,所有的意思
registry.addMapping("/**")// 所有接口
//是否发送Cookie
.allowCredentials(true)// 是否发送 Cookie
//放行哪些原始域
.allowedOrigins("*")// 支持域--之后改成自己服务器的地址
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})// 支持方法
.allowedHeaders("*")
.exposedHeaders("*");
}
}
3. 业务层--登录
package com.wjy.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wjy.entity.User;
import com.wjy.mapper.PermissionMapper;
import com.wjy.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户信息--username必须唯一
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
User user = userMapper.selectOne(wrapper);
//判断用户是否存在
if (Objects.nonNull(user)){
//查询当前用户具有的权限
List<SimpleGrantedAuthority> collection = permissionMapper.selectByUserId(
user.getUserid())//根据用户id查询权限
.stream()//转换流
.map(p -> new SimpleGrantedAuthority(p.getPercode()))//把每次查询的权限码封装成SimpleGrantedAuthority
.collect(Collectors.toList());//转换为集合
return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getUserpwd(),collection);
}
return null;
}
}