手把手教你用springboot打造标准系统(三)--Token登陆和跨域处理

现在开发流行使用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<String, Object> 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: '<App/>'
})

//给所有请求头部加上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

<template>
    <el-row type="flex" justify="center">
        <el-form ref="loginForm" :model="user" :rules="rules" status-icon label-width="80px">
            <el-form-item label="用户名" prop="username">
                <el-input v-model="user.username" ></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password" >
                <el-input v-model="user.password" type="password"></el-input>
            </el-form-item>
            <el-form-item>
                    <el-button type="primary" icon="el-icon-upload" @click="login">登录</el-button>
            </el-form-item>
        </el-form>
    </el-row>
</template>

<script>
    import { mapMutations } from 'vuex';
    import store from '../store';
    export default {
        methods: {
            ...mapMutations(['changeLogin']),
            login () {
                let _this = this;
                this.$refs.loginForm.validate((valid) => {//验证是否输入

                    if (valid) {
                       this.$ajax.post('/sys/login', this.user,
                         {
                          headers:{
                                'Content-Type': 'application/json;charset=UTF-8'  //这里加上头部信息
                              }
                         }
                       ).then((res) => {
                            if (res.data) {
                                if(res.data.code==0){
                                    this.$notify({
                                        type: 'success',
                                        message: '欢迎你,' + res.data.username + '!',
                                        duration: 3000
                                    })

                                    // 将用户token保存到vuex中
                                    _this.changeLogin({ Authorization: res.data.token });
                                    
                                    //如果写入就进入
                                    if (store.state.Authorization) {
                                        this.$router.replace('/main')
                                        console.log(store.state.Authorization)
                                    } else {
                                        this.$router.replace('/login');
                                    }
                                }else{
                                    this.$notify({
                                        type: 'fail',
                                        message:  res.data.msg + '!'
                                    })
                                }
                            } else {
                                this.$message({
                                    type: 'error',
                                    message: '用户名或密码错误',
                                    showClose: true
                                })
                            }
                        }).catch((err) => {
                            this.$message({
                                type: 'error',
                                message: '网络错误,请重试',
                                showClose: true
                            })
                        })

                        //this.$router.replace('/')
                    }
                    else {
                         return false
                    }
                })
            }

        },
        data () {
            return {
                user: {
                    username: '',
                    password: ''
                },
                rules: {
                    username: [
                        {required: true, message: '用户名不能为空', trigger: 'blur'}
                    ],
                    password: [
                        {required: true, message: '密码不能为空', trigger: 'blur'}
                    ]
                }
            }
        }
    }
</script>

添加登陆后的Main界面:

<template>
    <div>
        <h1>主页面</h1>
        欢迎!<b @click="hello">hello</b>
    </div>
</template>

<script>
    export default {
        methods: {
            hello () {
                this.$router.replace('/hello')
            },
            getMenuList () {
                this.$ajax.post('sys/menu/nav',{}, {
                    headers:{
                         'Content-Type': 'application/json;charset=UTF-8'  //这里加上头部信息
                    }
                 }
                 ).then((res) => {
                    vm.menuList = res.menuList;
                    window.permissions = res.permissions;
                 }).catch((err) => {
                    this.$message({
                        type: 'error',
                        message: '网络错误,请重试',
                        showClose: true
                    })
                })
            }

        }
    }
</script>

完成后的文件:

默认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集成进来。

发布了4 篇原创文章 · 获赞 0 · 访问量 506
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览