现在开发流行使用VUE做前端,通过前后端分离的方式来架构系统。那么为什么要前后端分离呢?
首先,前后端分离是团队合作的产物,团队任务分工后,前端工程师专注前端业务展现,后端工程师专注业务逻辑。
其次,前后端分离有利于系统大流量的并发处理。分离后,前端通常会采取CDN加速,以达到快速展现的目标,同时减轻了网络带宽和服务器的压力。
由于本人在VUE上的造诣一般,这里就不多讲VUE,只是用来做一些显示和验证后端。
需要学习VUE的给你个传送门https://www.runoob.com/vue2/vue-tutorial.html
认证阅读几遍应该就没有什么问题,毕竟javascript的语法和java还是比较相似的。
首先,创建一个生产Token的类TokenGenerator
package com.james.framework.modules.sys.oauth2;
import com.james.framework.common.exception.RRException;
import java.security.MessageDigest;
import java.util.UUID;
public class TokenGenerator {
public static String generateValue() {
return generateValue(UUID.randomUUID().toString());
}
private static final char[] hexCode = "0123456789abcdef".toCharArray();
public static String toHexString(byte[] data) {
if(data == null) {
return null;
}
StringBuilder r = new StringBuilder(data.length*2);
for ( byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
/**
* token生产方法,采用MD5加密
* @param param
* @return
*/
public static String generateValue(String param) {
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] messageDigest = algorithm.digest();
return toHexString(messageDigest);
} catch (Exception e) {
throw new RRException("生成Token失败", e);
}
}
}
此处主要方法generateValue,通过对UUID进行MD5加密后,再做一次Hex处理。功能不复杂具体看代码。
然后,创建一个过滤器,拦截所有请求,检查header中是否携带token,没有token返回401,具体看OAuth2Filter代码。
package com.james.framework.modules.sys.oauth2;
import com.google.gson.Gson;
import com.james.framework.common.utils.Rest;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class OAuth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
String json = new Gson().toJson(Rest.error(401, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
Rest r = Rest.error(401, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}
添加SysLoginController,处理用户登陆:
package com.james.framework.modules.sys.controller;
import com.google.gson.Gson;
import com.james.framework.common.utils.Rest;
import com.james.framework.modules.sys.entity.SysUserEntity;
import com.james.framework.modules.sys.service.SysUserService;
import com.james.framework.modules.sys.service.SysUserTokenService;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Map;
/**
* 登录相关
*
*/
@RestController
public class SysLoginController extends AbstractController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserTokenService sysUserTokenService;
/**
* 登录
*/
@RequestMapping(value = "/sys/login", method = RequestMethod.POST)
public Map login(@RequestBody String jsonString)throws IOException {
Gson gson = new Gson();
SysUserEntity userPara = gson.fromJson(jsonString, SysUserEntity.class);
//用户信息
SysUserEntity user = sysUserService.queryByUserName(userPara.getUsername());
//账号不存在、密码错误
if(user == null || !user.getPassword().equals(new Sha256Hash(userPara.getPassword(), user.getSalt()).toHex())) {
return Rest.error("账号或密码不正确");
}
//账号锁定
if(user.getStatus() == 0){
return Rest.error("账号已被锁定,请联系管理员");
}
//生成token,并保存到数据库
Rest r = sysUserTokenService.createToken(user.getUserId());
//为了显示,如果有昵称,此处可以返回昵称
r.put("username",user.getUsername());
return r;
}
/**
* 退出
*/
@RequestMapping(value = "/sys/logout", method = RequestMethod.POST)
public Rest logout() {
sysUserTokenService.logout(getUserId());
return Rest.ok();
}
}
安装VUE
npm install vue
安装vue-cli
npm install --global vue-cli
安装webpack
npm install webpack –g
初始化项目
vue init webpack admin
这里创建了一个admin的项目
cd进入admin目录
安装依赖
npm install
启动项目
npm run dev
添加store模块,index.js代码如下,将token保存在localStorage中:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 存储token
Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
},
mutations: {
// 修改token,并将token存入localStorage
changeLogin (state, user) {
console.log("changeLogin::"+user.Authorization);
state.Authorization = user.Authorization;
localStorage.setItem('Authorization', user.Authorization);
}
}
});
export default store;
添加axios模块,用于和后台服务打交道。Index.js代码:
import Vue from 'vue'
import axios from 'axios'
axios.defaults.baseURL="http://localhost:8080" //后台地址
Vue.prototype.$ajax = axios
修改Main.js增加对token的检查,如果token存在于localStorage将token放在header中,没有token就进入登陆界面。
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router' // 引入路由配置
import element from './element'
import axios from './axios'
import store from './store'
Vue.config.productionTip = false
Vue.config.debug = true
/* eslint-disable no-new */
new Vue({
el: '#app',
router, // 等价于 router: router
store,
axios,
components: { App },
template: ' '
})
//给所有请求头部加上token
axios.interceptors.request.use(
config => {
//在所有请求头部添加token值
const token = store.state.token;
if (token) {
config.headers.Authorization = token;
}
return config
},
error => {
console.log("error")
return Promise.reject(error)
}
);
//异步请求后,判断token是否过期
axios.interceptors.response.use(
response =>{
return response;
},
error => {
if(error.response){
switch (error.response.status) {
case 401:
localStorage.removeItem('Authorization');
this.$router.push('/');
}
}
}
)
修改路由器,检查是否存在token,如果没有token就到login界面
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Main from '@/components/Main'
import Login from '@/components/Login'
Vue.use(Router) // 注册vue-router
const router = new Router({
routes: [
{
path: '/main',
name: 'Main',
component: Main
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
]
})
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
let token = localStorage.getItem('Authorization');
console.log(token)
if (token) {//如果有就直接到首页咯
next();
} else {
if(to.path=='/login'){//如果是登录页面路径,就直接next()
next();
}else{ //不然就跳转到登录;
next('/login');
}
}
});
export default router;
添加登陆界面Login.vue
登录
添加登陆后的Main界面:
主页面
欢迎!hello
完成后的文件:
默认vue端口为8080,和后段端口重复,我们可以在config目录下index.js中进行修改。
前端就基本完成,不过启动后还是会报错的,请求不到后端数据,这是因为跨域了!
给springboot添加跨域处理,增加一个跨域的config:
package com.james.framework.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Collections;
/**
* 跨域请求过滤器
*
*/
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Collections.singletonList(CorsConfiguration.ALL));
corsConfiguration.setAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
corsConfiguration.setAllowedMethods(Collections.singletonList(CorsConfiguration.ALL));
corsConfiguration.addExposedHeader("Authorization");
source.registerCorsConfiguration("/**", corsConfiguration);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
讲到这里这篇基本完成了,不过细心的朋友会发现springboot的代码中存在shiro,是因为下一步将shiro集成进来。