权限系统 为保证登录有效要做前后台的权限校验
写登录接口时 登陆成功向session中传递一个加密的AccessToken里面装着登录信息以及过期时间
- 先写一个AccessToken类来装信息
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class AccessToken {
//登录信息仅用name代替
private String name;
//过期时间
private long expire;
public AccessToken(String name){
this.name = name;
this.expire = System.currentTimeMillis() + 1000L * 60 * 2;
}
}
加密解密工具类
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
public class EncryptorUtil {
private static StandardPBEStringEncryptor encryptor;
static {
encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("et2004");
}
public static String encrypt(String text){
return encryptor.encrypt(text);
}
public static String decrypt(String ciphertext){
return encryptor.decrypt(ciphertext);
}
}
统一响应的返回格式
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVo<T> {
private int code;
private String msg;
private T data;
private static final int SUCCESS_CODE=200;
private static final int ERROR_CODE=201;
private static final int NO_AUTH_CODE = 999;
private static final String SUCCESS_MSG="SUCCESS";
//成功!
public static <T> ResultVo<T> success(T data){
return new ResultVo<>(SUCCESS_CODE,SUCCESS_MSG,data);
}
public static ResultVo failed(String message) {
return new ResultVo<>(ERROR_CODE,message,"");
}
public static ResultVo noAuth(String message) {
return new ResultVo(NO_AUTH_CODE,message,"");
}
}
登录接口:
/**
* 登录接口
* 1.根据用户名查询用户
* 如果用户为空 则返回用户名或密码错误
* 2.用户名不为空就验证密码
* 密码不匹配 则返回用户名或密码错误
* 3.验证通过
* 创建AccessToken
* 把AccessToken转成json字符串
* 加密AccessToken的json字符串返回前端
* */
@PostMapping("/login")
public ResultVo login(@RequestParam String name,@RequestParam String password){
//
User user = userService.getUser(name);
if(ObjectUtils.isEmpty(user)){
return ResultVo.failed("用户名或密码错误");
}
//验证用户密码
password = DigestUtils.md5Hex(password);
if(!StringUtils.equals(password,user.getPassword())){
return ResultVo.failed("用户名或密码错误");
}
AccessToken token = new AccessToken(name);
String jsonToken = JSONObject.toJSONString(token);
return ResultVo.success(EncryptorUtil.encrypt(jsonToken));
}
前端接到AccessToken后将其存到sessionStorage中
let qs = new URLSearchParams();
qs.append("name", this.loginForm.name);
qs.append("password", this.loginForm.password);
this.$http
.post("/user/login",qs)
.then((res) => {
if (res.data.code === 200) {
window.sessionStorage.setItem("token", res.data.data);
this.$router.push("/");
} else {
this.$message.error(res.data.msg);
}
})
.catch((e) => {
console.log(e);
this.$message.error("网络异常");
});
自此前端的请求都要加上一个请求头token 在main.js中添加请求拦截器
返回的响应我们也要拿到响应中的token装到session中 添加响应拦截器
未登录时我们不能从导航栏跳转路由 设置全局路由守卫
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './assets/css/et2004.css'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
//全局前置导航守卫
router.beforeEach((to,from,next)=>{
//to:即将进入的路由
//from:即将离开的路由
//如果访问的是登录 直接放行
if(to.path === '/login'){
return next()
}
//验证是否有登录的token
let token = window.sessionStorage.getItem('token')
if(token){
return next()
}else{
ElementUI.Message.error("登录失效,请重新登录")
return next('/login')
}
})
//请求拦截器
axios.interceptors.request.use(config =>{
let url = config.url
if(url === '/user/login'){
return config
}
let token =window.sessionStorage.getItem('token')
if(token){
config.headers.token = token
return config
}else{
router.push('/login')
Promise.reject('用户未登录')
}
//标识放行
return config
},error=>{
Promise.reject(error)
})
//响应拦截器
axios.interceptors.response.use(response=>{
//1.如果登录接口的响应 直接放行
if(response.config.url === "/user/login"){
return response
}
//2.判断返回码是否999.999表示无权限访问
if(response.data.code === 999){
console.log('认证失败:',response.data.msg);
router.push('/login')
return Promise.reject(error)
}
//拿到后端响应的token,重新设置到sessionStorage中
console.log();
let newToken = response.headers.token
window.sessionStorage.setItem('token',newToken)
return response
},error=>{
Promise.request(error)
})
import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:8000/car-app'
Vue.prototype.$http = axios
Vue.prototype.contextPath = 'http://localhost:8000/car-app'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
而且在我们的后端也需要对所有除了login以外的请求携带的token做验证处理 这需要请求拦截器
LoginInterceptor
拦截器需要在配置类中配置
//登录拦截器
@Component
@Slf4j
public class LoginInterseptor implements HandlerInterceptor {
private static final String OPTIONS_METHOD = "OPTIONS";
private static final String TOKEN = "token";
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.放行OPTION预检请求
String method = request.getMethod();
if (StringUtils.equals(OPTIONS_METHOD,method.toUpperCase())){
log.info("options请求");
return true;
}
//2.验证请求头中是否有token
String tokenHeader = request.getHeader(TOKEN);
if(StringUtils.isEmpty(tokenHeader)){
this.noAuth(response,"没有携带认证数据");
return false;
}
//3.解码token(解码之后是json 将json转换成AccessToken),验证有效期
try{
String jsonToken = EncryptorUtil.decrypt(tokenHeader);
AccessToken accessToken = JSONObject.parseObject(jsonToken, AccessToken.class);
//3.1 验证有效
System.out.println(new Date().getTime()+"-"+accessToken.getExpire());
if(new Date().getTime()-accessToken.getExpire()>=0){
log.warn("token过期");
this.noAuth(response,"token过期");
return false;
}
//3.2 验证用户名
User user = userService.getUser(accessToken.getName());
if(ObjectUtils.isEmpty(user)){
log.warn("用户不存在");
this.noAuth(response,"认证失败");
return false;
}
//重新生成token返回前端
accessToken = new AccessToken(accessToken.getName());
System.out.println(accessToken.getName());//
response.setHeader(TOKEN,EncryptorUtil.encrypt(JSONObject.toJSONString(accessToken)));
return true;
}catch(Exception e){
log.error(e.getMessage(),e);
this.noAuth(response,"服务器异常");
return false;
}
}
private void noAuth(HttpServletResponse response,String message) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
PrintWriter pw = response.getWriter();
pw.print(JSONObject.toJSONString(ResultVo.noAuth(message)));
pw.flush();
pw.close();
}
}
配置拦截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
LoginInterseptor loginInterseptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterseptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")// 登录请求放行
.excludePathPatterns("/pics/**");// 访问图片放行
}
}