SpringSecurity+前端项目+redis完成认证授权的代码
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));//签名}//校验tokenpublic 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 {@Autowiredprivate StringRedisTemplate redisTemplate;//密码编码器--会自动加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Autowiredprivate MyUserDetailService myUserDetailService;//用户认证和授权@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailService);}@Autowiredprivate LoginFilter loginFilter;//登录前后的权限设置@Overrideprotected 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中的tokenhttp.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中的tokenredisTemplate.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.生成tokenString 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 {@Overrideprotected 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());//创建tokenUsernamePasswordAuthenticationToken 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 {@Overridepublic 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 {@Autowiredprivate UserMapper userMapper;@Autowiredprivate PermissionMapper permissionMapper;@Overridepublic 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;}
}